Below is a composite of three patches pending for the usb-storage driver. Together they provide support for autosuspend. The inactivity timeout is controlled by a module parameter named "autosuspend"; its value gives the timeout in seconds (0 means no autosuspend).
If anyone can try this out and let me know how it works, I would appreciate it. If you are using a UHCI host controller, then you should probably also apply this patch: http://marc.theaimsgroup.com/?l=linux-usb-devel&m=110193921701118&w=2 Thanks, Alan Stern ===== drivers/usb/storage/datafab.c 1.36 vs edited ===== --- 1.36/drivers/usb/storage/datafab.c 2004-10-11 13:52:07 -04:00 +++ edited/drivers/usb/storage/datafab.c 2004-12-02 12:16:51 -05:00 @@ -57,9 +57,9 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "datafab.h" ===== drivers/usb/storage/dpcm.c 1.6 vs edited ===== --- 1.6/drivers/usb/storage/dpcm.c 2004-08-24 10:55:46 -04:00 +++ edited/drivers/usb/storage/dpcm.c 2004-12-02 12:16:51 -05:00 @@ -34,9 +34,9 @@ #include <scsi/scsi_cmnd.h> #include <scsi/scsi_device.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "dpcm.h" #include "sddr09.h" ===== drivers/usb/storage/freecom.c 1.41 vs edited ===== --- 1.41/drivers/usb/storage/freecom.c 2004-11-15 12:27:17 -05:00 +++ edited/drivers/usb/storage/freecom.c 2004-12-02 12:16:51 -05:00 @@ -34,9 +34,9 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "freecom.h" ===== drivers/usb/storage/initializers.c 1.12 vs edited ===== --- 1.12/drivers/usb/storage/initializers.c 2003-08-11 12:21:44 -04:00 +++ edited/drivers/usb/storage/initializers.c 2004-12-02 12:16:51 -05:00 @@ -39,6 +39,8 @@ #include <linux/sched.h> #include <linux/errno.h> + +#include "usb.h" #include "initializers.h" #include "debug.h" #include "transport.h" ===== drivers/usb/storage/isd200.c 1.58 vs edited ===== --- 1.58/drivers/usb/storage/isd200.c 2004-11-22 13:42:02 -05:00 +++ edited/drivers/usb/storage/isd200.c 2004-12-02 12:16:51 -05:00 @@ -54,9 +54,9 @@ #include <scsi/scsi_cmnd.h> #include <scsi/scsi_device.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "scsiglue.h" #include "isd200.h" ===== drivers/usb/storage/jumpshot.c 1.39 vs edited ===== --- 1.39/drivers/usb/storage/jumpshot.c 2004-10-11 13:52:07 -04:00 +++ edited/drivers/usb/storage/jumpshot.c 2004-12-02 12:16:51 -05:00 @@ -54,9 +54,9 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "jumpshot.h" ===== drivers/usb/storage/protocol.c 1.25 vs edited ===== --- 1.25/drivers/usb/storage/protocol.c 2004-10-20 12:38:15 -04:00 +++ edited/drivers/usb/storage/protocol.c 2004-12-02 12:16:51 -05:00 @@ -47,8 +47,9 @@ #include <linux/highmem.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> -#include "protocol.h" + #include "usb.h" +#include "protocol.h" #include "debug.h" #include "scsiglue.h" #include "transport.h" ===== drivers/usb/storage/scsiglue.c 1.90 vs edited ===== --- 1.90/drivers/usb/storage/scsiglue.c 2004-11-22 13:42:02 -05:00 +++ edited/drivers/usb/storage/scsiglue.c 2004-12-02 12:17:03 -05:00 @@ -53,10 +53,9 @@ #include <scsi/scsi_devinfo.h> #include <scsi/scsi_device.h> #include <scsi/scsi_eh.h> -#include <scsi/scsi_host.h> -#include "scsiglue.h" #include "usb.h" +#include "scsiglue.h" #include "debug.h" #include "transport.h" #include "protocol.h" @@ -83,7 +82,7 @@ static int slave_configure(struct scsi_device *sdev) { - struct us_data *us = (struct us_data *) sdev->host->hostdata[0]; + struct us_data *us = host_to_us(sdev->host); /* Scatter-gather buffers (all but the last) must have a length * divisible by the bulk maxpacket size. Otherwise a data packet @@ -167,10 +166,9 @@ static int queuecommand(struct scsi_cmnd *srb, void (*done)(struct scsi_cmnd *)) { - struct us_data *us = (struct us_data *)srb->device->host->hostdata[0]; + struct us_data *us = host_to_us(srb->device->host); US_DEBUGP("%s called\n", __FUNCTION__); - srb->host_scribble = (unsigned char *)us; /* check for state-transition errors */ if (us->srb != NULL) { @@ -201,10 +199,9 @@ /* Command timeout and abort */ /* This is always called with scsi_lock(srb->host) held */ -static int command_abort(struct scsi_cmnd *srb ) +static int command_abort(struct scsi_cmnd *srb) { - struct Scsi_Host *host = srb->device->host; - struct us_data *us = (struct us_data *) host->hostdata[0]; + struct us_data *us = host_to_us(srb->device->host); US_DEBUGP("%s called\n", __FUNCTION__); @@ -224,13 +221,13 @@ set_bit(US_FLIDX_ABORTING, &us->flags); usb_stor_stop_transport(us); } - scsi_unlock(host); + scsi_unlock(us_to_host(us)); /* Wait for the aborted command to finish */ wait_for_completion(&us->notify); /* Reacquire the lock and allow USB transfers to resume */ - scsi_lock(host); + scsi_lock(us_to_host(us)); clear_bit(US_FLIDX_ABORTING, &us->flags); clear_bit(US_FLIDX_TIMED_OUT, &us->flags); return SUCCESS; @@ -241,24 +238,26 @@ /* This is always called with scsi_lock(srb->host) held */ static int device_reset(struct scsi_cmnd *srb) { - struct us_data *us = (struct us_data *)srb->device->host->hostdata[0]; + struct us_data *us = host_to_us(srb->device->host); int result; US_DEBUGP("%s called\n", __FUNCTION__); - scsi_unlock(srb->device->host); + scsi_unlock(us_to_host(us)); /* lock the device pointers and do the reset */ down(&(us->dev_semaphore)); if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { result = FAILED; US_DEBUGP("No reset during disconnect\n"); - } else + } else { result = us->transport_reset(us); + usb_stor_autosuspend_event(us); + } up(&(us->dev_semaphore)); /* lock the host for the return */ - scsi_lock(srb->device->host); + scsi_lock(us_to_host(us)); return result; } @@ -268,12 +267,12 @@ /* This is always called with scsi_lock(srb->host) held */ static int bus_reset(struct scsi_cmnd *srb) { - struct us_data *us = (struct us_data *)srb->device->host->hostdata[0]; + struct us_data *us = host_to_us(srb->device->host); int result, rc; US_DEBUGP("%s called\n", __FUNCTION__); - scsi_unlock(srb->device->host); + scsi_unlock(us_to_host(us)); /* The USB subsystem doesn't handle synchronisation between * a device's several drivers. Therefore we reset only devices @@ -283,7 +282,7 @@ if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { result = -EIO; US_DEBUGP("No reset during disconnect\n"); - } else if (us->pusb_dev->actconfig->desc.bNumInterfaces != 1) { + } else if (us->flags & US_FL_COMPOSITE) { result = -EBUSY; US_DEBUGP("Refusing to reset a multi-interface device\n"); } else { @@ -296,12 +295,13 @@ if (rc) usb_unlock_device(us->pusb_dev); US_DEBUGP("usb_reset_device returns %d\n", result); + usb_stor_autosuspend_event(us); } } up(&(us->dev_semaphore)); /* lock the host for the return */ - scsi_lock(srb->device->host); + scsi_lock(us_to_host(us)); return result < 0 ? FAILED : SUCCESS; } @@ -311,11 +311,12 @@ void usb_stor_report_device_reset(struct us_data *us) { int i; + struct Scsi_Host *host = us_to_host(us); - scsi_report_device_reset(us->host, 0, 0); + scsi_report_device_reset(host, 0, 0); if (us->flags & US_FL_SCM_MULT_TARG) { - for (i = 1; i < us->host->max_id; ++i) - scsi_report_device_reset(us->host, 0, i); + for (i = 1; i < host->max_id; ++i) + scsi_report_device_reset(host, 0, i); } } @@ -330,20 +331,18 @@ #define DO_FLAG(a) \ do { if (us->flags & US_FL_##a) pos += sprintf(pos, " " #a); } while(0) -static int proc_info (struct Scsi_Host *hostptr, char *buffer, char **start, off_t offset, - int length, int inout) +static int proc_info (struct Scsi_Host *host, char *buffer, + char **start, off_t offset, int length, int inout) { - struct us_data *us; + struct us_data *us = host_to_us(host); char *pos = buffer; /* if someone is sending us data, just throw it away */ if (inout) return length; - us = (struct us_data*)hostptr->hostdata[0]; - /* print the controller name */ - SPRINTF(" Host scsi%d: usb-storage\n", hostptr->host_no); + SPRINTF(" Host scsi%d: usb-storage\n", host->host_no); /* print product, vendor, and serial number strings */ SPRINTF(" Vendor: %s\n", us->vendor); @@ -362,6 +361,7 @@ DO_FLAG(SCM_MULT_TARG); DO_FLAG(FIX_INQUIRY); DO_FLAG(FIX_CAPACITY); + DO_FLAG(IGNORE_RESIDUE); *(pos++) = '\n'; } ===== drivers/usb/storage/scsiglue.h 1.13 vs edited ===== --- 1.13/drivers/usb/storage/scsiglue.h 2004-08-24 10:55:47 -04:00 +++ edited/drivers/usb/storage/scsiglue.h 2004-12-02 12:16:51 -05:00 @@ -41,10 +41,8 @@ #ifndef _SCSIGLUE_H_ #define _SCSIGLUE_H_ -#include <scsi/scsi_host.h> - struct us_data; -struct scsi_cmnd; +struct scsi_host_template; extern void usb_stor_report_device_reset(struct us_data *us); ===== drivers/usb/storage/sddr09.c 1.48 vs edited ===== --- 1.48/drivers/usb/storage/sddr09.c 2004-10-11 13:52:08 -04:00 +++ edited/drivers/usb/storage/sddr09.c 2004-12-02 12:16:51 -05:00 @@ -48,9 +48,9 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "sddr09.h" ===== drivers/usb/storage/sddr55.c 1.23 vs edited ===== --- 1.23/drivers/usb/storage/sddr55.c 2004-10-11 13:52:08 -04:00 +++ edited/drivers/usb/storage/sddr55.c 2004-12-02 12:16:51 -05:00 @@ -31,9 +31,9 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "sddr55.h" ===== drivers/usb/storage/shuttle_usbat.c 1.35 vs edited ===== --- 1.35/drivers/usb/storage/shuttle_usbat.c 2004-08-24 10:55:48 -04:00 +++ edited/drivers/usb/storage/shuttle_usbat.c 2004-12-02 12:16:51 -05:00 @@ -47,9 +47,9 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include "usb.h" #include "transport.h" #include "protocol.h" -#include "usb.h" #include "debug.h" #include "shuttle_usbat.h" ===== drivers/usb/storage/transport.c 1.153 vs edited ===== --- 1.153/drivers/usb/storage/transport.c 2004-11-22 13:42:02 -05:00 +++ edited/drivers/usb/storage/transport.c 2004-12-02 12:16:51 -05:00 @@ -54,10 +54,10 @@ #include <scsi/scsi_cmnd.h> #include <scsi/scsi_device.h> +#include "usb.h" #include "transport.h" #include "protocol.h" #include "scsiglue.h" -#include "usb.h" #include "debug.h" @@ -1118,11 +1118,11 @@ * RESETTING bit, and clear the ABORTING bit so that the reset * may proceed. */ - scsi_lock(us->host); + scsi_lock(us_to_host(us)); usb_stor_report_device_reset(us); set_bit(US_FLIDX_RESETTING, &us->flags); clear_bit(US_FLIDX_ABORTING, &us->flags); - scsi_unlock(us->host); + scsi_unlock(us_to_host(us)); /* A 20-second timeout may seem rather long, but a LaCie * StudioDrive USB2 device takes 16+ seconds to get going ===== drivers/usb/storage/transport.h 1.44 vs edited ===== --- 1.44/drivers/usb/storage/transport.h 2004-10-11 13:52:09 -04:00 +++ edited/drivers/usb/storage/transport.h 2004-12-02 12:16:51 -05:00 @@ -43,8 +43,8 @@ #include <linux/config.h> #include <linux/blkdev.h> -#include "usb.h" +struct us_data; struct scsi_cmnd; /* Protocols */ ===== drivers/usb/storage/usb.c 1.130 vs edited ===== --- 1.130/drivers/usb/storage/usb.c 2004-11-22 13:42:02 -05:00 +++ edited/drivers/usb/storage/usb.c 2004-12-02 12:17:03 -05:00 @@ -51,6 +51,9 @@ #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> @@ -89,10 +92,6 @@ #endif -#include <linux/module.h> -#include <linux/init.h> -#include <linux/slab.h> - /* Some informational data */ MODULE_AUTHOR("Matthew Dharm <[EMAIL PROTECTED]>"); MODULE_DESCRIPTION("USB Mass Storage driver for Linux"); @@ -102,6 +101,24 @@ module_param(delay_use, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(delay_use, "seconds to delay before using a new device"); +#ifdef CONFIG_USB_SUSPEND +static unsigned int autosuspend = 0; +module_param(autosuspend, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(autosuspend, + "timeout in seconds for autosuspending an inactive device " + "(0 for no autosuspend)"); + +static int storage_suspend(struct usb_interface *iface, u32 state); +static int storage_resume(struct usb_interface *iface); +#endif + + +/* 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); @@ -234,9 +251,131 @@ .name = "usb-storage", .probe = storage_probe, .disconnect = storage_disconnect, +#ifdef CONFIG_USB_SUSPEND + .suspend = storage_suspend, + .resume = storage_resume, +#endif .id_table = storage_usb_ids, }; + +#ifdef CONFIG_USB_SUSPEND + +static int storage_suspend(struct usb_interface *iface, u32 state) +{ + struct us_data *us = usb_get_intfdata(iface); + + down(&us->dev_semaphore); + if (!test_and_set_bit(US_FLIDX_SUSPENDED, &us->flags)) { + US_DEBUGP("%s\n", __FUNCTION__); + iface->dev.power.power_state = state; + } + up(&us->dev_semaphore); + return 0; +} + +static int storage_resume(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + down(&us->dev_semaphore); + if (test_and_clear_bit(US_FLIDX_SUSPENDED, &us->flags)) { + US_DEBUGP("%s\n", __FUNCTION__); + iface->dev.power.power_state = PM_SUSPEND_ON; + } + up(&us->dev_semaphore); + return 0; +} + +/* Return 1 if it's time to autosuspend; otherwise set the timer if needed */ +static int autosuspend_update(struct us_data *us) +{ + unsigned long timeout; + + if (autosuspend == 0 || + test_bit(US_FLIDX_DISCONNECTING, &us->flags) || + test_bit(US_FLIDX_SUSPENDED, &us->flags) || + // !us->hdev->dev.power.wakeup_enabled || + us->flags & US_FL_COMPOSITE) + return 0; + timeout = us->last_event_time + autosuspend * HZ; + if (time_before(jiffies, timeout)) { + mod_timer(&us->autosuspend_timer, timeout); + return 0; + } + return 1; +} + +/* An autosuspend event occurred so restart the timer. + * us->dev_semaphore should be locked. */ +void usb_stor_autosuspend_event(struct us_data *us) +{ + us->last_event_time = jiffies; + autosuspend_update(us); +} + +/* It may be time to autosuspend. Do it if everything is right. Must + * be in process context (able to sleep) with us->dev_semaphore unlocked. */ +static inline void autosuspend_try(struct us_data *us) +{ + if (autosuspend_update(us)) { + US_DEBUGP("autosuspend\n"); + usb_suspend_device(us->pusb_dev, PM_SUSPEND_MEM); + + /* If the suspend failed, trying again later isn't + * likely to help. Just let it go... */ + } +} + +/* It may be time to autosuspend. Tell the control thread if we're ready. */ +static void autosuspend_timer_func(unsigned long __us) +{ + struct us_data *us = (struct us_data *) __us; + + if (autosuspend_update(us)) { + + /* Invoking the control thread with us->srb == NULL will + * cause it to try an autosuspend. */ + up(&us->sema); + } +} + +/* Autoresume on demand. Returns nonzero if the device has disconnected. + * us->dev_semaphore must be locked. */ +static inline int autoresume_try(struct us_data *us) +{ + if (test_bit(US_FLIDX_SUSPENDED, &us->flags)) { + up(&us->dev_semaphore); + US_DEBUGP("autoresume\n"); + usb_resume_device(us->pusb_dev); + down(&us->dev_semaphore); + return test_bit(US_FLIDX_DISCONNECTING, &us->flags); + } + return 0; +} + +static inline void autosuspend_activate(struct us_data *us) +{ + init_timer(&us->autosuspend_timer); + us->autosuspend_timer.function = autosuspend_timer_func; + us->autosuspend_timer.data = (unsigned long) us; +} + +static inline void autosuspend_quiesce(struct us_data *us) +{ + del_timer_sync(&us->autosuspend_timer); +} + +#else /* #ifdef CONFIG_USB_SUSPEND */ + +static inline void autosuspend_try(struct us_data *us) {} +static inline int autoresume_try(struct us_data *us) { return 0; } +static inline void autosuspend_activate(struct us_data *us) {} +static inline void autosuspend_quiesce(struct us_data *us) {} + +#endif + + /* * fill_inquiry_response takes an unsigned char array (which must * be at least 36 characters) and populates the vendor name, @@ -281,7 +420,7 @@ 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(); @@ -290,11 +429,13 @@ * 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)); @@ -308,15 +449,16 @@ /* 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"); - up(&(us->dev_semaphore)); + /* if the device has disconnected, we are free to exit */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) break; - } - /* lock access to the state */ - scsi_lock(host); + /* is this an autosuspend request? */ + if (!us->srb) { + up(&us->dev_semaphore); + autosuspend_try(us); + continue; + } /* has the command timed out *already* ? */ if (test_bit(US_FLIDX_TIMED_OUT, &us->flags)) { @@ -324,14 +466,6 @@ 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 * is UNKNOWN */ @@ -369,12 +503,17 @@ us->srb->result = SAM_STAT_GOOD; } + /* autoresume if we are suspended */ + else if (autoresume_try(us) != 0) + break; + /* we've got a command, let's do it! */ else { US_DEBUG(usb_stor_show_command(us->srb)); us->proto_handler(us->srb, us); } +SkipForAbort: /* lock access to the state */ scsi_lock(host); @@ -384,7 +523,6 @@ us->srb->result); us->srb->scsi_done(us->srb); } else { -SkipForAbort: US_DEBUGP("scsi command aborted\n"); } @@ -397,14 +535,18 @@ complete(&(us->notify)); /* finished working on this command */ -SkipForDisconnect: us->srb = NULL; scsi_unlock(host); + usb_stor_autosuspend_event(us); /* unlock the device pointers */ up(&(us->dev_semaphore)); } /* for (;;) */ + up(&us->dev_semaphore); + US_DEBUGP("-- exiting\n"); + scsi_host_put(host); + /* notify the exit routine that we're actually exiting now * * complete()/wait_for_completion() is similar to up()/down(), @@ -419,7 +561,7 @@ * 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); } /*********************************************************************** @@ -481,6 +623,8 @@ idesc->bInterfaceProtocol : unusual_dev->useTransport; us->flags = unusual_dev->flags; + if (us->pusb_dev->actconfig->desc.bNumInterfaces != 1) + us->flags |= US_FL_COMPOSITE; /* Log a message if a non-generic unusual_dev entry contains an * unnecessary subclass or protocol override. This may stimulate @@ -783,20 +927,6 @@ 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) { @@ -805,10 +935,14 @@ return p; } us->pid = p; + atomic_inc(&total_threads); /* Wait for the thread to start */ wait_for_completion(&(us->notify)); + /* Initialize the autosuspend stuff */ + autosuspend_activate(us); + return 0; } @@ -817,33 +951,15 @@ { 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. - */ - 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); + /* Stop the autosuspend timer */ + autosuspend_quiesce(us); - up(&us->sema); - wait_for_completion(&us->notify); - } + /* Tell the control thread to exit. The SCSI host must + * already have been removed so it won't try to queue + * any more commands. + */ + US_DEBUGP("-- sending exit command to thread\n"); + up(&us->sema); /* Call the destructor routine, if it exists */ if (us->extra_destructor) { @@ -851,15 +967,9 @@ 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); } @@ -878,9 +988,6 @@ /* 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 */ @@ -896,6 +1003,13 @@ 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); @@ -904,7 +1018,7 @@ 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->dev_reset_wait, test_bit(US_FLIDX_DISCONNECTING, &us->flags), delay_use * HZ); if (current->flags & PF_FREEZE) { @@ -915,11 +1029,14 @@ /* 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); } @@ -927,25 +1044,30 @@ 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); /* Associate the us_data structure with the USB device */ result = associate_dev(us, intf); @@ -998,7 +1120,7 @@ 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"); @@ -1010,17 +1132,23 @@ 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; } @@ -1032,24 +1160,26 @@ US_DEBUGP("storage_disconnect() called\n"); /* Prevent new USB transfers, stop the current command, and - * interrupt a device-reset delay */ + * interrupt a device-reset or SCSI-scan delay */ set_bit(US_FLIDX_DISCONNECTING, &us->flags); usb_stor_stop_transport(us); wake_up(&us->dev_reset_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)); } /*********************************************************************** @@ -1079,6 +1209,16 @@ */ 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); ===== drivers/usb/storage/usb.h 1.66 vs edited ===== --- 1.66/drivers/usb/storage/usb.h 2004-11-22 13:42:02 -05:00 +++ edited/drivers/usb/storage/usb.h 2004-12-02 12:17:03 -05:00 @@ -48,6 +48,8 @@ #include <linux/blkdev.h> #include <linux/smp_lock.h> #include <linux/completion.h> +#include <linux/timer.h> +#include <scsi/scsi_host.h> struct us_data; struct scsi_cmnd; @@ -74,6 +76,7 @@ #define US_FL_FIX_INQUIRY 0x00000040 /* INQUIRY response needs faking */ #define US_FL_FIX_CAPACITY 0x00000080 /* READ CAPACITY response too big */ #define US_FL_IGNORE_RESIDUE 0x00000100 /* reported residue is wrong */ +#define US_FL_COMPOSITE 0x00000800 /* there are multiple interfaces */ /* Dynamic flag definitions: used in set_bit() etc. */ #define US_FLIDX_URB_ACTIVE 18 /* 0x00040000 current_urb is in use */ @@ -84,6 +87,7 @@ (1UL << US_FLIDX_DISCONNECTING)) #define US_FLIDX_RESETTING 22 /* 0x00400000 device reset in progress */ #define US_FLIDX_TIMED_OUT 23 /* 0x00800000 SCSI midlayer timed out */ +#define US_FLIDX_SUSPENDED 24 /* 0x01000000 device is suspended */ #define USB_STOR_STRING_LEN 32 @@ -138,7 +142,6 @@ proto_cmnd proto_handler; /* protocol handler */ /* SCSI interfaces */ - struct Scsi_Host *host; /* our dummy host data */ struct scsi_cmnd *srb; /* current srb */ /* thread information */ @@ -156,14 +159,26 @@ struct semaphore sema; /* to sleep thread on */ struct completion notify; /* thread begin/end */ wait_queue_head_t dev_reset_wait; /* wait during reset */ - wait_queue_head_t scsi_scan_wait; /* wait before scanning */ - struct completion scsi_scan_done; /* scan thread end */ /* subdriver information */ void *extra; /* Any extra data */ extra_data_destructor extra_destructor;/* extra data destructor */ + +#ifdef CONFIG_USB_SUSPEND + /* autosuspend support */ + unsigned long last_event_time; + struct timer_list autosuspend_timer; +#endif }; +/* Convert between us_data and the corresponding Scsi_Host */ +static struct Scsi_Host inline *us_to_host(struct us_data *us) { + return container_of((void *) us, struct Scsi_Host, hostdata); +} +static struct us_data inline *host_to_us(struct Scsi_Host *host) { + return (struct us_data *) host->hostdata; +} + /* The structure which defines our driver */ extern struct usb_driver usb_storage_driver; @@ -175,6 +190,13 @@ * single queue element srb for write access */ #define scsi_unlock(host) spin_unlock_irq(host->host_lock) #define scsi_lock(host) spin_lock_irq(host->host_lock) + +/* Function for reporting events that should delay autosuspend */ +#ifdef CONFIG_USB_SUSPEND +extern void usb_stor_autosuspend_event(struct us_data *us); +#else +static inline void usb_stor_autosuspend_event(struct us_data *us) {} +#endif /* Vendor ID list for devices that require special handling */ ------------------------------------------------------- SF email is sponsored by - The IT Product Guide Read honest & candid reviews on hundreds of IT Products from real users. Discover which products truly live up to the hype. Start reading now. http://productguide.itmanagersjournal.com/ _______________________________________________ [EMAIL PROTECTED] To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel