diff options
| author | B.J. Buchalter <bj@mhlabs.com> | 2011-05-02 13:33:42 -0400 |
|---|---|---|
| committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2011-05-02 16:55:22 -0400 |
| commit | 2e053a27d9d5ad5e0831e002cbf8043836fb2060 (patch) | |
| tree | 4e3ed1110128cbb1ba3b5baf4e45161300ad58c2 | |
| parent | 115881d395959b75c8c3bb94913f2ce869b8aa7a (diff) | |
firewire: Fix for broken configrom updates in quick succession
Current implementation of ohci_set_config_rom() uses a deferred
bus reset via fw_schedule_bus_reset(). If clients add multiple
unit descriptors to the config_rom in quick succession, the
deferred bus reset may not have fired before succeeding update
requests have come in. This can lead to an incorrect partial
update of the config_rom for both addition and removal of
config_rom descriptors, as the ohci_set_config_rom() routine
will return -EBUSY if a previous pending update has not been
completed yet; the requested update just gets dropped on the floor.
This patch recognizes that the "in-flight" update can be modified
until it has been processed by the bus-reset, and the locking
in the bus_reset_tasklet ensures that the update is done atomically
with respect to modifications made by ohci_set_config_rom(). The
-EBUSY error case is simply removed.
[Stefan R: The bug always existed at least theoretically. But it
became easy to trigger since 2.6.36 commit 02d37bed188c "firewire: core:
integrate software-forced bus resets with bus management" which
introduced long mandatory delays between janitorial bus resets.]
Signed-off-by: Benjamin Buchalter <bj@mhlabs.com>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de> (trivial style changes)
Cc: <stable@kernel.org> # 2.6.36.y and newer
| -rw-r--r-- | drivers/firewire/ohci.c | 39 |
1 files changed, 25 insertions, 14 deletions
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index f903d7b6f34a..23d1468ad253 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c | |||
| @@ -2199,7 +2199,6 @@ static int ohci_set_config_rom(struct fw_card *card, | |||
| 2199 | { | 2199 | { |
| 2200 | struct fw_ohci *ohci; | 2200 | struct fw_ohci *ohci; |
| 2201 | unsigned long flags; | 2201 | unsigned long flags; |
| 2202 | int ret = -EBUSY; | ||
| 2203 | __be32 *next_config_rom; | 2202 | __be32 *next_config_rom; |
| 2204 | dma_addr_t uninitialized_var(next_config_rom_bus); | 2203 | dma_addr_t uninitialized_var(next_config_rom_bus); |
| 2205 | 2204 | ||
| @@ -2240,22 +2239,37 @@ static int ohci_set_config_rom(struct fw_card *card, | |||
| 2240 | 2239 | ||
| 2241 | spin_lock_irqsave(&ohci->lock, flags); | 2240 | spin_lock_irqsave(&ohci->lock, flags); |
| 2242 | 2241 | ||
| 2242 | /* | ||
| 2243 | * If there is not an already pending config_rom update, | ||
| 2244 | * push our new allocation into the ohci->next_config_rom | ||
| 2245 | * and then mark the local variable as null so that we | ||
| 2246 | * won't deallocate the new buffer. | ||
| 2247 | * | ||
| 2248 | * OTOH, if there is a pending config_rom update, just | ||
| 2249 | * use that buffer with the new config_rom data, and | ||
| 2250 | * let this routine free the unused DMA allocation. | ||
| 2251 | */ | ||
| 2252 | |||
| 2243 | if (ohci->next_config_rom == NULL) { | 2253 | if (ohci->next_config_rom == NULL) { |
| 2244 | ohci->next_config_rom = next_config_rom; | 2254 | ohci->next_config_rom = next_config_rom; |
| 2245 | ohci->next_config_rom_bus = next_config_rom_bus; | 2255 | ohci->next_config_rom_bus = next_config_rom_bus; |
| 2256 | next_config_rom = NULL; | ||
| 2257 | } | ||
| 2246 | 2258 | ||
| 2247 | copy_config_rom(ohci->next_config_rom, config_rom, length); | 2259 | copy_config_rom(ohci->next_config_rom, config_rom, length); |
| 2248 | 2260 | ||
| 2249 | ohci->next_header = config_rom[0]; | 2261 | ohci->next_header = config_rom[0]; |
| 2250 | ohci->next_config_rom[0] = 0; | 2262 | ohci->next_config_rom[0] = 0; |
| 2251 | 2263 | ||
| 2252 | reg_write(ohci, OHCI1394_ConfigROMmap, | 2264 | reg_write(ohci, OHCI1394_ConfigROMmap, ohci->next_config_rom_bus); |
| 2253 | ohci->next_config_rom_bus); | ||
| 2254 | ret = 0; | ||
| 2255 | } | ||
| 2256 | 2265 | ||
| 2257 | spin_unlock_irqrestore(&ohci->lock, flags); | 2266 | spin_unlock_irqrestore(&ohci->lock, flags); |
| 2258 | 2267 | ||
| 2268 | /* If we didn't use the DMA allocation, delete it. */ | ||
| 2269 | if (next_config_rom != NULL) | ||
| 2270 | dma_free_coherent(ohci->card.device, CONFIG_ROM_SIZE, | ||
| 2271 | next_config_rom, next_config_rom_bus); | ||
| 2272 | |||
| 2259 | /* | 2273 | /* |
| 2260 | * Now initiate a bus reset to have the changes take | 2274 | * Now initiate a bus reset to have the changes take |
| 2261 | * effect. We clean up the old config rom memory and DMA | 2275 | * effect. We clean up the old config rom memory and DMA |
| @@ -2263,13 +2277,10 @@ static int ohci_set_config_rom(struct fw_card *card, | |||
| 2263 | * controller could need to access it before the bus reset | 2277 | * controller could need to access it before the bus reset |
| 2264 | * takes effect. | 2278 | * takes effect. |
| 2265 | */ | 2279 | */ |
| 2266 | if (ret == 0) | ||
| 2267 | fw_schedule_bus_reset(&ohci->card, true, true); | ||
| 2268 | else | ||
| 2269 | dma_free_coherent(ohci->card.device, CONFIG_ROM_SIZE, | ||
| 2270 | next_config_rom, next_config_rom_bus); | ||
| 2271 | 2280 | ||
| 2272 | return ret; | 2281 | fw_schedule_bus_reset(&ohci->card, true, true); |
| 2282 | |||
| 2283 | return 0; | ||
| 2273 | } | 2284 | } |
| 2274 | 2285 | ||
| 2275 | static void ohci_send_request(struct fw_card *card, struct fw_packet *packet) | 2286 | static void ohci_send_request(struct fw_card *card, struct fw_packet *packet) |
