diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2008-01-25 18:57:41 +0100 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2008-01-30 22:22:27 +0100 |
commit | b5d2a5e04e6a26cb3f77af8cbc31e74c361d706c (patch) | |
tree | cfc3b2dc141f643bcf6ef065cca802340b335799 | |
parent | cf5a56ac8083dd04ffe8b9b2ec7895e9bcff44bc (diff) |
firewire: enforce access order between generation and node ID, fix "giving up on config rom"
fw_device.node_id and fw_device.generation are accessed without mutexes.
We have to ensure that all readers will get to see node_id updates
before generation updates.
Fixes an inability to recognize devices after "giving up on config rom",
https://bugzilla.redhat.com/show_bug.cgi?id=429950
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Reviewed by Nick Piggin <nickpiggin@yahoo.com.au>.
Verified to fix 'giving up on config rom' issues on multiple system and
drive combinations that were previously affected.
Signed-off-by: Jarod Wilson <jwilson@redhat.com>
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
-rw-r--r-- | drivers/firewire/fw-cdev.c | 1 | ||||
-rw-r--r-- | drivers/firewire/fw-device.c | 15 | ||||
-rw-r--r-- | drivers/firewire/fw-device.h | 12 | ||||
-rw-r--r-- | drivers/firewire/fw-sbp2.c | 3 | ||||
-rw-r--r-- | drivers/firewire/fw-topology.c | 6 |
5 files changed, 35 insertions, 2 deletions
diff --git a/drivers/firewire/fw-cdev.c b/drivers/firewire/fw-cdev.c index cea8a799799..7e73cbaa412 100644 --- a/drivers/firewire/fw-cdev.c +++ b/drivers/firewire/fw-cdev.c @@ -207,6 +207,7 @@ fill_bus_reset_event(struct fw_cdev_event_bus_reset *event, event->closure = client->bus_reset_closure; event->type = FW_CDEV_EVENT_BUS_RESET; event->generation = client->device->generation; + smp_rmb(); /* node_id must not be older than generation */ event->node_id = client->device->node_id; event->local_node_id = card->local_node->node_id; event->bm_node_id = 0; /* FIXME: We don't track the BM. */ diff --git a/drivers/firewire/fw-device.c b/drivers/firewire/fw-device.c index 56681b3b297..872df223860 100644 --- a/drivers/firewire/fw-device.c +++ b/drivers/firewire/fw-device.c @@ -27,6 +27,7 @@ #include <linux/idr.h> #include <linux/rwsem.h> #include <asm/semaphore.h> +#include <asm/system.h> #include <linux/ctype.h> #include "fw-transaction.h" #include "fw-topology.h" @@ -182,9 +183,14 @@ static void fw_device_release(struct device *dev) int fw_device_enable_phys_dma(struct fw_device *device) { + int generation = device->generation; + + /* device->node_id, accessed below, must not be older than generation */ + smp_rmb(); + return device->card->driver->enable_phys_dma(device->card, device->node_id, - device->generation); + generation); } EXPORT_SYMBOL(fw_device_enable_phys_dma); @@ -389,12 +395,16 @@ static int read_rom(struct fw_device *device, int index, u32 * data) struct read_quadlet_callback_data callback_data; struct fw_transaction t; u64 offset; + int generation = device->generation; + + /* device->node_id, accessed below, must not be older than generation */ + smp_rmb(); init_completion(&callback_data.done); offset = 0xfffff0000400ULL + index * 4; fw_send_request(device->card, &t, TCODE_READ_QUADLET_REQUEST, - device->node_id, device->generation, device->max_speed, + device->node_id, generation, device->max_speed, offset, NULL, 4, complete_transaction, &callback_data); wait_for_completion(&callback_data.done); @@ -801,6 +811,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event) device = node->data; device->node_id = node->node_id; + smp_wmb(); /* update node_id before generation */ device->generation = card->generation; if (atomic_read(&device->state) == FW_DEVICE_RUNNING) { PREPARE_DELAYED_WORK(&device->work, fw_device_update); diff --git a/drivers/firewire/fw-device.h b/drivers/firewire/fw-device.h index 894d4a92a18..0854fe2bc11 100644 --- a/drivers/firewire/fw-device.h +++ b/drivers/firewire/fw-device.h @@ -35,6 +35,18 @@ struct fw_attribute_group { struct attribute *attrs[11]; }; +/* + * Note, fw_device.generation always has to be read before fw_device.node_id. + * Use SMP memory barriers to ensure this. Otherwise requests will be sent + * to an outdated node_id if the generation was updated in the meantime due + * to a bus reset. + * + * Likewise, fw-core will take care to update .node_id before .generation so + * that whenever fw_device.generation is current WRT the actual bus generation, + * fw_device.node_id is guaranteed to be current too. + * + * The same applies to fw_device.card->node_id vs. fw_device.generation. + */ struct fw_device { atomic_t state; struct fw_node *node; diff --git a/drivers/firewire/fw-sbp2.c b/drivers/firewire/fw-sbp2.c index d406c34fd37..705a20ce6b4 100644 --- a/drivers/firewire/fw-sbp2.c +++ b/drivers/firewire/fw-sbp2.c @@ -40,6 +40,7 @@ #include <linux/stringify.h> #include <linux/timer.h> #include <linux/workqueue.h> +#include <asm/system.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> @@ -662,6 +663,7 @@ static void sbp2_login(struct work_struct *work) int generation, node_id, local_node_id; generation = device->generation; + smp_rmb(); /* node_id must not be older than generation */ node_id = device->node_id; local_node_id = device->card->node_id; @@ -912,6 +914,7 @@ static void sbp2_reconnect(struct work_struct *work) int generation, node_id, local_node_id; generation = device->generation; + smp_rmb(); /* node_id must not be older than generation */ node_id = device->node_id; local_node_id = device->card->node_id; diff --git a/drivers/firewire/fw-topology.c b/drivers/firewire/fw-topology.c index 0fc9b000e99..172c1867e9a 100644 --- a/drivers/firewire/fw-topology.c +++ b/drivers/firewire/fw-topology.c @@ -21,6 +21,7 @@ #include <linux/module.h> #include <linux/wait.h> #include <linux/errno.h> +#include <asm/system.h> #include "fw-transaction.h" #include "fw-topology.h" @@ -518,6 +519,11 @@ fw_core_handle_bus_reset(struct fw_card *card, card->bm_retries = 0; card->node_id = node_id; + /* + * Update node_id before generation to prevent anybody from using + * a stale node_id together with a current generation. + */ + smp_wmb(); card->generation = generation; card->reset_jiffies = jiffies; schedule_delayed_work(&card->work, 0); |