firewire: core: integrate software-forced bus resets with bus management
authorStefan Richter <stefanr@s5r6.in-berlin.de>
Thu, 8 Jul 2010 14:09:06 +0000 (16:09 +0200)
committerStefan Richter <stefanr@s5r6.in-berlin.de>
Tue, 13 Jul 2010 07:58:27 +0000 (09:58 +0200)
Bus resets which are triggered
  - by the kernel drivers after updates of the local nodes' config ROM,
  - by userspace software via ioctl
shall be deferred until after >=2 seconds after the last bus reset.

If multiple modifications of the local nodes' config ROM happen in a row,
only a single bus reset should happen after them.

When the local node's link goes from inactive to active or vice versa,
and at the two occasions of bus resets mentioned above --- and if the
current gap count differs from 63 --- the bus reset should be preceded
by a PHY configuration packet that reaffirms the gap count.  Otherwise a
bus manager would have to reset the bus again right after that.

This is necessary to promote bus stability, e.g. leave grace periods for
allocations and reallocations of isochronous channels and bandwidth,
SBP-2 reconnections etc.; see IEEE 1394 clause 8.2.1.

This change implements all of the above by moving bus reset initiation
into a delayed work (except for bus resets which are triggered by the
bus manager workqueue job and are performed there immediately).  It
comes with a necessary addition to the card driver methods that allows
to get the current gap count from PHY registers.

Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
drivers/firewire/core-card.c
drivers/firewire/core-cdev.c
drivers/firewire/core-transaction.c
drivers/firewire/core.h
drivers/firewire/ohci.c
include/linux/firewire.h

index 6c316cfe70c472d6bc93bb158bf946c5a4d355c3..2bb5c036e80692176d3db524fbffed8add9f49f0 100644 (file)
@@ -204,6 +204,45 @@ void fw_core_remove_descriptor(struct fw_descriptor *desc)
 }
 EXPORT_SYMBOL(fw_core_remove_descriptor);
 
+static int reset_bus(struct fw_card *card, bool short_reset)
+{
+       int reg = short_reset ? 5 : 1;
+       int bit = short_reset ? PHY_BUS_SHORT_RESET : PHY_BUS_RESET;
+
+       return card->driver->update_phy_reg(card, reg, 0, bit);
+}
+
+void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset)
+{
+       /* We don't try hard to sort out requests of long vs. short resets. */
+       card->br_short = short_reset;
+
+       /* Use an arbitrary short delay to combine multiple reset requests. */
+       fw_card_get(card);
+       if (!schedule_delayed_work(&card->br_work,
+                                  delayed ? DIV_ROUND_UP(HZ, 100) : 0))
+               fw_card_put(card);
+}
+EXPORT_SYMBOL(fw_schedule_bus_reset);
+
+static void br_work(struct work_struct *work)
+{
+       struct fw_card *card = container_of(work, struct fw_card, br_work.work);
+
+       /* Delay for 2s after last reset per IEEE 1394 clause 8.2.1. */
+       if (card->reset_jiffies != 0 &&
+           time_is_after_jiffies(card->reset_jiffies + 2 * HZ)) {
+               if (!schedule_delayed_work(&card->br_work, 2 * HZ))
+                       fw_card_put(card);
+               return;
+       }
+
+       fw_send_phy_config(card, FW_PHY_CONFIG_NO_NODE_ID, card->generation,
+                          FW_PHY_CONFIG_CURRENT_GAP_COUNT);
+       reset_bus(card, card->br_short);
+       fw_card_put(card);
+}
+
 static void allocate_broadcast_channel(struct fw_card *card, int generation)
 {
        int channel, bandwidth = 0;
@@ -230,13 +269,13 @@ static const char gap_count_table[] = {
 void fw_schedule_bm_work(struct fw_card *card, unsigned long delay)
 {
        fw_card_get(card);
-       if (!schedule_delayed_work(&card->work, delay))
+       if (!schedule_delayed_work(&card->bm_work, delay))
                fw_card_put(card);
 }
 
-static void fw_card_bm_work(struct work_struct *work)
+static void bm_work(struct work_struct *work)
 {
-       struct fw_card *card = container_of(work, struct fw_card, work.work);
+       struct fw_card *card = container_of(work, struct fw_card, bm_work.work);
        struct fw_device *root_device;
        struct fw_node *root_node;
        int root_id, new_root_id, irm_id, bm_id, local_id;
@@ -413,7 +452,7 @@ static void fw_card_bm_work(struct work_struct *work)
                fw_notify("phy config: card %d, new root=%x, gap_count=%d\n",
                          card->index, new_root_id, gap_count);
                fw_send_phy_config(card, new_root_id, generation, gap_count);
-               fw_core_initiate_bus_reset(card, 1);
+               reset_bus(card, true);
                /* Will allocate broadcast channel after the reset. */
                goto out;
        }
@@ -465,7 +504,8 @@ void fw_card_initialize(struct fw_card *card,
 
        card->local_node = NULL;
 
-       INIT_DELAYED_WORK(&card->work, fw_card_bm_work);
+       INIT_DELAYED_WORK(&card->br_work, br_work);
+       INIT_DELAYED_WORK(&card->bm_work, bm_work);
 }
 EXPORT_SYMBOL(fw_card_initialize);
 
@@ -491,7 +531,6 @@ int fw_card_add(struct fw_card *card,
 }
 EXPORT_SYMBOL(fw_card_add);
 
-
 /*
  * The next few functions implement a dummy driver that is used once a card
  * driver shuts down an fw_card.  This allows the driver to cleanly unload,
@@ -507,6 +546,11 @@ static int dummy_enable(struct fw_card *card,
        return -1;
 }
 
+static int dummy_read_phy_reg(struct fw_card *card, int address)
+{
+       return -ENODEV;
+}
+
 static int dummy_update_phy_reg(struct fw_card *card, int address,
                                int clear_bits, int set_bits)
 {
@@ -547,6 +591,7 @@ static int dummy_enable_phys_dma(struct fw_card *card,
 
 static const struct fw_card_driver dummy_driver_template = {
        .enable          = dummy_enable,
+       .read_phy_reg    = dummy_read_phy_reg,
        .update_phy_reg  = dummy_update_phy_reg,
        .set_config_rom  = dummy_set_config_rom,
        .send_request    = dummy_send_request,
@@ -568,7 +613,7 @@ void fw_core_remove_card(struct fw_card *card)
 
        card->driver->update_phy_reg(card, 4,
                                     PHY_LINK_ACTIVE | PHY_CONTENDER, 0);
-       fw_core_initiate_bus_reset(card, 1);
+       fw_schedule_bus_reset(card, false, true);
 
        mutex_lock(&card_mutex);
        list_del_init(&card->link);
@@ -588,12 +633,3 @@ void fw_core_remove_card(struct fw_card *card)
        WARN_ON(!list_empty(&card->transaction_list));
 }
 EXPORT_SYMBOL(fw_core_remove_card);
-
-int fw_core_initiate_bus_reset(struct fw_card *card, int short_reset)
-{
-       int reg = short_reset ? 5 : 1;
-       int bit = short_reset ? PHY_BUS_SHORT_RESET : PHY_BUS_RESET;
-
-       return card->driver->update_phy_reg(card, reg, 0, bit);
-}
-EXPORT_SYMBOL(fw_core_initiate_bus_reset);
index 7a690c466ce9ab54bf22452bc976afaacc6c9b4f..ee2e87353102c420b86343786215eacc9a016ecd 100644 (file)
@@ -820,8 +820,9 @@ static int ioctl_send_response(struct client *client, union ioctl_arg *arg)
 
 static int ioctl_initiate_bus_reset(struct client *client, union ioctl_arg *arg)
 {
-       return fw_core_initiate_bus_reset(client->device->card,
+       fw_schedule_bus_reset(client->device->card, true,
                        arg->initiate_bus_reset.type == FW_CDEV_SHORT_RESET);
+       return 0;
 }
 
 static void release_descriptor(struct client *client,
index 7813da8a12939153012a6be83f02b8f8a757ca11..5f5a7852f7ac5afc3cd7690d6e94773106b045ed 100644 (file)
@@ -426,9 +426,21 @@ void fw_send_phy_config(struct fw_card *card,
                        int node_id, int generation, int gap_count)
 {
        long timeout = DIV_ROUND_UP(HZ, 10);
-       u32 data = PHY_IDENTIFIER(PHY_PACKET_CONFIG) |
-                  PHY_CONFIG_ROOT_ID(node_id) |
-                  PHY_CONFIG_GAP_COUNT(gap_count);
+       u32 data = PHY_IDENTIFIER(PHY_PACKET_CONFIG);
+
+       if (node_id != FW_PHY_CONFIG_NO_NODE_ID)
+               data |= PHY_CONFIG_ROOT_ID(node_id);
+
+       if (gap_count == FW_PHY_CONFIG_CURRENT_GAP_COUNT) {
+               gap_count = card->driver->read_phy_reg(card, 1);
+               if (gap_count < 0)
+                       return;
+
+               gap_count &= 63;
+               if (gap_count == 63)
+                       return;
+       }
+       data |= PHY_CONFIG_GAP_COUNT(gap_count);
 
        mutex_lock(&phy_config_mutex);
 
index 3000dd74acfd26ae8438e653b0fe5e387a8cad28..ff6c90922001b0d8222326e6110ff9b171e20b55 100644 (file)
@@ -51,6 +51,7 @@ struct fw_card_driver {
        int (*enable)(struct fw_card *card,
                      const __be32 *config_rom, size_t length);
 
+       int (*read_phy_reg)(struct fw_card *card, int address);
        int (*update_phy_reg)(struct fw_card *card, int address,
                              int clear_bits, int set_bits);
 
@@ -102,8 +103,8 @@ void fw_card_initialize(struct fw_card *card,
 int fw_card_add(struct fw_card *card,
                u32 max_receive, u32 link_speed, u64 guid);
 void fw_core_remove_card(struct fw_card *card);
-int fw_core_initiate_bus_reset(struct fw_card *card, int short_reset);
 int fw_compute_block_crc(__be32 *block);
+void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset);
 void fw_schedule_bm_work(struct fw_card *card, unsigned long delay);
 
 static inline struct fw_card *fw_card_get(struct fw_card *card)
@@ -225,6 +226,9 @@ void fw_core_handle_response(struct fw_card *card, struct fw_packet *packet);
 int fw_get_response_length(struct fw_request *request);
 void fw_fill_response(struct fw_packet *response, u32 *request_header,
                      int rcode, void *payload, size_t length);
+
+#define FW_PHY_CONFIG_NO_NODE_ID       -1
+#define FW_PHY_CONFIG_CURRENT_GAP_COUNT        -1
 void fw_send_phy_config(struct fw_card *card,
                        int node_id, int generation, int gap_count);
 
index a4bbf3dadf58c55d7d084382dc6de578e5de106b..bb6a92bc9e6a2bfc8211a877a345d18462011a45 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/mm.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
+#include <linux/mutex.h>
 #include <linux/pci.h>
 #include <linux/pci_ids.h>
 #include <linux/spinlock.h>
@@ -182,6 +183,8 @@ struct fw_ohci {
         */
        spinlock_t lock;
 
+       struct mutex phy_reg_mutex;
+
        struct ar_context ar_request_ctx;
        struct ar_context ar_response_ctx;
        struct context at_request_ctx;
@@ -517,13 +520,10 @@ static int write_phy_reg(const struct fw_ohci *ohci, int addr, u32 val)
        return -EBUSY;
 }
 
-static int ohci_update_phy_reg(struct fw_card *card, int addr,
-                              int clear_bits, int set_bits)
+static int update_phy_reg(struct fw_ohci *ohci, int addr,
+                         int clear_bits, int set_bits)
 {
-       struct fw_ohci *ohci = fw_ohci(card);
-       int ret;
-
-       ret = read_phy_reg(ohci, addr);
+       int ret = read_phy_reg(ohci, addr);
        if (ret < 0)
                return ret;
 
@@ -541,13 +541,38 @@ static int read_paged_phy_reg(struct fw_ohci *ohci, int page, int addr)
 {
        int ret;
 
-       ret = ohci_update_phy_reg(&ohci->card, 7, PHY_PAGE_SELECT, page << 5);
+       ret = update_phy_reg(ohci, 7, PHY_PAGE_SELECT, page << 5);
        if (ret < 0)
                return ret;
 
        return read_phy_reg(ohci, addr);
 }
 
+static int ohci_read_phy_reg(struct fw_card *card, int addr)
+{
+       struct fw_ohci *ohci = fw_ohci(card);
+       int ret;
+
+       mutex_lock(&ohci->phy_reg_mutex);
+       ret = read_phy_reg(ohci, addr);
+       mutex_unlock(&ohci->phy_reg_mutex);
+
+       return ret;
+}
+
+static int ohci_update_phy_reg(struct fw_card *card, int addr,
+                              int clear_bits, int set_bits)
+{
+       struct fw_ohci *ohci = fw_ohci(card);
+       int ret;
+
+       mutex_lock(&ohci->phy_reg_mutex);
+       ret = update_phy_reg(ohci, addr, clear_bits, set_bits);
+       mutex_unlock(&ohci->phy_reg_mutex);
+
+       return ret;
+}
+
 static int ar_context_add_page(struct ar_context *ctx)
 {
        struct device *dev = ctx->ohci->card.device;
@@ -1676,7 +1701,7 @@ static int configure_1394a_enhancements(struct fw_ohci *ohci)
                clear = PHY_ENABLE_ACCEL | PHY_ENABLE_MULTI;
                set = 0;
        }
-       ret = ohci_update_phy_reg(&ohci->card, 5, clear, set);
+       ret = update_phy_reg(ohci, 5, clear, set);
        if (ret < 0)
                return ret;
 
@@ -1856,12 +1881,8 @@ static int ohci_enable(struct fw_card *card,
                  OHCI1394_HCControl_BIBimageValid);
        flush_writes(ohci);
 
-       /*
-        * We are ready to go, initiate bus reset to finish the
-        * initialization.
-        */
-
-       fw_core_initiate_bus_reset(&ohci->card, 1);
+       /* We are ready to go, reset bus to finish initialization. */
+       fw_schedule_bus_reset(&ohci->card, false, true);
 
        return 0;
 }
@@ -1936,7 +1957,7 @@ static int ohci_set_config_rom(struct fw_card *card,
         * takes effect.
         */
        if (ret == 0)
-               fw_core_initiate_bus_reset(&ohci->card, 1);
+               fw_schedule_bus_reset(&ohci->card, true, true);
        else
                dma_free_coherent(ohci->card.device, CONFIG_ROM_SIZE,
                                  next_config_rom, next_config_rom_bus);
@@ -2570,6 +2591,7 @@ static int ohci_queue_iso(struct fw_iso_context *base,
 
 static const struct fw_card_driver ohci_driver = {
        .enable                 = ohci_enable,
+       .read_phy_reg           = ohci_read_phy_reg,
        .update_phy_reg         = ohci_update_phy_reg,
        .set_config_rom         = ohci_set_config_rom,
        .send_request           = ohci_send_request,
@@ -2645,6 +2667,7 @@ static int __devinit pci_probe(struct pci_dev *dev,
        pci_set_drvdata(dev, ohci);
 
        spin_lock_init(&ohci->lock);
+       mutex_init(&ohci->phy_reg_mutex);
 
        tasklet_init(&ohci->bus_reset_tasklet,
                     bus_reset_tasklet, (unsigned long)ohci);
index db30a752a87a5ef0a3cb812629c49a6ff07b789c..adc5b55e6e5f6cfcf2e0fb19d0b2b6af1fdddbd6 100644 (file)
@@ -114,8 +114,10 @@ struct fw_card {
 
        struct list_head link;
 
-       /* Work struct for BM duties. */
-       struct delayed_work work;
+       struct delayed_work br_work; /* bus reset job */
+       bool br_short;
+
+       struct delayed_work bm_work; /* bus manager job */
        int bm_retries;
        int bm_generation;
        __be32 bm_transaction_data[2];