[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:
parent
3dd2ae81f7
commit
eecd11ed47
|
@ -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 */
|
||||||
|
|
Loading…
Reference in New Issue