diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2006-06-19 14:50:15 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2006-07-12 16:03:21 -0700 |
commit | eecd11ed47c8c9bd8e7e3dff35baccae18dd0d84 (patch) | |
tree | 7d91cf5b80a59ff84cdd2bf5f4ae96e82e026276 /drivers/usb | |
parent | 3dd2ae81f70f191f5b6751d18fdfe61dbafda7e8 (diff) |
[PATCH] usb-storage: fix race between reset and disconnect
My recent patch converting usb-storage to use
usb_reset_composite_device() added a bug, a race between reset and
disconnect. It was necessary to drop the private lock while executing a
reset, and if a disconnect occurs at that time it will cause a crash.
This patch (as722) fixes the problem by explicitly checking for an early
termination after executing each command.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/storage/usb.c | 22 |
1 files changed, 14 insertions, 8 deletions
diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 1185acac4b2..04c3bec8120 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -373,8 +373,12 @@ static int usb_stor_control_thread(void * __us) /* lock access to the state */ scsi_lock(host); + /* did the command already complete because of a disconnect? */ + if (!us->srb) + ; /* nothing to do */ + /* indicate that the command is done */ - if (us->srb->result != DID_ABORT << 16) { + else if (us->srb->result != DID_ABORT << 16) { US_DEBUGP("scsi cmd done, result=0x%x\n", us->srb->result); us->srb->scsi_done(us->srb); @@ -836,32 +840,34 @@ static void dissociate_dev(struct us_data *us) * the host */ static void quiesce_and_remove_host(struct us_data *us) { + struct Scsi_Host *host = us_to_host(us); + /* Prevent new USB transfers, stop the current command, and * interrupt a SCSI-scan or device-reset delay */ + scsi_lock(host); set_bit(US_FLIDX_DISCONNECTING, &us->flags); + scsi_unlock(host); usb_stor_stop_transport(us); wake_up(&us->delay_wait); /* It doesn't matter if the SCSI-scanning thread is still running. * The thread will exit when it sees the DISCONNECTING flag. */ - /* Wait for the current command to finish, then remove the host */ - mutex_lock(&us->dev_mutex); - mutex_unlock(&us->dev_mutex); - /* queuecommand won't accept any new commands and the control * thread won't execute a previously-queued command. If there * is such a command pending, complete it with an error. */ + mutex_lock(&us->dev_mutex); if (us->srb) { us->srb->result = DID_NO_CONNECT << 16; - scsi_lock(us_to_host(us)); + scsi_lock(host); us->srb->scsi_done(us->srb); us->srb = NULL; - scsi_unlock(us_to_host(us)); + scsi_unlock(host); } + mutex_unlock(&us->dev_mutex); /* Now we own no commands so it's safe to remove the SCSI host */ - scsi_remove_host(us_to_host(us)); + scsi_remove_host(host); } /* Second stage of disconnect processing: deallocate all resources */ |