Oliver:

This patch includes the various facilities we have been discussing.  It 
seems to work okay.

Obviously it isn't in final form.  It needs to be cleaned up and broken 
apart into several simpler patches.  But you ought to be able to test it 
easily enough.  It needs to go on top of as853, posted a few minutes ago.

Alan Stern



Index: usb-2.6/fs/sysfs/file.c
===================================================================
--- usb-2.6.orig/fs/sysfs/file.c
+++ usb-2.6/fs/sysfs/file.c
@@ -516,6 +516,30 @@ int sysfs_create_file(struct kobject * k
 
 
 /**
+ * sysfs_add_file_to_group - add an attribute file to a pre-existing group.
+ * @kobj: object we're acting for.
+ * @attr: attribute descriptor.
+ * @group: group name.
+ */
+int sysfs_add_file_to_group(struct kobject *kobj,
+               const struct attribute *attr, const char *group)
+{
+       struct dentry *dir;
+       int error;
+
+       dir = lookup_one_len(group, kobj->dentry, strlen(group));
+       if (IS_ERR(dir))
+               error = PTR_ERR(dir);
+       else {
+               error = sysfs_add_file(dir, attr, SYSFS_KOBJ_ATTR);
+               dput(dir);
+       }
+       return error;
+}
+EXPORT_SYMBOL_GPL(sysfs_add_file_to_group);
+
+
+/**
  * sysfs_update_file - update the modified timestamp on an object attribute.
  * @kobj: object we're acting for.
  * @attr: attribute descriptor.
@@ -600,6 +624,26 @@ void sysfs_remove_file(struct kobject * 
 }
 
 
+/**
+ * sysfs_remove_file_from_group - remove an attribute file from a group.
+ * @kobj: object we're acting for.
+ * @attr: attribute descriptor.
+ * @group: group name.
+ */
+void sysfs_remove_file_from_group(struct kobject *kobj,
+               const struct attribute *attr, const char *group)
+{
+       struct dentry *dir;
+
+       dir = lookup_one_len(group, kobj->dentry, strlen(group));
+       if (!IS_ERR(dir)) {
+               sysfs_hash_and_remove(dir, attr->name);
+               dput(dir);
+       }
+}
+EXPORT_SYMBOL_GPL(sysfs_remove_file_from_group);
+
+
 EXPORT_SYMBOL_GPL(sysfs_create_file);
 EXPORT_SYMBOL_GPL(sysfs_remove_file);
 EXPORT_SYMBOL_GPL(sysfs_update_file);
Index: usb-2.6/drivers/usb/core/usb.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/usb.c
+++ usb-2.6/drivers/usb/core/usb.c
@@ -22,6 +22,7 @@
  */
 
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/string.h>
 #include <linux/bitops.h>
 #include <linux/slab.h>
@@ -50,6 +51,13 @@ static int nousb;    /* Disable USB when bu
 
 struct workqueue_struct *ksuspend_usb_wq;      /* For autosuspend */
 
+#ifdef CONFIG_USB_SUSPEND
+static int usb_autosuspend_delay = 2;          /* Default delay value,
+                                                * in seconds */
+module_param_named(autosuspend, usb_autosuspend_delay, uint, 0644);
+MODULE_PARM_DESC(autosuspend, "default autosuspend delay");
+#endif
+
 
 /**
  * usb_ifnum_to_if - get the interface object with a given interface number
@@ -306,6 +314,7 @@ usb_alloc_dev(struct usb_device *parent,
 #ifdef CONFIG_PM
        mutex_init(&dev->pm_mutex);
        INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work);
+       dev->autosuspend_delay = usb_autosuspend_delay * HZ;
 #endif
        return dev;
 }
Index: usb-2.6/include/linux/usb.h
===================================================================
--- usb-2.6.orig/include/linux/usb.h
+++ usb-2.6/include/linux/usb.h
@@ -111,6 +111,9 @@ enum usb_interface_condition {
  *     to the sysfs representation for that 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
@@ -154,7 +157,13 @@ struct usb_interface {
 
        struct device dev;              /* interface specific device info */
        struct device *usb_dev;         /* pointer to the usb class's device, 
if any */
+       unsigned long iflags;           /* interface atomic bitflags */
+#define USB_IF_DEVICE_BUSY     0
+
+#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) \
@@ -389,10 +398,13 @@ struct usb_device {
 
        int pm_usage_cnt;               /* usage counter for autosuspend */
        u32 quirks;                     /* quirks of the whole device */
+
 #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 */
 #endif
Index: usb-2.6/drivers/usb/core/driver.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/driver.c
+++ usb-2.6/drivers/usb/core/driver.c
@@ -957,20 +957,35 @@ done:
 #ifdef CONFIG_USB_SUSPEND
 
 /* 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)
@@ -981,14 +996,17 @@ static int autosuspend_check(struct usb_
                                                "for autosuspend\n");
                                return -EOPNOTSUPP;
                        }
+                       if (test_and_clear_bit(USB_IF_DEVICE_BUSY,
+                                       &intf->iflags) || starting)
+                               delay = max(delay, intf->autosuspend_delay);
                }
        }
-       return 0;
+       return delay;
 }
 
 #else
 
-#define autosuspend_check(udev)                0
+#define autosuspend_check(udev, starting)      0
 
 #endif
 
@@ -1044,9 +1062,15 @@ int usb_suspend_both(struct usb_device *
        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 */
@@ -1183,9 +1207,13 @@ static int usb_autopm_do_device(struct u
                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;
 }
@@ -1220,6 +1248,26 @@ void usb_autosuspend_device(struct usb_d
 }
 
 /**
+ * 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
  *
@@ -1267,9 +1315,13 @@ static int usb_autopm_do_interface(struc
                        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;
Index: usb-2.6/drivers/usb/core/sysfs.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/sysfs.c
+++ usb-2.6/drivers/usb/core/sysfs.c
@@ -158,6 +158,130 @@ show_quirks(struct device *dev, struct d
 }
 static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, 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_autoresume_device(udev);
+
+               /* Give something a chance to happen,
+                * then autosuspend the device again. */
+               if (rc == 0)
+                       usb_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                                                         \
@@ -230,6 +354,10 @@ int usb_create_sysfs_dev_files(struct us
        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)
@@ -251,6 +379,9 @@ int usb_create_sysfs_dev_files(struct us
        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);
@@ -262,6 +393,7 @@ void usb_remove_sysfs_dev_files(struct u
        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)
@@ -373,33 +505,41 @@ static inline void usb_remove_intf_ep_fi
 
 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);
 }
Index: usb-2.6/drivers/usb/core/usb.h
===================================================================
--- usb-2.6.orig/drivers/usb/core/usb.h
+++ usb-2.6/drivers/usb/core/usb.h
@@ -65,14 +65,14 @@ static inline void usb_pm_unlock(struct 
 
 #ifdef CONFIG_USB_SUSPEND
 
-#define USB_AUTOSUSPEND_DELAY  (HZ*2)
-
 extern void usb_autosuspend_device(struct usb_device *udev);
+extern void usb_try_autosuspend_device(struct usb_device *udev);
 extern int usb_autoresume_device(struct usb_device *udev);
 
 #else
 
-#define usb_autosuspend_device(udev)   do {} while (0)
+#define usb_autosuspend_device(udev)           do {} while (0)
+#define usb_try_autosuspend_device(udev)       do {} while (0)
 static inline int usb_autoresume_device(struct usb_device *udev)
 {
        return 0;
Index: usb-2.6/drivers/usb/core/quirks.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/quirks.c
+++ usb-2.6/drivers/usb/core/quirks.c
@@ -45,8 +45,8 @@ static const struct usb_device_id usb_qu
 
 static inline void usb_autosuspend_quirk(struct usb_device *udev)
 {
-       /* unbalanced resume to prevent autosuspends */
-       usb_autoresume_device(udev);
+       /* disable autosuspend, but allow the user to re-enable it via sysfs */
+       udev->autosuspend_delay = 0;
 }
 
 static const struct usb_device_id *find_id(struct usb_device *udev)
Index: usb-2.6/drivers/usb/core/message.c
===================================================================
--- usb-2.6.orig/drivers/usb/core/message.c
+++ usb-2.6/drivers/usb/core/message.c
@@ -1477,6 +1477,9 @@ free_interfaces:
                intf->dev.release = release_interface;
                device_initialize (&intf->dev);
                mark_quiesced(intf);
+#ifdef CONFIG_PM
+               intf->autosuspend_delay = HZ;
+#endif
                sprintf (&intf->dev.bus_id[0], "%d-%s:%d.%d",
                         dev->bus->busnum, dev->devpath,
                         configuration, alt->desc.bInterfaceNumber);


-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier.
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to