firewire: core: use non-reentrant workqueue with rescuer

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>
This commit is contained in:
Stefan Richter 2010-10-13 13:39:46 +02:00
Родитель 13882a82ee
Коммит 6ea9e7bbfc
5 изменённых файлов: 36 добавлений и 16 удалений

Просмотреть файл

@ -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. */ /* Use an arbitrary short delay to combine multiple reset requests. */
fw_card_get(card); fw_card_get(card);
if (!schedule_delayed_work(&card->br_work, if (!queue_delayed_work(fw_wq, &card->br_work,
delayed ? DIV_ROUND_UP(HZ, 100) : 0)) delayed ? DIV_ROUND_UP(HZ, 100) : 0))
fw_card_put(card); fw_card_put(card);
} }
EXPORT_SYMBOL(fw_schedule_bus_reset); 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. */ /* Delay for 2s after last reset per IEEE 1394 clause 8.2.1. */
if (card->reset_jiffies != 0 && if (card->reset_jiffies != 0 &&
time_before64(get_jiffies_64(), card->reset_jiffies + 2 * HZ)) { 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); fw_card_put(card);
return; return;
} }

Просмотреть файл

@ -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) static void schedule_iso_resource(struct iso_resource *r, unsigned long delay)
{ {
client_get(r->client); client_get(r->client);
if (!schedule_delayed_work(&r->work, delay)) if (!queue_delayed_work(fw_wq, &r->work, delay))
client_put(r->client); client_put(r->client);
} }

Просмотреть файл

@ -725,6 +725,14 @@ struct fw_device *fw_device_get_by_devt(dev_t devt)
return device; 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 * These defines control the retry behavior for reading the config
* rom. It shouldn't be necessary to tweak these; if the device * 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(), if (time_before64(get_jiffies_64(),
device->card->reset_jiffies + SHUTDOWN_DELAY) device->card->reset_jiffies + SHUTDOWN_DELAY)
&& !list_empty(&device->card->link)) { && !list_empty(&device->card->link)) {
schedule_delayed_work(&device->work, SHUTDOWN_DELAY); fw_schedule_device_work(device, SHUTDOWN_DELAY);
return; return;
} }
@ -862,7 +870,7 @@ static int lookup_existing_device(struct device *dev, void *data)
fw_notify("rediscovered device %s\n", dev_name(dev)); fw_notify("rediscovered device %s\n", dev_name(dev));
PREPARE_DELAYED_WORK(&old->work, fw_device_update); 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) if (current_node == card->root_node)
fw_schedule_bm_work(card, 0); 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 && if (device->config_rom_retries < MAX_RETRIES &&
atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
device->config_rom_retries++; device->config_rom_retries++;
schedule_delayed_work(&device->work, RETRY_DELAY); fw_schedule_device_work(device, RETRY_DELAY);
} else { } else {
if (device->node->link_on) if (device->node->link_on)
fw_notify("giving up on config rom for node id %x\n", 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_INITIALIZING,
FW_DEVICE_RUNNING) == FW_DEVICE_GONE) { FW_DEVICE_RUNNING) == FW_DEVICE_GONE) {
PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown);
schedule_delayed_work(&device->work, SHUTDOWN_DELAY); fw_schedule_device_work(device, SHUTDOWN_DELAY);
} else { } else {
if (device->config_rom_retries) if (device->config_rom_retries)
fw_notify("created device %s: GUID %08x%08x, S%d00, " 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 && if (device->config_rom_retries < MAX_RETRIES / 2 &&
atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
device->config_rom_retries++; device->config_rom_retries++;
schedule_delayed_work(&device->work, RETRY_DELAY / 2); fw_schedule_device_work(device, RETRY_DELAY / 2);
return; return;
} }
@ -1131,7 +1139,7 @@ static void fw_device_refresh(struct work_struct *work)
if (device->config_rom_retries < MAX_RETRIES && if (device->config_rom_retries < MAX_RETRIES &&
atomic_read(&device->state) == FW_DEVICE_INITIALIZING) { atomic_read(&device->state) == FW_DEVICE_INITIALIZING) {
device->config_rom_retries++; device->config_rom_retries++;
schedule_delayed_work(&device->work, RETRY_DELAY); fw_schedule_device_work(device, RETRY_DELAY);
return; return;
} }
@ -1158,7 +1166,7 @@ static void fw_device_refresh(struct work_struct *work)
gone: gone:
atomic_set(&device->state, FW_DEVICE_GONE); atomic_set(&device->state, FW_DEVICE_GONE);
PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown);
schedule_delayed_work(&device->work, SHUTDOWN_DELAY); fw_schedule_device_work(device, SHUTDOWN_DELAY);
out: out:
if (node_id == card->root_node->node_id) if (node_id == card->root_node->node_id)
fw_schedule_bm_work(card, 0); 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. * first config rom scan half a second after bus reset.
*/ */
INIT_DELAYED_WORK(&device->work, fw_device_init); INIT_DELAYED_WORK(&device->work, fw_device_init);
schedule_delayed_work(&device->work, INITIAL_DELAY); fw_schedule_device_work(device, INITIAL_DELAY);
break; break;
case FW_NODE_INITIATED_RESET: 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_RUNNING,
FW_DEVICE_INITIALIZING) == FW_DEVICE_RUNNING) { FW_DEVICE_INITIALIZING) == FW_DEVICE_RUNNING) {
PREPARE_DELAYED_WORK(&device->work, fw_device_refresh); PREPARE_DELAYED_WORK(&device->work, fw_device_refresh);
schedule_delayed_work(&device->work, fw_schedule_device_work(device,
device->is_local ? 0 : INITIAL_DELAY); device->is_local ? 0 : INITIAL_DELAY);
} }
break; break;
@ -1245,7 +1253,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
device->generation = card->generation; device->generation = card->generation;
if (atomic_read(&device->state) == FW_DEVICE_RUNNING) { if (atomic_read(&device->state) == FW_DEVICE_RUNNING) {
PREPARE_DELAYED_WORK(&device->work, fw_device_update); PREPARE_DELAYED_WORK(&device->work, fw_device_update);
schedule_delayed_work(&device->work, 0); fw_schedule_device_work(device, 0);
} }
break; break;
@ -1270,7 +1278,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event)
if (atomic_xchg(&device->state, if (atomic_xchg(&device->state,
FW_DEVICE_GONE) == FW_DEVICE_RUNNING) { FW_DEVICE_GONE) == FW_DEVICE_RUNNING) {
PREPARE_DELAYED_WORK(&device->work, fw_device_shutdown); 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); list_empty(&card->link) ? 0 : SHUTDOWN_DELAY);
} }
break; break;

Просмотреть файл

@ -36,6 +36,7 @@
#include <linux/string.h> #include <linux/string.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/workqueue.h>
#include <asm/byteorder.h> #include <asm/byteorder.h>
@ -1213,13 +1214,21 @@ static int __init fw_core_init(void)
{ {
int ret; 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); ret = bus_register(&fw_bus_type);
if (ret < 0) if (ret < 0) {
destroy_workqueue(fw_wq);
return ret; return ret;
}
fw_cdev_major = register_chrdev(0, "firewire", &fw_device_ops); fw_cdev_major = register_chrdev(0, "firewire", &fw_device_ops);
if (fw_cdev_major < 0) { if (fw_cdev_major < 0) {
bus_unregister(&fw_bus_type); bus_unregister(&fw_bus_type);
destroy_workqueue(fw_wq);
return fw_cdev_major; return fw_cdev_major;
} }
@ -1235,6 +1244,7 @@ static void __exit fw_core_cleanup(void)
{ {
unregister_chrdev(fw_cdev_major, "firewire"); unregister_chrdev(fw_cdev_major, "firewire");
bus_unregister(&fw_bus_type); bus_unregister(&fw_bus_type);
destroy_workqueue(fw_wq);
idr_destroy(&fw_device_idr); idr_destroy(&fw_device_idr);
} }

Просмотреть файл

@ -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 rw_semaphore fw_device_rwsem;
extern struct idr fw_device_idr; extern struct idr fw_device_idr;
extern int fw_cdev_major; 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); struct fw_device *fw_device_get_by_devt(dev_t devt);
int fw_device_set_broadcast_channel(struct device *dev, void *gen); int fw_device_set_broadcast_channel(struct device *dev, void *gen);