[PATCH] USB Storage: close a race condition in disconnect near probe

This patch started life as as533, and has been re-diffed against the
current tree.

Disconnect processing in usb-storage naturally divides into two parts:
one to quiesce the driver (make sure no commands are executing or
queued) and remove the host, and the other to deallocate all the USB and
non-USB resources.  This patch creates two subroutines to handle those
two parts.  Mostly it's just code movement, but there is one significant
change.  If the scsi-scanning thread fails to initialize but the host
has successfully been added, we need to quiesce the driver before
removing the host.  After all, it's possible that scanning could have
been initiated from somewhere else, such as userspace -- very low
probability, but it's easily handled by calling the new subroutine.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Matthew Dharm <mdharm-usb@one-eyed-alien.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Matthew Dharm 2005-07-28 14:44:29 -07:00 committed by Greg Kroah-Hartman
parent 0f64e07813
commit 77f46328fb

View File

@ -786,6 +786,7 @@ static void usb_stor_release_resources(struct us_data *us)
* any more commands.
*/
US_DEBUGP("-- sending exit command to thread\n");
set_bit(US_FLIDX_DISCONNECTING, &us->flags);
up(&us->sema);
/* Call the destructor routine, if it exists */
@ -816,6 +817,36 @@ static void dissociate_dev(struct us_data *us)
usb_set_intfdata(us->pusb_intf, NULL);
}
/* First stage of disconnect processing: stop all commands and remove
* the host */
static void quiesce_and_remove_host(struct us_data *us)
{
/* Prevent new USB transfers, stop the current command, and
* interrupt a SCSI-scan or device-reset delay */
set_bit(US_FLIDX_DISCONNECTING, &us->flags);
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 */
down(&us->dev_semaphore);
up(&us->dev_semaphore);
scsi_remove_host(us_to_host(us));
}
/* Second stage of disconnect processing: deallocate all resources */
static void release_everything(struct us_data *us)
{
usb_stor_release_resources(us);
dissociate_dev(us);
/* Drop our reference to the host; the SCSI core will free it
* (and "us" along with it) when the refcount becomes 0. */
scsi_host_put(us_to_host(us));
}
/* Thread to carry out delayed SCSI-device scanning */
static int usb_stor_scan_thread(void * __us)
{
@ -956,7 +987,7 @@ static int storage_probe(struct usb_interface *intf,
if (result < 0) {
printk(KERN_WARNING USB_STORAGE
"Unable to start the device-scanning thread\n");
scsi_remove_host(host);
quiesce_and_remove_host(us);
goto BadDevice;
}
atomic_inc(&total_threads);
@ -969,10 +1000,7 @@ static int storage_probe(struct usb_interface *intf,
/* We come here if there are any problems */
BadDevice:
US_DEBUGP("storage_probe() failed\n");
set_bit(US_FLIDX_DISCONNECTING, &us->flags);
usb_stor_release_resources(us);
dissociate_dev(us);
scsi_host_put(host);
release_everything(us);
return result;
}
@ -982,28 +1010,8 @@ static void storage_disconnect(struct usb_interface *intf)
struct us_data *us = usb_get_intfdata(intf);
US_DEBUGP("storage_disconnect() called\n");
/* Prevent new USB transfers, stop the current command, and
* interrupt a SCSI-scan or device-reset delay */
set_bit(US_FLIDX_DISCONNECTING, &us->flags);
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 */
down(&us->dev_semaphore);
up(&us->dev_semaphore);
scsi_remove_host(us_to_host(us));
/* Wait for everything to become idle and release all our resources */
usb_stor_release_resources(us);
dissociate_dev(us);
/* Drop our reference to the host; the SCSI core will free it
* (and "us" along with it) when the refcount becomes 0. */
scsi_host_put(us_to_host(us));
quiesce_and_remove_host(us);
release_everything(us);
}
/***********************************************************************