Hi,

Alan Stern has written a change to the generic autosuspend mechanism that
allows timeout based suspension. As he suggested I've tested it by using
it for suspension of HID devices. It took some small changes to Alan's code
to introduce a bit more flexibility. It's tested with mice but needs review.

To allow testing I am posting the full amalgated patch against 2.6.20.
I'll also put apart the patches.

        Regards
                Oliver

------
--- linux-2.6.20/include/linux/hid.h    2007-02-06 14:14:56.000000000 +0100
+++ linux-stern/include/linux/hid.h     2007-02-15 14:26:18.000000000 +0100
@@ -389,6 +389,8 @@
 #define HID_RESET_PENDING      4
 #define HID_SUSPENDED          5
 #define HID_CLEAR_HALT         6
+#define HID_BUSY               7
+#define HID_REPORTED_IDLE      8
 
 struct hid_input {
        struct list_head list;
--- linux-2.6.20/drivers/usb/input/usbhid.h     2007-02-06 14:14:49.000000000 
+0100
+++ linux-stern/drivers/usb/input/usbhid.h      2007-02-15 14:17:59.000000000 
+0100
@@ -63,21 +63,24 @@
        unsigned char ctrlhead, ctrltail;                               /* 
Control fifo head & tail */
        char *ctrlbuf;                                                  /* 
Control buffer */
        dma_addr_t ctrlbuf_dma;                                         /* 
Control buffer dma */
-       spinlock_t ctrllock;                                            /* 
Control fifo spinlock */
 
        struct urb *urbout;                                             /* 
Output URB */
        struct hid_report *out[HID_CONTROL_FIFO_SIZE];                  /* 
Output pipe fifo */
        unsigned char outhead, outtail;                                 /* 
Output pipe fifo head & tail */
        char *outbuf;                                                   /* 
Output buffer */
        dma_addr_t outbuf_dma;                                          /* 
Output buffer dma */
-       spinlock_t outlock;                                             /* 
Output fifo spinlock */
 
-       unsigned long iofl;                                             /* I/O 
flags (CTRL_RUNNING, OUT_RUNNING) */
-       struct timer_list io_retry;                                     /* 
Retry timer */
-       unsigned long stop_retry;                                       /* Time 
to give up, in jiffies */
-       unsigned int retry_delay;                                       /* 
Delay length in ms */
-       struct work_struct reset_work;                                  /* Task 
context for resets */
+       spinlock_t outputlock;                                             /* 
Output fifo spinlock */
 
+       unsigned long iofl;                                     /* I/O flags 
(CTRL_RUNNING, OUT_RUNNING) */
+       struct timer_list io_retry;                             /* Retry timer 
*/
+       struct timer_list idle_timer;                           /* timer to 
determine idleness */
+       unsigned long stop_retry;                               /* Time to give 
up, in jiffies */
+       unsigned int idle_time;                                 /* Time to 
determine idleness, in seconds */
+       unsigned int retry_delay;                               /* Delay length 
in ms */
+       struct work_struct reset_work;                          /* Task context 
for resets */
+       struct work_struct idle_work;                           /* Task context 
for reporting idleness */
+       struct work_struct restart_work;                        /* waking up 
for output to be done in task context */
 };
 
 #define        hid_to_usb_dev(hid_dev) \
--- linux-2.6.20/drivers/usb/input/hid-core.c   2007-02-06 14:14:48.000000000 
+0100
+++ linux-stern/drivers/usb/input/hid-core.c    2007-02-15 15:47:37.000000000 
+0100
@@ -5,6 +5,7 @@
  *  Copyright (c) 2000-2005 Vojtech Pavlik <[EMAIL PROTECTED]>
  *  Copyright (c) 2005 Michael Haboustak <[EMAIL PROTECTED]> for Concept2, Inc
  *  Copyright (c) 2006 Jiri Kosina
+ *  Copyright (c) 2007 Oliver Neukum
  */
 
 /*
@@ -56,11 +57,15 @@
 module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);
 MODULE_PARM_DESC(mousepoll, "Polling interval of mice");
 
+static DEFINE_MUTEX(hid_open_mut);
+
 /*
  * Input submission and I/O error handler.
  */
 
 static void hid_io_error(struct hid_device *hid);
+static int hid_submit_out(struct hid_device *hid);
+static int hid_submit_ctrl(struct hid_device *hid);
 
 /* Start up the input URB */
 static int hid_start_in(struct hid_device *hid)
@@ -169,6 +174,44 @@
        spin_unlock_irqrestore(&usbhid->inlock, flags);
 }
 
+static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
+{
+       struct hid_device *hid = usb_get_intfdata(usbhid->intf);
+       int kicked;
+
+       WARN_ON(hid == NULL);
+       if (!hid)
+               return 0;
+
+       if ((kicked = (usbhid->outhead != usbhid->outtail))) {
+               dbg("Kicking head %d tail %d", usbhid->outhead, 
usbhid->outtail);
+               if (hid_submit_out(hid)) {
+                       clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+                       wake_up(&hid->wait);
+               }
+       }
+       return kicked;
+}
+
+static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
+{
+       struct hid_device *hid = usb_get_intfdata(usbhid->intf);
+       int kicked;
+
+       WARN_ON(hid == NULL);
+       if (!hid)
+               return 0;
+
+       if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
+               dbg("Kicking head %d tail %d", usbhid->ctrlhead, 
usbhid->ctrltail);
+               if (hid_submit_ctrl(hid)) {
+                       clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+                       wake_up(&hid->wait);
+               }
+       }
+       return kicked;
+}
+
 /*
  * Input interrupt completion handler.
  */
@@ -181,12 +224,14 @@
 
        switch (urb->status) {
                case 0:                 /* success */
+                       set_bit(HID_BUSY, &usbhid->iofl);
                        usbhid->retry_delay = 0;
                        hid_input_report(urb->context, HID_INPUT_REPORT,
                                         urb->transfer_buffer,
                                         urb->actual_length, 1);
                        break;
                case -EPIPE:            /* stall */
+                       set_bit(HID_BUSY, &usbhid->iofl);
                        clear_bit(HID_IN_RUNNING, &usbhid->iofl);
                        set_bit(HID_CLEAR_HALT, &usbhid->iofl);
                        schedule_work(&usbhid->reset_work);
@@ -200,6 +245,7 @@
                case -EPROTO:           /* protocol error or unplug */
                case -ETIME:            /* protocol error or unplug */
                case -ETIMEDOUT:        /* Should never happen, but... */
+                       set_bit(HID_BUSY, &usbhid->iofl);
                        clear_bit(HID_IN_RUNNING, &usbhid->iofl);
                        hid_io_error(hid);
                        return;
@@ -242,9 +288,11 @@
        struct hid_report *report;
        struct usbhid_device *usbhid = hid->driver_data;
 
+       WARN_ON(usbhid == NULL);
        report = usbhid->out[usbhid->outtail];
-
+       WARN_ON(report == NULL);
        hid_output_report(report, usbhid->outbuf);
+       BUG_ON(usbhid->urbout == NULL);
        usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 
+ (report->id > 0);
        usbhid->urbout->dev = hid_to_usb_dev(hid);
 
@@ -332,24 +380,20 @@
                        warn("output irq status %d received", urb->status);
        }
 
-       spin_lock_irqsave(&usbhid->outlock, flags);
+       spin_lock_irqsave(&usbhid->outputlock, flags);
 
        if (unplug)
                usbhid->outtail = usbhid->outhead;
        else
                usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE 
- 1);
 
-       if (usbhid->outhead != usbhid->outtail) {
-               if (hid_submit_out(hid)) {
-                       clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
-                       wake_up(&hid->wait);
-               }
-               spin_unlock_irqrestore(&usbhid->outlock, flags);
+       if (usbhid_restart_out_queue(usbhid)) {
+               spin_unlock_irqrestore(&usbhid->outputlock, flags);
                return;
        }
 
        clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
-       spin_unlock_irqrestore(&usbhid->outlock, flags);
+       spin_unlock_irqrestore(&usbhid->outputlock, flags);
        wake_up(&hid->wait);
 }
 
@@ -364,7 +408,7 @@
        unsigned long flags;
        int unplug = 0;
 
-       spin_lock_irqsave(&usbhid->ctrllock, flags);
+       spin_lock_irqsave(&usbhid->outputlock, flags);
 
        switch (urb->status) {
                case 0:                 /* success */
@@ -389,24 +433,55 @@
        else
                usbhid->ctrltail = (usbhid->ctrltail + 1) & 
(HID_CONTROL_FIFO_SIZE - 1);
 
-       if (usbhid->ctrlhead != usbhid->ctrltail) {
-               if (hid_submit_ctrl(hid)) {
-                       clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-                       wake_up(&hid->wait);
-               }
-               spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+       if (usbhid_restart_ctrl_queue(usbhid)) {
+               spin_unlock_irqrestore(&usbhid->outputlock, flags);
                return;
        }
 
        clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-       spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+       spin_unlock_irqrestore(&usbhid->outputlock, flags);
        wake_up(&hid->wait);
 }
 
-void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, 
unsigned char dir)
+static int usbhid_queue_report_out(struct hid_device *hid, struct hid_report 
*report)
 {
+       struct usbhid_device *usbhid = hid->driver_data;
        int head;
-       unsigned long flags;
+
+       if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == 
usbhid->outtail) {
+               warn("output queue full");
+               return 0;
+       }
+
+       usbhid->out[usbhid->outhead] = report;
+       usbhid->outhead = head;
+
+       return usbhid->outhead < usbhid->outtail ?
+               usbhid->outtail - usbhid->outhead :
+               HID_OUTPUT_FIFO_SIZE - usbhid->outhead + usbhid->outtail;
+}
+
+static int usbhid_queue_report_ctrl(struct hid_device *hid, struct hid_report 
*report, unsigned char dir)
+{
+       struct usbhid_device *usbhid = hid->driver_data;
+       int head;
+
+       if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == 
usbhid->ctrltail) {
+               warn("control queue full");
+               return 0;
+       }
+
+       usbhid->ctrl[usbhid->ctrlhead].report = report;
+       usbhid->ctrl[usbhid->ctrlhead].dir = dir;
+       usbhid->ctrlhead = head;
+
+       return usbhid->ctrlhead < usbhid->ctrltail ?
+               usbhid->ctrltail - usbhid->ctrlhead :
+               HID_CONTROL_FIFO_SIZE - usbhid->ctrlhead + usbhid->ctrltail;
+}
+
+static void __usbhid_submit_report(struct hid_device *hid, struct hid_report 
*report, unsigned char dir)
+{
        struct usbhid_device *usbhid = hid->driver_data;
 
        if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
@@ -414,49 +489,53 @@
 
        if (usbhid->urbout && dir == USB_DIR_OUT && report->type == 
HID_OUTPUT_REPORT) {
 
-               spin_lock_irqsave(&usbhid->outlock, flags);
-
-               if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) 
== usbhid->outtail) {
-                       spin_unlock_irqrestore(&usbhid->outlock, flags);
-                       warn("output queue full");
-                       return;
-               }
-
-               usbhid->out[usbhid->outhead] = report;
-               usbhid->outhead = head;
+               usbhid_queue_report_out(hid, report);
 
                if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl))
                        if (hid_submit_out(hid))
                                clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
 
-               spin_unlock_irqrestore(&usbhid->outlock, flags);
-               return;
-       }
+       } else {
 
-       spin_lock_irqsave(&usbhid->ctrllock, flags);
+               usbhid_queue_report_ctrl(hid, report, dir);
+
+               if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+                       if (hid_submit_ctrl(hid))
+                               clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
 
-       if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == 
usbhid->ctrltail) {
-               spin_unlock_irqrestore(&usbhid->ctrllock, flags);
-               warn("control queue full");
-               return;
        }
+}
 
-       usbhid->ctrl[usbhid->ctrlhead].report = report;
-       usbhid->ctrl[usbhid->ctrlhead].dir = dir;
-       usbhid->ctrlhead = head;
+void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, 
unsigned char dir)
+{
+       struct usbhid_device *usbhid = hid->driver_data;
+       unsigned long flags;
 
-       if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
-               if (hid_submit_ctrl(hid))
-                       clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+       spin_lock_irqsave(&usbhid->outputlock, flags);
+       __usbhid_submit_report(hid, report, dir);
+       spin_unlock_irqrestore(&usbhid->outputlock, flags);
+}
 
-       spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+static int usbhid_queue_report(struct hid_device *hid, struct hid_report 
*report, unsigned char dir)
+{
+       struct usbhid_device *usbhid = hid->driver_data;
+       int rv;
+
+       if (usbhid->urbout && dir == USB_DIR_OUT && report->type == 
HID_OUTPUT_REPORT) {
+               rv = usbhid_queue_report_out(hid, report);
+       } else {
+               rv = usbhid_queue_report_ctrl(hid, report, dir);
+       }
+       return rv;
 }
 
 static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, 
unsigned int code, int value)
 {
        struct hid_device *hid = dev->private;
+       struct usbhid_device *usbhid = hid->driver_data;
        struct hid_field *field;
-       int offset;
+       unsigned long flags;
+       int offset, used;
 
        if (type == EV_FF)
                return input_ff_event(dev, type, code, value);
@@ -469,9 +548,21 @@
                return -1;
        }
 
+       spin_lock_irqsave(&usbhid->outputlock, flags);
+
        hid_set_field(field, offset, value);
-       usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+       if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
+               /* the device isn't idle, we do the output now*/
+               __usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+       } else {
+               /* the device is idle, queue the request
+               and wake it up if the queue fills up too much*/
+               used = usbhid_queue_report(hid, field->report, USB_DIR_OUT);
+               if ( 3 < HID_OUTPUT_FIFO_SIZE - used)
+                       schedule_work(&usbhid->restart_work);
 
+       }
+       spin_unlock_irqrestore(&usbhid->outputlock, flags);
        return 0;
 }
 
@@ -514,7 +605,23 @@
 
 int usbhid_open(struct hid_device *hid)
 {
-       ++hid->open;
+       struct usbhid_device *usbhid = hid->driver_data;
+       int res;
+
+       set_bit(HID_BUSY, &usbhid->iofl);
+       mutex_lock(&hid_open_mut);
+       if (!hid->open++) {
+               res = usb_autopm_get_interface(usbhid->intf);
+               /* this has a twofold purpose */
+               if (res < 0) {
+                       hid->open--;
+                       mutex_unlock(&hid_open_mut);
+                       return -EIO;
+               }
+               usbhid->intf->needs_remote_wakeup = 1;
+               usb_autopm_put_interface(usbhid->intf);
+       }
+       mutex_unlock(&hid_open_mut);
        if (hid_start_in(hid))
                hid_io_error(hid);
        return 0;
@@ -524,8 +631,15 @@
 {
        struct usbhid_device *usbhid = hid->driver_data;
 
-       if (!--hid->open)
+       mutex_lock(&hid_open_mut);
+       if (!--hid->open) {
                usb_kill_urb(usbhid->urbin);
+               flush_scheduled_work();
+               usbhid->intf->needs_remote_wakeup = 0;
+               if (!test_and_clear_bit(HID_REPORTED_IDLE, &usbhid->iofl))
+                       usb_autopm_put_interface(usbhid->intf);
+       }
+       mutex_unlock(&hid_open_mut);
 }
 
 static int hidinput_open(struct input_dev *dev)
@@ -552,14 +666,21 @@
 void usbhid_init_reports(struct hid_device *hid)
 {
        struct hid_report *report;
-       struct usbhid_device *usbhid = hid->driver_data;
+       unsigned long flags;
        int err, ret;
+       struct usbhid_device *usbhid = hid->driver_data;
 
-       list_for_each_entry(report, 
&hid->report_enum[HID_INPUT_REPORT].report_list, list)
-               usbhid_submit_report(hid, report, USB_DIR_IN);
+       list_for_each_entry(report, 
&hid->report_enum[HID_INPUT_REPORT].report_list, list) {
+               spin_lock_irqsave(&usbhid->outputlock, flags);
+               __usbhid_submit_report(hid, report, USB_DIR_IN);
+               spin_unlock_irqrestore(&usbhid->outputlock, flags);
+       }
 
-       list_for_each_entry(report, 
&hid->report_enum[HID_FEATURE_REPORT].report_list, list)
-               usbhid_submit_report(hid, report, USB_DIR_IN);
+       list_for_each_entry(report, 
&hid->report_enum[HID_FEATURE_REPORT].report_list, list) {
+               spin_lock_irqsave(&usbhid->outputlock, flags);
+               __usbhid_submit_report(hid, report, USB_DIR_IN);
+               spin_unlock_irqrestore(&usbhid->outputlock, flags);
+       }
 
        err = 0;
        ret = usbhid_wait_io(hid);
@@ -1005,6 +1126,41 @@
        return 0;
 }
 
+static void usbhid_restart_queues(struct usbhid_device *usbhid)
+{
+       if (usbhid->urbout)
+               usbhid_restart_out_queue(usbhid);
+       usbhid_restart_ctrl_queue(usbhid);
+}
+
+static void __usbhid_restart_queues(struct work_struct *work)
+{
+       struct usbhid_device *usbhid =
+               container_of(work, struct usbhid_device, restart_work);
+
+       usb_autopm_get_interface(usbhid->intf);
+       usbhid_restart_queues(usbhid);
+       usb_autopm_put_interface(usbhid->intf);
+}
+
+static int hid_check_busy(struct usb_interface *intf)
+{
+       struct hid_device *hid = usb_get_intfdata(intf);
+       struct usbhid_device *usbhid = hid->driver_data;
+       unsigned long flags;
+       int rv;
+
+       spin_lock_irqsave(&usbhid->outputlock, flags);
+       rv = test_and_clear_bit(HID_BUSY, &usbhid->iofl)
+               || test_bit(HID_OUT_RUNNING, &usbhid->iofl)
+               || test_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+       if (!rv)
+               set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
+       spin_unlock_irqrestore(&usbhid->outputlock, flags);
+printk(KERN_ERR"%s - reporting %d at %ld\n", __FUNCTION__, rv, jiffies);
+       return rv;
+}
+
 static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
 {
        struct usbhid_device *usbhid = hid->driver_data;
@@ -1183,17 +1339,18 @@
        init_waitqueue_head(&hid->wait);
 
        INIT_WORK(&usbhid->reset_work, hid_reset);
+       INIT_WORK(&usbhid->restart_work, __usbhid_restart_queues);
        setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
 
        spin_lock_init(&usbhid->inlock);
-       spin_lock_init(&usbhid->outlock);
-       spin_lock_init(&usbhid->ctrllock);
+       spin_lock_init(&usbhid->outputlock);
 
        hid->version = le16_to_cpu(hdesc->bcdHID);
        hid->country = hdesc->bCountryCode;
        hid->dev = &intf->dev;
        usbhid->intf = intf;
        usbhid->ifnum = interface->desc.bInterfaceNumber;
+       usbhid->idle_time = 10; //FIXME
 
        hid->name[0] = 0;
 
@@ -1299,6 +1456,8 @@
        if (!(hid = usb_hid_configure(intf)))
                return -ENODEV;
 
+       usb_set_intfdata(intf, hid);
+
        usbhid_init_reports(hid);
        hid_dump_device(hid);
 
@@ -1307,7 +1466,6 @@
        if (!hiddev_connect(hid))
                hid->claimed |= HID_CLAIMED_HIDDEV;
 
-       usb_set_intfdata(intf, hid);
 
        if (!hid->claimed) {
                printk ("HID device not claimed by input or hiddev\n");
@@ -1351,7 +1509,7 @@
 
 static int hid_suspend(struct usb_interface *intf, pm_message_t message)
 {
-       struct hid_device *hid = usb_get_intfdata (intf);
+       struct hid_device *hid = usb_get_intfdata(intf);
        struct usbhid_device *usbhid = hid->driver_data;
 
        spin_lock_irq(&usbhid->inlock); /* Sync with error handler */
@@ -1359,6 +1517,7 @@
        spin_unlock_irq(&usbhid->inlock);
        del_timer(&usbhid->io_retry);
        usb_kill_urb(usbhid->urbin);
+       flush_scheduled_work();
        dev_dbg(&intf->dev, "suspend\n");
        return 0;
 }
@@ -1370,8 +1529,10 @@
        int status;
 
        clear_bit(HID_SUSPENDED, &usbhid->iofl);
+       set_bit(HID_BUSY, &usbhid->iofl);
        usbhid->retry_delay = 0;
        status = hid_start_in(hid);
+       usbhid_restart_queues(usbhid);
        dev_dbg(&intf->dev, "resume status %d\n", status);
        return status;
 }
@@ -1409,7 +1570,9 @@
        .resume =       hid_resume,
        .pre_reset =    hid_pre_reset,
        .post_reset =   hid_post_reset,
+       .check_busy =   hid_check_busy,
        .id_table =     hid_usb_ids,
+       .supports_autosuspend = 1,
 };
 
 static int __init hid_init(void)
--- linux-2.6.20/drivers/usb/input/hiddev.c     2007-02-06 14:14:49.000000000 
+0100
+++ linux-stern/drivers/usb/input/hiddev.c      2007-02-15 11:24:17.000000000 
+0100
@@ -496,8 +496,11 @@
                if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
                        return -EINVAL;
 
+               if (usb_autopm_get_interface(usbhid->intf) < 0)
+                       return -EIO;
                usbhid_submit_report(hid, report, USB_DIR_IN);
                usbhid_wait_io(hid);
+               usb_autopm_put_interface(usbhid->intf);
 
                return 0;
 
@@ -511,8 +514,11 @@
                if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
                        return -EINVAL;
 
+               if (usb_autopm_get_interface(usbhid->intf) < 0)
+                       return -EIO;
                usbhid_submit_report(hid, report, USB_DIR_OUT);
                usbhid_wait_io(hid);
+               usb_autopm_put_interface(usbhid->intf);
 
                return 0;
 
--- linux-2.6.20/drivers/usb/core/driver.c      2007-02-06 14:14:48.000000000 
+0100
+++ linux-stern/drivers/usb/core/driver.c       2007-02-15 16:04:57.000000000 
+0100
@@ -807,7 +807,7 @@
 }
 EXPORT_SYMBOL_GPL_FUTURE(usb_deregister);
 
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM
 
 /* Caller has locked udev's pm_mutex */
 static int usb_suspend_device(struct usb_device *udev, pm_message_t msg)
@@ -942,21 +942,48 @@
 
 #ifdef CONFIG_USB_SUSPEND
 
+static int is_interface_busy(struct usb_interface *intf)
+{
+       struct usb_driver *drv = to_usb_driver(intf->dev.driver);
+
+       if (!drv)
+               return 0;
+       if (!drv->check_busy)
+               return 0;
+
+       return (drv->check_busy)(intf);
+}
+
 /* Internal routine to check whether we may autosuspend a device. */
-static int autosuspend_check(struct usb_device *udev)
+static int autosuspend_check(struct usb_device *udev, int starting)
 {
        int                     i;
+       unsigned                delay;
        struct usb_interface    *intf;
 
-       /* For autosuspend, fail fast if anything is in use.
-        * Also fail if any interfaces require remote wakeup but it
-        * isn't available. */
+       /* For autosuspend, fail fast if anything is in use or autosuspend
+        * is disabled.  Also fail if any interfaces require remote wakeup
+        * but it isn't available.
+        */
        udev->do_remote_wakeup = device_may_wakeup(&udev->dev);
        if (udev->pm_usage_cnt > 0)
                return -EBUSY;
+       if (!udev->autosuspend_delay)
+               return -EPERM;
+
+       /* starting is non-zero when the autosuspend timer is first started,
+        * in which case we should return the maximum delay needed.
+        * It is zero when the timer expires, in which case we should return
+        * 0 if it is okay to autosuspend now, otherwise the next timer
+        * delay value.
+        */
+       delay = (starting ? udev->autosuspend_delay : 0);
+
        if (udev->actconfig) {
                for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
                        intf = udev->actconfig->interface[i];
+                       if (!intf->autosuspend_delay)
+                               return -EPERM;
                        if (!is_active(intf))
                                continue;
                        if (intf->pm_usage_cnt > 0)
@@ -967,6 +994,29 @@
                                                "for autosuspend\n");
                                return -EOPNOTSUPP;
                        }
+                       if (starting || is_interface_busy(intf))
+                               delay = max(delay, intf->autosuspend_delay);
+               }
+       }
+       return delay;
+}
+
+/* Internal routine to check whether we need to autoresume a device. */
+static int autosuspend_recheck(struct usb_device *udev)
+{
+       int                     i;
+       struct usb_interface    *intf;
+
+       /* There's a race between the test of the USB_IF_DEVICE_BUSY flag
+        * above and the completion of an URB (when a driver would set the
+        * flag).  If the flag gets set after the decision to start an
+        * autosuspend has been made, we need to go back and autoresume
+        * the device. */
+       if (udev->actconfig) {
+               for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
+                       intf = udev->actconfig->interface[i];
+                       if (is_interface_busy(intf))
+                               return 1;
                }
        }
        return 0;
@@ -974,9 +1024,9 @@
 
 #else
 
-#define autosuspend_check(udev)                0
+#define autosuspend_check(udev, starting)      0
 
-#endif
+#endif /* CONFIG_USB_SUSPEND */
 
 /**
  * usb_suspend_both - suspend a USB device and its interfaces
@@ -1030,9 +1080,15 @@
        udev->do_remote_wakeup = device_may_wakeup(&udev->dev);
 
        if (udev->auto_pm) {
-               status = autosuspend_check(udev);
-               if (status < 0)
-                       return status;
+               int     delay = autosuspend_check(udev, 0);
+
+               if (delay < 0)
+                       return delay;
+               if (delay > 0) {
+                       queue_delayed_work(ksuspend_usb_wq,
+                                       &udev->autosuspend, delay);
+                       return -EBUSY;
+               }
        }
 
        /* Suspend all the interfaces and then udev itself */
@@ -1152,7 +1208,21 @@
        return status;
 }
 
-#ifdef CONFIG_USB_SUSPEND
+#ifdef CONFIG_USB_SUSPEND
+
+/* usb_autosuspend_work - callback routine to autosuspend a USB device */
+void usb_autosuspend_work(struct work_struct *work)
+{
+       struct usb_device *udev =
+               container_of(work, struct usb_device, autosuspend.work);
+
+       usb_pm_lock(udev);
+       udev->auto_pm = 1;
+       usb_suspend_both(udev, PMSG_SUSPEND);
+       if (autosuspend_recheck(udev))
+               usb_resume_both(udev);
+       usb_pm_unlock(udev);
+}
 
 /* Internal routine to adjust a device's usage counter and change
  * its autosuspend state.
@@ -1169,9 +1239,13 @@
                status = usb_resume_both(udev);
                if (status != 0)
                        udev->pm_usage_cnt -= inc_usage_cnt;
-       } else if (inc_usage_cnt <= 0 && autosuspend_check(udev) == 0)
-               queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend,
-                               USB_AUTOSUSPEND_DELAY);
+       } else if (inc_usage_cnt <= 0) {
+               int     delay = autosuspend_check(udev, 1);
+
+               if (delay > 0)
+                       queue_delayed_work(ksuspend_usb_wq,
+                                       &udev->autosuspend, delay);
+       }
        usb_pm_unlock(udev);
        return status;
 }
@@ -1206,6 +1280,26 @@
 }
 
 /**
+ * usb_try_autosuspend_device - attempt an autosuspend of a USB device and its 
interfaces
+ * @udev: the usb_device to autosuspend
+ *
+ * This routine should be called when a core subsystem thinks @udev may
+ * be ready to autosuspend.
+ *
+ * @udev's usage counter left unchanged.  If it or any of the usage counters
+ * for an active interface is greater than 0, or autosuspend is not allowed
+ * for any other reason, no autosuspend request will be queued.
+ *
+ * This routine can run only in process context.
+ */
+void usb_try_autosuspend_device(struct usb_device *udev)
+{
+       usb_autopm_do_device(udev, 0);
+       // dev_dbg(&udev->dev, "%s: cnt %d\n",
+       //              __FUNCTION__, udev->pm_usage_cnt);
+}
+
+/**
  * usb_autoresume_device - immediately autoresume a USB device and its 
interfaces
  * @udev: the usb_device to autoresume
  *
@@ -1253,9 +1347,13 @@
                        status = usb_resume_both(udev);
                        if (status != 0)
                                intf->pm_usage_cnt -= inc_usage_cnt;
-               } else if (inc_usage_cnt <= 0 && autosuspend_check(udev) == 0)
-                       queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend,
-                                       USB_AUTOSUSPEND_DELAY);
+               } else if (inc_usage_cnt <= 0) {
+                       int delay = autosuspend_check(udev, 1);
+
+                       if (delay > 0)
+                               queue_delayed_work(ksuspend_usb_wq,
+                                               &udev->autosuspend, delay);
+               }
        }
        usb_pm_unlock(udev);
        return status;
@@ -1366,7 +1464,12 @@
 }
 EXPORT_SYMBOL_GPL(usb_autopm_set_interface);
 
-#endif /* CONFIG_USB_SUSPEND */
+#else
+
+static void usb_autosuspend_work(struct work_struct *work)
+{}
+
+#endif /* CONFIG_USB_SUSPEND */
 
 static int usb_suspend(struct device *dev, pm_message_t message)
 {
@@ -1402,7 +1505,7 @@
        return status;
 }
 
-#endif /* CONFIG_PM */
+#endif /* CONFIG_PM */
 
 struct bus_type usb_bus_type = {
        .name =         "usb",
--- linux-2.6.20/drivers/usb/core/sysfs.c       2007-02-06 14:13:49.000000000 
+0100
+++ linux-stern/drivers/usb/core/sysfs.c        2007-02-15 10:57:23.000000000 
+0100
@@ -148,6 +148,130 @@
 }
 static DEVICE_ATTR(maxchild, S_IRUGO, show_maxchild, NULL);
 
+#ifdef CONFIG_USB_SUSPEND
+
+static ssize_t
+show_autosuspend(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       unsigned value;
+
+       if (is_usb_device(dev)) {
+               struct usb_device *udev = to_usb_device(dev);
+
+               value = udev->autosuspend_delay;
+       } else {
+               struct usb_interface *intf = to_usb_interface(dev);
+
+               value = intf->autosuspend_delay;
+       }
+       return sprintf(buf, "%u\n", value / HZ);
+}
+
+static ssize_t
+set_autosuspend(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct usb_device *udev;
+       unsigned value, old;
+
+       if (sscanf(buf, "%u", &value) != 1 || value >= INT_MAX/HZ)
+               return -EINVAL;
+       value *= HZ;
+
+       if (is_usb_device(dev)) {
+               udev = to_usb_device(dev);
+               old = udev->autosuspend_delay;
+               udev->autosuspend_delay = value;
+       } else {
+               struct usb_interface *intf = to_usb_interface(dev);
+
+               udev = interface_to_usbdev(intf);
+               old = intf->autosuspend_delay;
+               intf->autosuspend_delay = value;
+       }
+       if (value > 0 && old == 0)
+               usb_try_autosuspend_device(udev);
+
+       return count;
+}
+
+static DEVICE_ATTR(autosuspend, S_IRUGO | S_IWUSR,
+               show_autosuspend, set_autosuspend);
+
+static ssize_t
+show_suspended(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct usb_device *udev = to_usb_device(dev);
+
+       return sprintf(buf, "%d\n", (udev->state == USB_STATE_SUSPENDED));
+}
+
+static ssize_t
+set_suspended(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct usb_device *udev = to_usb_device(dev);
+       unsigned value;
+       int rc;
+
+       if (sscanf(buf, "%u", &value) != 1 || value > 1)
+               return -EINVAL;
+       usb_lock_device(udev);
+       if (value)
+               rc = usb_bus_type.suspend(dev, PMSG_SUSPEND);
+       else {
+               rc = usb_bus_type.resume(dev);
+
+               /* Give something a chance to happen,
+                * then autosuspend the device again. */
+               if (rc == 0)
+                       usb_try_autosuspend_device(udev);
+       }
+       usb_unlock_device(udev);
+       return (rc < 0) ? rc : count;
+}
+
+static DEVICE_ATTR(suspended, S_IRUGO | S_IWUSR,
+               show_suspended, set_suspended);
+
+static char power_group[] = "power";
+
+extern int sysfs_add_file_to_group(struct kobject *kobj,
+               const struct attribute *attr, const char *group);
+extern void sysfs_remove_file_from_group(struct kobject *kobj,
+               const struct attribute *attr, const char *group);
+
+static int add_power_attributes(struct device *dev)
+{
+       int rc;
+
+       rc = sysfs_add_file_to_group(&dev->kobj,
+                       &dev_attr_autosuspend.attr,
+                       power_group);
+       if (rc == 0 && is_usb_device(dev))
+               rc = sysfs_add_file_to_group(&dev->kobj,
+                               &dev_attr_suspended.attr,
+                               power_group);
+       return rc;
+}
+
+static void remove_power_attributes(struct device *dev)
+{
+       sysfs_remove_file_from_group(&dev->kobj,
+                       &dev_attr_suspended.attr,
+                       power_group);
+       sysfs_remove_file_from_group(&dev->kobj,
+                       &dev_attr_autosuspend.attr,
+                       power_group);
+}
+
+#else
+
+#define add_power_attributes(dev)      0
+#define remove_power_attributes(dev)   do {} while (0)
+
+#endif /* CONFIG_USB_SUSPEND */
+
 /* Descriptor fields */
 #define usb_descriptor_attr_le16(field, format_string)                 \
 static ssize_t                                                         \
@@ -219,6 +343,10 @@
        if (retval)
                return retval;
 
+       retval = add_power_attributes(dev);
+       if (retval)
+               goto error;
+
        if (udev->manufacturer) {
                retval = device_create_file (dev, &dev_attr_manufacturer);
                if (retval)
@@ -240,6 +368,9 @@
        return 0;
 error:
        usb_remove_ep_files(&udev->ep0);
+       remove_power_attributes(dev);
+       sysfs_remove_group(&dev->kobj, &dev_attr_grp);
+
        device_remove_file(dev, &dev_attr_manufacturer);
        device_remove_file(dev, &dev_attr_product);
        device_remove_file(dev, &dev_attr_serial);
@@ -251,6 +382,7 @@
        struct device *dev = &udev->dev;
 
        usb_remove_ep_files(&udev->ep0);
+       remove_power_attributes(dev);
        sysfs_remove_group(&dev->kobj, &dev_attr_grp);
 
        if (udev->manufacturer)
@@ -362,33 +494,42 @@
 
 int usb_create_sysfs_intf_files(struct usb_interface *intf)
 {
+       struct device *dev = &intf->dev;
        struct usb_device *udev = interface_to_usbdev(intf);
        struct usb_host_interface *alt = intf->cur_altsetting;
        int retval;
 
-       retval = sysfs_create_group(&intf->dev.kobj, &intf_attr_grp);
+       retval = sysfs_create_group(&dev->kobj, &intf_attr_grp);
+       if (retval)
+               goto error;
+
+       retval = add_power_attributes(dev);
        if (retval)
                goto error;
 
        if (alt->string == NULL)
                alt->string = usb_cache_string(udev, alt->desc.iInterface);
        if (alt->string)
-               retval = device_create_file(&intf->dev, &dev_attr_interface);
+               retval = device_create_file(dev, &dev_attr_interface);
        usb_create_intf_ep_files(intf, udev);
        return 0;
 error:
        if (alt->string)
-               device_remove_file(&intf->dev, &dev_attr_interface);
-       sysfs_remove_group(&intf->dev.kobj, &intf_attr_grp);
+               device_remove_file(dev, &dev_attr_interface);
+       remove_power_attributes(dev);
+       sysfs_remove_group(&dev->kobj, &intf_attr_grp);
        usb_remove_intf_ep_files(intf);
        return retval;
 }
 
 void usb_remove_sysfs_intf_files (struct usb_interface *intf)
 {
+       struct device *dev = &intf->dev;
+
        usb_remove_intf_ep_files(intf);
-       sysfs_remove_group(&intf->dev.kobj, &intf_attr_grp);
+       remove_power_attributes(dev);
+       sysfs_remove_group(&dev->kobj, &intf_attr_grp);
 
        if (intf->cur_altsetting->string)
-               device_remove_file(&intf->dev, &dev_attr_interface);
+               device_remove_file(dev, &dev_attr_interface);
 }
--- linux-2.6.20/include/linux/usb.h    2007-02-06 14:14:57.000000000 +0100
+++ linux-stern/include/linux/usb.h     2007-02-15 13:41:21.000000000 +0100
@@ -110,6 +110,9 @@
  * @class_dev: driver model's class view of this device.
  * @pm_usage_cnt: PM usage counter for this interface; autosuspend is not
  *     allowed unless the counter is 0.
+ * @autosuspend_delay: time delay required by this interface to insure
+ *     that the device is idle and can be autosuspended.  0 means
+ *     don't autosuspend.
  *
  * USB device drivers attach to interfaces on a physical device.  Each
  * interface encapsulates a single high level function, such as feeding
@@ -153,7 +156,11 @@
 
        struct device dev;              /* interface specific device info */
        struct class_device *class_dev;
+
+#ifdef CONFIG_PM
        int pm_usage_cnt;               /* usage counter for autosuspend */
+       unsigned autosuspend_delay;     /* in jiffies */
+#endif
 };
 #define        to_usb_interface(d) container_of(d, struct usb_interface, dev)
 #define        interface_to_usbdev(intf) \
@@ -390,6 +397,7 @@
 #ifdef CONFIG_PM
        struct delayed_work autosuspend; /* for delayed autosuspends */
        struct mutex pm_mutex;          /* protects PM operations */
+       unsigned autosuspend_delay;     /* in jiffies */
 
        unsigned auto_pm:1;             /* autosuspend/resume in progress */
        unsigned do_remote_wakeup:1;    /* remote wakeup should be enabled */
@@ -804,6 +812,8 @@
        void (*pre_reset) (struct usb_interface *intf);
        void (*post_reset) (struct usb_interface *intf);
 
+       int (*check_busy) (struct usb_interface *intf);
+
        const struct usb_device_id *id_table;
 
        struct usb_dynids dynids;

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to