| From: |
| Alan Stern <stern@rowland.harvard.edu> |
| To: |
| USB development list <linux-usb-devel@lists.sourceforge.net> |
| Subject: |
| Request for testing: usb-storage autosuspend |
| Date: |
| Thu, 2 Dec 2004 12:22:33 -0500 (EST) |
| Archive-link: |
| Article,
Thread
|
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=110...
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 <mdharm-usb@one-eyed-alien.net>");
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/
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel