#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#include "debug.h"
#include "initializers.h"
-#ifdef CONFIG_USB_STORAGE_HP8200e
+#ifdef CONFIG_USB_STORAGE_USBAT
#include "shuttle_usbat.h"
#endif
#ifdef CONFIG_USB_STORAGE_SDDR09
#endif
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-
/* Some informational data */
MODULE_AUTHOR("Matthew Dharm <mdharm-usb@one-eyed-alien.net>");
MODULE_DESCRIPTION("USB Mass Storage driver for Linux");
MODULE_PARM_DESC(delay_use, "seconds to delay before using a new device");
+/* These are used to make sure the module doesn't unload before all the
+ * threads have exited.
+ */
+static atomic_t total_threads = ATOMIC_INIT(0);
+static DECLARE_COMPLETION(threads_gone);
+
+
static int storage_probe(struct usb_interface *iface,
const struct usb_device_id *id);
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, US_SC_QIC, US_PR_BULK) },
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, US_SC_UFI, US_PR_BULK) },
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, US_SC_8070, US_PR_BULK) },
-#if !defined(CONFIG_BLK_DEV_UB) && !defined(CONFIG_BLK_DEV_UB_MODULE)
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, US_SC_SCSI, US_PR_BULK) },
-#endif
/* Terminating entry */
{ }
.useTransport = US_PR_BULK},
{ .useProtocol = US_SC_8070,
.useTransport = US_PR_BULK},
-#if !defined(CONFIG_BLK_DEV_UB) && !defined(CONFIG_BLK_DEV_UB_MODULE)
{ .useProtocol = US_SC_SCSI,
.useTransport = US_PR_BULK},
-#endif
/* Terminating entry */
{ NULL }
};
-struct usb_driver usb_storage_driver = {
+static struct usb_driver usb_storage_driver = {
.owner = THIS_MODULE,
.name = "usb-storage",
.probe = storage_probe,
static int usb_stor_control_thread(void * __us)
{
struct us_data *us = (struct us_data *)__us;
- struct Scsi_Host *host = us->host;
+ struct Scsi_Host *host = us_to_host(us);
lock_kernel();
* so get rid of all our resources.
*/
daemonize("usb-storage");
-
current->flags |= PF_NOFREEZE;
-
unlock_kernel();
+ /* acquire a reference to the host, so it won't be deallocated
+ * until we're ready to exit */
+ scsi_host_get(host);
+
/* signal that we've started the thread */
complete(&(us->notify));
/* lock the device pointers */
down(&(us->dev_semaphore));
- /* if us->srb is NULL, we are being asked to exit */
- if (us->srb == NULL) {
- US_DEBUGP("-- exit command received\n");
+ /* if the device has disconnected, we are free to exit */
+ if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
+ US_DEBUGP("-- exiting\n");
up(&(us->dev_semaphore));
break;
}
goto SkipForAbort;
}
- /* don't do anything if we are disconnecting */
- if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
- US_DEBUGP("No command during disconnect\n");
- goto SkipForDisconnect;
- }
-
scsi_unlock(host);
/* reject the command if the direction indicator
complete(&(us->notify));
/* finished working on this command */
-SkipForDisconnect:
us->srb = NULL;
scsi_unlock(host);
up(&(us->dev_semaphore));
} /* for (;;) */
+ scsi_host_put(host);
+
/* notify the exit routine that we're actually exiting now
*
* complete()/wait_for_completion() is similar to up()/down(),
* This is important in preemption kernels, which transfer the flow
* of execution immediately upon a complete().
*/
- complete_and_exit(&(us->notify), 0);
+ complete_and_exit(&threads_gone, 0);
}
/***********************************************************************
unusual_dev->useTransport;
us->flags = unusual_dev->flags;
+ /*
+ * This flag is only needed when we're in high-speed, so let's
+ * disable it if we're in full-speed
+ */
+ if (dev->speed != USB_SPEED_HIGH)
+ us->flags &= ~US_FL_GO_SLOW;
+
/* Log a message if a non-generic unusual_dev entry contains an
* unnecessary subclass or protocol override. This may stimulate
* reports from users that will help us remove unneeded entries
idesc->bInterfaceProtocol,
msgs[msg]);
}
-
- /* Read the device's string descriptors */
- if (dev->descriptor.iManufacturer)
- usb_string(dev, dev->descriptor.iManufacturer,
- us->vendor, sizeof(us->vendor));
- if (dev->descriptor.iProduct)
- usb_string(dev, dev->descriptor.iProduct,
- us->product, sizeof(us->product));
- if (dev->descriptor.iSerialNumber)
- usb_string(dev, dev->descriptor.iSerialNumber,
- us->serial, sizeof(us->serial));
-
- /* Use the unusual_dev strings if the device didn't provide them */
- if (strlen(us->vendor) == 0) {
- if (unusual_dev->vendorName)
- strlcpy(us->vendor, unusual_dev->vendorName,
- sizeof(us->vendor));
- else
- strcpy(us->vendor, "Unknown");
- }
- if (strlen(us->product) == 0) {
- if (unusual_dev->productName)
- strlcpy(us->product, unusual_dev->productName,
- sizeof(us->product));
- else
- strcpy(us->product, "Unknown");
- }
- if (strlen(us->serial) == 0)
- strcpy(us->serial, "None");
-
- US_DEBUGP("Vendor: %s, Product: %s\n", us->vendor, us->product);
}
/* Get the transport settings */
us->transport_reset = usb_stor_Bulk_reset;
break;
-#ifdef CONFIG_USB_STORAGE_HP8200e
+#ifdef CONFIG_USB_STORAGE_USBAT
case US_PR_SCM_ATAPI:
us->transport_name = "SCM/ATAPI";
- us->transport = hp8200e_transport;
+ us->transport = usbat_transport;
us->transport_reset = usb_stor_CB_reset;
us->max_lun = 1;
break;
up(&us->dev_semaphore);
- /*
- * Since this is a new device, we need to register a SCSI
- * host definition with the higher SCSI layers.
- */
- us->host = scsi_host_alloc(&usb_stor_host_template, sizeof(us));
- if (!us->host) {
- printk(KERN_WARNING USB_STORAGE
- "Unable to allocate the scsi host\n");
- return -EBUSY;
- }
-
- /* Set the hostdata to prepare for scanning */
- us->host->hostdata[0] = (unsigned long) us;
-
/* Start up our control thread */
p = kernel_thread(usb_stor_control_thread, us, CLONE_VM);
if (p < 0) {
return p;
}
us->pid = p;
+ atomic_inc(&total_threads);
/* Wait for the thread to start */
wait_for_completion(&(us->notify));
}
/* Release all our dynamic resources */
-void usb_stor_release_resources(struct us_data *us)
+static void usb_stor_release_resources(struct us_data *us)
{
US_DEBUGP("-- %s\n", __FUNCTION__);
- /* Kill the control thread. The SCSI host must already have been
- * removed so it won't try to queue any more commands.
+ /* Tell the control thread to exit. The SCSI host must
+ * already have been removed so it won't try to queue
+ * any more commands.
*/
- if (us->pid) {
-
- /* Wait for the thread to be idle */
- down(&us->dev_semaphore);
- US_DEBUGP("-- sending exit command to thread\n");
-
- /* If the SCSI midlayer queued a final command just before
- * scsi_remove_host() was called, us->srb might not be
- * NULL. We can overwrite it safely, because the midlayer
- * will not wait for the command to finish. Also the
- * control thread will already have been awakened.
- * That's okay, an extra up() on us->sema won't hurt.
- *
- * Enqueue the command, wake up the thread, and wait for
- * notification that it has exited.
- */
- scsi_lock(us->host);
- us->srb = NULL;
- scsi_unlock(us->host);
- up(&us->dev_semaphore);
-
- up(&us->sema);
- wait_for_completion(&us->notify);
- }
+ US_DEBUGP("-- sending exit command to thread\n");
+ up(&us->sema);
/* Call the destructor routine, if it exists */
if (us->extra_destructor) {
us->extra_destructor(us->extra);
}
- /* Finish the host removal sequence */
- if (us->host)
- scsi_host_put(us->host);
-
/* Free the extra data and the URB */
- if (us->extra)
- kfree(us->extra);
- if (us->current_urb)
- usb_free_urb(us->current_urb);
-
+ kfree(us->extra);
+ usb_free_urb(us->current_urb);
}
/* Dissociate from the USB device */
/* Remove our private data from the interface */
usb_set_intfdata(us->pusb_intf, NULL);
-
- /* Free the structure itself */
- kfree(us);
}
/* Thread to carry out delayed SCSI-device scanning */
daemonize("usb-stor-scan");
unlock_kernel();
+ /* Acquire a reference to the host, so it won't be deallocated
+ * until we're ready to exit */
+ scsi_host_get(us_to_host(us));
+
+ /* Signal that we've started the thread */
+ complete(&(us->notify));
+
printk(KERN_DEBUG
"usb-storage: device found at %d\n", us->pusb_dev->devnum);
printk(KERN_DEBUG "usb-storage: waiting for device "
"to settle before scanning\n");
retry:
- wait_event_interruptible_timeout(us->scsi_scan_wait,
+ wait_event_interruptible_timeout(us->delay_wait,
test_bit(US_FLIDX_DISCONNECTING, &us->flags),
delay_use * HZ);
if (current->flags & PF_FREEZE) {
/* If the device is still connected, perform the scanning */
if (!test_bit(US_FLIDX_DISCONNECTING, &us->flags)) {
- scsi_scan_host(us->host);
+ scsi_scan_host(us_to_host(us));
printk(KERN_DEBUG "usb-storage: device scan complete\n");
+
+ /* Should we unbind if no devices were detected? */
}
- complete_and_exit(&us->scsi_scan_done, 0);
+ scsi_host_put(us_to_host(us));
+ complete_and_exit(&threads_gone, 0);
}
static int storage_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
+ struct Scsi_Host *host;
struct us_data *us;
const int id_index = id - storage_usb_ids;
int result;
US_DEBUGP("USB Mass Storage device detected\n");
- /* Allocate the us_data structure and initialize the mutexes */
- us = (struct us_data *) kmalloc(sizeof(*us), GFP_KERNEL);
- if (!us) {
- printk(KERN_WARNING USB_STORAGE "Out of memory\n");
+ /*
+ * Ask the SCSI layer to allocate a host structure, with extra
+ * space at the end for our private us_data structure.
+ */
+ host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us));
+ if (!host) {
+ printk(KERN_WARNING USB_STORAGE
+ "Unable to allocate the scsi host\n");
return -ENOMEM;
}
+
+ us = host_to_us(host);
memset(us, 0, sizeof(struct us_data));
init_MUTEX(&(us->dev_semaphore));
init_MUTEX_LOCKED(&(us->sema));
init_completion(&(us->notify));
- init_waitqueue_head(&us->dev_reset_wait);
- init_waitqueue_head(&us->scsi_scan_wait);
- init_completion(&us->scsi_scan_done);
+ init_waitqueue_head(&us->delay_wait);
/* Associate the us_data structure with the USB device */
result = associate_dev(us, intf);
result = usb_stor_acquire_resources(us);
if (result)
goto BadDevice;
- result = scsi_add_host(us->host, &intf->dev);
+ result = scsi_add_host(host, &intf->dev);
if (result) {
printk(KERN_WARNING USB_STORAGE
"Unable to add the scsi host\n");
if (result < 0) {
printk(KERN_WARNING USB_STORAGE
"Unable to start the device-scanning thread\n");
- scsi_remove_host(us->host);
+ scsi_remove_host(host);
goto BadDevice;
}
+ atomic_inc(&total_threads);
+
+ /* Wait for the thread to start */
+ wait_for_completion(&(us->notify));
return 0;
/* 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);
return result;
}
US_DEBUGP("storage_disconnect() called\n");
/* Prevent new USB transfers, stop the current command, and
- * interrupt a device-reset delay */
+ * interrupt a SCSI-scan or device-reset delay */
set_bit(US_FLIDX_DISCONNECTING, &us->flags);
usb_stor_stop_transport(us);
- wake_up(&us->dev_reset_wait);
+ wake_up(&us->delay_wait);
- /* Interrupt the SCSI-device-scanning thread's time delay, and
- * wait for the thread to finish */
- wake_up(&us->scsi_scan_wait);
- wait_for_completion(&us->scsi_scan_done);
+ /* 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->host);
+ 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));
}
/***********************************************************************
*/
US_DEBUGP("-- calling usb_deregister()\n");
usb_deregister(&usb_storage_driver) ;
+
+ /* Don't return until all of our control and scanning threads
+ * have exited. Since each thread signals threads_gone as its
+ * last act, we have to call wait_for_completion the right number
+ * of times.
+ */
+ while (atomic_read(&total_threads) > 0) {
+ wait_for_completion(&threads_gone);
+ atomic_dec(&total_threads);
+ }
}
module_init(usb_stor_init);