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 19:03:21 -0400 |
commit | eecd11ed47c8c9bd8e7e3dff35baccae18dd0d84 (patch) | |
tree | 7d91cf5b80a59ff84cdd2bf5f4ae96e82e026276 /drivers/usb/storage/usb.c | |
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/storage/usb.c')
-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 1185acac4b21..04c3bec81201 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) | |||
373 | /* lock access to the state */ | 373 | /* lock access to the state */ |
374 | scsi_lock(host); | 374 | scsi_lock(host); |
375 | 375 | ||
376 | /* did the command already complete because of a disconnect? */ | ||
377 | if (!us->srb) | ||
378 | ; /* nothing to do */ | ||
379 | |||
376 | /* indicate that the command is done */ | 380 | /* indicate that the command is done */ |
377 | if (us->srb->result != DID_ABORT << 16) { | 381 | else if (us->srb->result != DID_ABORT << 16) { |
378 | US_DEBUGP("scsi cmd done, result=0x%x\n", | 382 | US_DEBUGP("scsi cmd done, result=0x%x\n", |
379 | us->srb->result); | 383 | us->srb->result); |
380 | us->srb->scsi_done(us->srb); | 384 | us->srb->scsi_done(us->srb); |
@@ -836,32 +840,34 @@ static void dissociate_dev(struct us_data *us) | |||
836 | * the host */ | 840 | * the host */ |
837 | static void quiesce_and_remove_host(struct us_data *us) | 841 | static void quiesce_and_remove_host(struct us_data *us) |
838 | { | 842 | { |
843 | struct Scsi_Host *host = us_to_host(us); | ||
844 | |||
839 | /* Prevent new USB transfers, stop the current command, and | 845 | /* Prevent new USB transfers, stop the current command, and |
840 | * interrupt a SCSI-scan or device-reset delay */ | 846 | * interrupt a SCSI-scan or device-reset delay */ |
847 | scsi_lock(host); | ||
841 | set_bit(US_FLIDX_DISCONNECTING, &us->flags); | 848 | set_bit(US_FLIDX_DISCONNECTING, &us->flags); |
849 | scsi_unlock(host); | ||
842 | usb_stor_stop_transport(us); | 850 | usb_stor_stop_transport(us); |
843 | wake_up(&us->delay_wait); | 851 | wake_up(&us->delay_wait); |
844 | 852 | ||
845 | /* It doesn't matter if the SCSI-scanning thread is still running. | 853 | /* It doesn't matter if the SCSI-scanning thread is still running. |
846 | * The thread will exit when it sees the DISCONNECTING flag. */ | 854 | * The thread will exit when it sees the DISCONNECTING flag. */ |
847 | 855 | ||
848 | /* Wait for the current command to finish, then remove the host */ | ||
849 | mutex_lock(&us->dev_mutex); | ||
850 | mutex_unlock(&us->dev_mutex); | ||
851 | |||
852 | /* queuecommand won't accept any new commands and the control | 856 | /* queuecommand won't accept any new commands and the control |
853 | * thread won't execute a previously-queued command. If there | 857 | * thread won't execute a previously-queued command. If there |
854 | * is such a command pending, complete it with an error. */ | 858 | * is such a command pending, complete it with an error. */ |
859 | mutex_lock(&us->dev_mutex); | ||
855 | if (us->srb) { | 860 | if (us->srb) { |
856 | us->srb->result = DID_NO_CONNECT << 16; | 861 | us->srb->result = DID_NO_CONNECT << 16; |
857 | scsi_lock(us_to_host(us)); | 862 | scsi_lock(host); |
858 | us->srb->scsi_done(us->srb); | 863 | us->srb->scsi_done(us->srb); |
859 | us->srb = NULL; | 864 | us->srb = NULL; |
860 | scsi_unlock(us_to_host(us)); | 865 | scsi_unlock(host); |
861 | } | 866 | } |
867 | mutex_unlock(&us->dev_mutex); | ||
862 | 868 | ||
863 | /* Now we own no commands so it's safe to remove the SCSI host */ | 869 | /* Now we own no commands so it's safe to remove the SCSI host */ |
864 | scsi_remove_host(us_to_host(us)); | 870 | scsi_remove_host(host); |
865 | } | 871 | } |
866 | 872 | ||
867 | /* Second stage of disconnect processing: deallocate all resources */ | 873 | /* Second stage of disconnect processing: deallocate all resources */ |