firewire: core: use non-reentrant workqueue with rescuer
authorStefan Richter <stefanr@s5r6.in-berlin.de>
Wed, 13 Oct 2010 11:39:46 +0000 (13:39 +0200)
committerStefan Richter <stefanr@s5r6.in-berlin.de>
Tue, 10 May 2011 20:53:45 +0000 (22:53 +0200)
firewire-core manages the following types of work items:

fw_card.br_work:
  - resets the bus on a card and possibly sends a PHY packet before that
  - does not sleep for long or not at all
  - is scheduled via fw_schedule_bus_reset() by
      - firewire-ohci's pci_probe method
      - firewire-ohci's set_config_rom method, called by kernelspace
        protocol drivers and userspace drivers which add/remove
Configuration ROM descriptors
      - userspace drivers which use the bus reset ioctl
      - itself if the last reset happened less than 2 seconds ago

fw_card.bm_work:
  - performs bus management duties
  - usually does not (but may in corner cases) sleep for long
  - is scheduled via fw_schedule_bm_work() by
      - firewire-ohci's self-ID-complete IRQ handler tasklet
      - firewire-core's fw_device.work instances whenever the root node
        device was (successfully or unsuccessfully) discovered,
refreshed, or rediscovered
      - itself in case of resource allocation failures or in order to
        obey the 125ms bus manager arbitration interval

fw_device.work:
  - performs node probe, update, shutdown, revival, removal; including
    kernel driver probe, update, shutdown and bus reset notification to
    userspace drivers
  - usually sleeps moderately long, in corner cases very long
  - is scheduled by
      - firewire-ohci's self-ID-complete IRQ handler tasklet via the
        core's fw_node_event
      - firewire-ohci's pci_remove method via core's fw_destroy_nodes/
        fw_node_event
      - itself during retries, e.g. while a node is powering up

iso_resource.work:
  - accesses registers at the Isochronous Resource Manager node
  - usually does not (but may in corner cases) sleep for long
  - is scheduled via schedule_iso_resource() by
      - the owning userspace driver at addition and removal of the
        resource
      - firewire-core's fw_device.work instances after bus reset
      - itself in case of resource allocation if necessary to obey the
        1000ms reallocation period after bus reset

fw_card.br_work instances should not, and instances of the others must
not, be executed in parallel by multiple CPUs -- but were not protected
against that.  Hence allocate a non-reentrant workqueue for them.

fw_device.work may be used in the memory reclaim path in case of SBP-2
device updates.  Hence we need a workqueue with rescuer and cannot use
system_nrt_wq.

Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Reviewed-by: Tejun Heo <tj@kernel.org>
drivers/firewire/core-card.c
drivers/firewire/core-cdev.c
drivers/firewire/core-device.c
drivers/firewire/core-transaction.c
drivers/firewire/core.h

index f05fc7bfceeb09eec1f19cec0ede696087d1fa00..bb8c4d22b03e95ad53c8381a07aa9f0823a6cc96 100644 (file)
@@ -228,8 +228,8 @@ void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool 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))
+       if (!queue_delayed_work(fw_wq, &card->br_work,
+                               delayed ? DIV_ROUND_UP(HZ, 100) : 0))
                fw_card_put(card);
 }
 EXPORT_SYMBOL(fw_schedule_bus_reset);
@@ -241,7 +241,7 @@ static void br_work(struct work_struct *work)
        /* Delay for 2s after last reset per IEEE 1394 clause 8.2.1. */
        if (card->reset_jiffies != 0 &&
            time_before64(get_jiffies_64(), card->reset_jiffies + 2 * HZ)) {
-               if (!schedule_delayed_work(&card->br_work, 2 * HZ))
+               if (!queue_delayed_work(fw_wq, &card->br_work, 2 * HZ))
                        fw_card_put(card);
                return;
        }
index 64768c2194f1f8b2ad094934c5addb9025f24cb5..aa1131d26e30a850766c13ca56749c2235199d97 100644 (file)
@@ -149,7 +149,7 @@ static void release_iso_resource(struct client *, struct client_resource *);
 static void schedule_iso_resource(struct iso_resource *r, unsigned long delay)
 {
        client_get(r->client);
-       if (!schedule_delayed_work(&r->work, delay))
+       if (!queue_delayed_work(fw_wq, &r->work, delay))
                client_put(r->client);
 }
 
index 9a262439e3a7220aa8bd4712acb73dc99b63fb62..ef900d923f1571d1e6d08ec09c6c0519767bd233 100644 (file)
@@ -725,6 +725,14 @@ struct fw_device *fw_device_get_by_devt(dev_t devt)
        return device;
 }
 
+struct workqueue_struct *fw_wq;
+
+static void fw_schedule_device_work(struct fw_device *device,
+                                   unsigned long delay)
+{
+       queue_delayed_work(fw_wq, &device->work, delay);
+}
+
 /*
  * These defines control the retry behavior for reading the config
  * rom.  It shouldn't be necessary to tweak these; if the device
@@ -750,7 +758,7 @@ static void fw_device_shutdown(struct work_struct *work)
        if (time_before64(get_jiffies_64(),
                          device->card->reset_jiffies + SHUTDOWN_DELAY)
            && !list_empty(&device->card->link)) {
-               schedule_delayed_work(&device->work, SHUTDOWN_DELAY);
+               fw_schedule_device_work(device, SHUTDOWN_DELAY);
                return;
        }
 
@@ -862,7 +870,7 @@ static int lookup_existing_device(struct device *dev, void *data)
                fw_notify("rediscovered device %s\n", dev_name(dev));
 
                PREPARE_DELAYED_WORK(&old->work, fw_device_update);
-               schedule_delayed_work(&old->work, 0);
+               fw_schedule_device_work(old, 0);
 
                if (current_node == card->root_node)
                        fw_schedule_bm_work(card, 0);
@@ -953,7 +961,7 @@ static void fw_device_init(struct work_struct *work)
                if (device->config_rom_retries < MAX_RETRIES &&
                    atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
                        device->config_rom_retries++;
-                       schedule_delayed_work(&device->work, RETRY_DELAY);
+                       fw_schedule_device_work(device, RETRY_DELAY);
                } else {
                        if (device->node->link_on)
                                fw_notify("giving up on config rom for node id %x\n",
@@ -1019,7 +1027,7 @@ static void fw_device_init(struct work_struct *work)
                           FW_DEVICE_INITIALIZING,
                           FW_DEVICE_RUNNING) == FW_DEVICE_GONE) {
                PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown);
-               schedule_delayed_work(&device->work, SHUTDOWN_DELAY);
+               fw_schedule_device_work(device, SHUTDOWN_DELAY);
        } else {
                if (device->config_rom_retries)
                        fw_notify("created device %s: GUID %08x%08x, S%d00, "
@@ -1098,7 +1106,7 @@ static void fw_device_refresh(struct work_struct *work)
                if (device->config_rom_retries < MAX_RETRIES / 2 &&
                    atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
                        device->config_rom_retries++;
-                       schedule_delayed_work(&device->work, RETRY_DELAY / 2);
+                       fw_schedule_device_work(device, RETRY_DELAY / 2);
 
                        return;
                }
@@ -1131,7 +1139,7 @@ static void fw_device_refresh(struct work_struct *work)
                if (device->config_rom_retries < MAX_RETRIES &&
                    atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
                        device->config_rom_retries++;
-                       schedule_delayed_work(&device->work, RETRY_DELAY);
+                       fw_schedule_device_work(device, RETRY_DELAY);
 
                        return;
                }
@@ -1158,7 +1166,7 @@ static void fw_device_refresh(struct work_struct *work)
  gone:
        atomic_set(&device->state, FW_DEVICE_GONE);
        PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown);
-       schedule_delayed_work(&device->work, SHUTDOWN_DELAY);
+       fw_schedule_device_work(device, SHUTDOWN_DELAY);
  out:
        if (node_id == card->root_node->node_id)
                fw_schedule_bm_work(card, 0);
@@ -1214,7 +1222,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
                 * first config rom scan half a second after bus reset.
                 */
                INIT_DELAYED_WORK(&device->work, fw_device_init);
-               schedule_delayed_work(&device->work, INITIAL_DELAY);
+               fw_schedule_device_work(device, INITIAL_DELAY);
                break;
 
        case FW_NODE_INITIATED_RESET:
@@ -1230,7 +1238,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
                            FW_DEVICE_RUNNING,
                            FW_DEVICE_INITIALIZING) == FW_DEVICE_RUNNING) {
                        PREPARE_DELAYED_WORK(&device->work, fw_device_refresh);
-                       schedule_delayed_work(&device->work,
+                       fw_schedule_device_work(device,
                                device->is_local ? 0 : INITIAL_DELAY);
                }
                break;
@@ -1245,7 +1253,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
                device->generation = card->generation;
                if (atomic_read(&device->state) == FW_DEVICE_RUNNING) {
                        PREPARE_DELAYED_WORK(&device->work, fw_device_update);
-                       schedule_delayed_work(&device->work, 0);
+                       fw_schedule_device_work(device, 0);
                }
                break;
 
@@ -1270,7 +1278,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
                if (atomic_xchg(&device->state,
                                FW_DEVICE_GONE) == FW_DEVICE_RUNNING) {
                        PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown);
-                       schedule_delayed_work(&device->work,
+                       fw_schedule_device_work(device,
                                list_empty(&card->link) ? 0 : SHUTDOWN_DELAY);
                }
                break;
index 77275fdf6c1fb83fb6ce05fc69ca22c61b338e34..d4c28a217b2ca0369609bddea8fba3f471139022 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/string.h>
 #include <linux/timer.h>
 #include <linux/types.h>
+#include <linux/workqueue.h>
 
 #include <asm/byteorder.h>
 
@@ -1213,13 +1214,21 @@ static int __init fw_core_init(void)
 {
        int ret;
 
+       fw_wq = alloc_workqueue(KBUILD_MODNAME,
+                               WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
+       if (!fw_wq)
+               return -ENOMEM;
+
        ret = bus_register(&fw_bus_type);
-       if (ret < 0)
+       if (ret < 0) {
+               destroy_workqueue(fw_wq);
                return ret;
+       }
 
        fw_cdev_major = register_chrdev(0, "firewire", &fw_device_ops);
        if (fw_cdev_major < 0) {
                bus_unregister(&fw_bus_type);
+               destroy_workqueue(fw_wq);
                return fw_cdev_major;
        }
 
@@ -1235,6 +1244,7 @@ static void __exit fw_core_cleanup(void)
 {
        unregister_chrdev(fw_cdev_major, "firewire");
        bus_unregister(&fw_bus_type);
+       destroy_workqueue(fw_wq);
        idr_destroy(&fw_device_idr);
 }
 
index 0fe4e4e6eda72a696596c7dbcce1a7f0f6538569..00ea7730c6a73ea1ba3259c6cff3bf9f89ee6e47 100644 (file)
@@ -140,6 +140,8 @@ void fw_cdev_handle_phy_packet(struct fw_card *card, struct fw_packet *p);
 extern struct rw_semaphore fw_device_rwsem;
 extern struct idr fw_device_idr;
 extern int fw_cdev_major;
+struct workqueue_struct;
+extern struct workqueue_struct *fw_wq;
 
 struct fw_device *fw_device_get_by_devt(dev_t devt);
 int fw_device_set_broadcast_channel(struct device *dev, void *gen);