Skip to content

Commit

Permalink
firewire: Fix for broken configrom updates in quick succession
Browse files Browse the repository at this point in the history
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 02d37be "firewire: core:
integrate software-forced bus resets with bus management" which
introduced long mandatory delays between janitorial bus resets.]

Signed-off-by: Benjamin Buchalter <[email protected]>
Signed-off-by: Stefan Richter <[email protected]> (trivial style changes)
Cc: <[email protected]> # 2.6.36.y and newer
  • Loading branch information
mhbj authored and Stefan Richter committed May 2, 2011
1 parent 115881d commit 2e053a2
Showing 1 changed file with 25 additions and 14 deletions.
39 changes: 25 additions & 14 deletions drivers/firewire/ohci.c
Original file line number Diff line number Diff line change
Expand Up @@ -2199,7 +2199,6 @@ static int ohci_set_config_rom(struct fw_card *card,
{
struct fw_ohci *ohci;
unsigned long flags;
int ret = -EBUSY;
__be32 *next_config_rom;
dma_addr_t uninitialized_var(next_config_rom_bus);

Expand Down Expand Up @@ -2240,36 +2239,48 @@ static int ohci_set_config_rom(struct fw_card *card,

spin_lock_irqsave(&ohci->lock, flags);

/*
* If there is not an already pending config_rom update,
* push our new allocation into the ohci->next_config_rom
* and then mark the local variable as null so that we
* won't deallocate the new buffer.
*
* OTOH, if there is a pending config_rom update, just
* use that buffer with the new config_rom data, and
* let this routine free the unused DMA allocation.
*/

if (ohci->next_config_rom == NULL) {
ohci->next_config_rom = next_config_rom;
ohci->next_config_rom_bus = next_config_rom_bus;
next_config_rom = NULL;
}

copy_config_rom(ohci->next_config_rom, config_rom, length);
copy_config_rom(ohci->next_config_rom, config_rom, length);

ohci->next_header = config_rom[0];
ohci->next_config_rom[0] = 0;
ohci->next_header = config_rom[0];
ohci->next_config_rom[0] = 0;

reg_write(ohci, OHCI1394_ConfigROMmap,
ohci->next_config_rom_bus);
ret = 0;
}
reg_write(ohci, OHCI1394_ConfigROMmap, ohci->next_config_rom_bus);

spin_unlock_irqrestore(&ohci->lock, flags);

/* If we didn't use the DMA allocation, delete it. */
if (next_config_rom != NULL)
dma_free_coherent(ohci->card.device, CONFIG_ROM_SIZE,
next_config_rom, next_config_rom_bus);

/*
* Now initiate a bus reset to have the changes take
* effect. We clean up the old config rom memory and DMA
* mappings in the bus reset tasklet, since the OHCI
* controller could need to access it before the bus reset
* takes effect.
*/
if (ret == 0)
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);

return ret;
fw_schedule_bus_reset(&ohci->card, true, true);

return 0;
}

static void ohci_send_request(struct fw_card *card, struct fw_packet *packet)
Expand Down

0 comments on commit 2e053a2

Please sign in to comment.