[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>
This commit is contained in:
Alan Stern 2006-06-19 14:50:15 -04:00 committed by Greg Kroah-Hartman
parent 3dd2ae81f7
commit eecd11ed47
1 changed files with 14 additions and 8 deletions

View File

@ -373,8 +373,12 @@ static int usb_stor_control_thread(void * __us)
/* lock access to the state */ /* lock access to the state */
scsi_lock(host); scsi_lock(host);
/* did the command already complete because of a disconnect? */
if (!us->srb)
; /* nothing to do */
/* indicate that the command is done */ /* 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_DEBUGP("scsi cmd done, result=0x%x\n",
us->srb->result); us->srb->result);
us->srb->scsi_done(us->srb); us->srb->scsi_done(us->srb);
@ -836,32 +840,34 @@ static void dissociate_dev(struct us_data *us)
* the host */ * the host */
static void quiesce_and_remove_host(struct us_data *us) 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 /* Prevent new USB transfers, stop the current command, and
* interrupt a SCSI-scan or device-reset delay */ * interrupt a SCSI-scan or device-reset delay */
scsi_lock(host);
set_bit(US_FLIDX_DISCONNECTING, &us->flags); set_bit(US_FLIDX_DISCONNECTING, &us->flags);
scsi_unlock(host);
usb_stor_stop_transport(us); usb_stor_stop_transport(us);
wake_up(&us->delay_wait); wake_up(&us->delay_wait);
/* It doesn't matter if the SCSI-scanning thread is still running. /* It doesn't matter if the SCSI-scanning thread is still running.
* The thread will exit when it sees the DISCONNECTING flag. */ * 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 /* queuecommand won't accept any new commands and the control
* thread won't execute a previously-queued command. If there * thread won't execute a previously-queued command. If there
* is such a command pending, complete it with an error. */ * is such a command pending, complete it with an error. */
mutex_lock(&us->dev_mutex);
if (us->srb) { if (us->srb) {
us->srb->result = DID_NO_CONNECT << 16; us->srb->result = DID_NO_CONNECT << 16;
scsi_lock(us_to_host(us)); scsi_lock(host);
us->srb->scsi_done(us->srb); us->srb->scsi_done(us->srb);
us->srb = NULL; 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 */ /* 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 */ /* Second stage of disconnect processing: deallocate all resources */