Thread (6 messages) flat view 6 messages, 2 authors, 2012-09-17

Re: [RFC PATCH 3/3] usbnet: support runtime PM triggered by link change

From: Ming Lei <hidden>
Date: 2012-09-17 13:07:24

On Mon, Sep 17, 2012 at 4:50 PM, Oliver Neukum [off-list ref] wrote:
On Sunday 16 September 2012 01:48:19 Ming Lei wrote:

quoted
+void usbnet_link_updated(struct usbnet *dev)
+{
+     complete(&dev->link_update_completion);
+}
+EXPORT_SYMBOL_GPL(usbnet_link_updated);
Isn't that a bit too trivial to get the _GPL version?
OK, will use the non GPL version.
quoted
+#define usbnet_link_suspend(dev) do { \
+     dev_dbg(&dev->intf->dev, "%s:link suspend", __func__); \
+     usb_autopm_put_interface_async(dev->intf); \
+} while(0)
+
+#define usbnet_link_resume(dev) do { \
+     dev_dbg(&dev->intf->dev, "%s:link resume", __func__); \
+     usb_autopm_get_interface_async(dev->intf); \
+} while(0)
Why macros?
Just for easy debug by dumping the caller function name.

[..]
quoted
+/* called by usbnet_open */
+static void enable_link_runtime_pm(struct usbnet *dev)
+{
+     dev->link_rpm_enabled = 1;
+
+     if (!dev->link_remote_wakeup) {
+             dev->old_autosuspend_delay =
+                     dev->udev->dev.power.autosuspend_delay;
+             pm_runtime_set_autosuspend_delay(&dev->udev->dev, 1);
This is a problem. Suppose the user changes the autosuspend timeout.
You cannot assume that the old value remains valid.
Good catch, I will fix it in -v1 by reading again the timeout value in
disable_link_runtime_pm().
quoted
+     }
+
+     if (!netif_carrier_ok(dev->net)) {
+             dev->link_open_suspend = 1;
+             usbnet_link_suspend(dev);
+     }
+}
quoted
+static void update_link_state(struct usbnet *dev)
+{
+     char            *buf = NULL;
+     unsigned        pipe = 0;
+     unsigned        maxp;
+     int             ret, act_len, timeout;
+     struct urb      urb;
+
+     pipe = usb_rcvintpipe(dev->udev,
+                           dev->status->desc.bEndpointAddress
+                             & USB_ENDPOINT_NUMBER_MASK);
+     maxp = usb_maxpacket(dev->udev, pipe, 0);
+
+     /*
+      * Take default timeout as 2 times of period.
+      * It is observed that asix device can update its link
+      * state duing one period(128ms). Low level driver can set
+      * its default update link time in bind() callback.
+      */
+     if (!dev->link_update_timeout) {
+             timeout = max((int) dev->status->desc.bInterval,
+                     (dev->udev->speed == USB_SPEED_HIGH) ? 7 : 3);
+             timeout = 1 << timeout;
+             if (dev->udev->speed == USB_SPEED_HIGH)
+                     timeout /= 8;
+             if (timeout < 128)
+                     timeout = 128;
+     } else
+             timeout = dev->link_update_timeout;
+
+     buf = kmalloc(maxp, GFP_KERNEL);
+     if (!buf)
+             return;
+
+     dev_dbg(&dev->intf->dev, "%s: timeout %dms\n", __func__, timeout);
+     ret = usb_interrupt_msg(dev->udev, pipe, buf, maxp,
+                     &act_len, timeout);
+     if (!ret) {
+             urb.status = 0;
+             urb.actual_length = act_len;
+             urb.transfer_buffer = buf;
+             urb.transfer_buffer_length = maxp;
+             dev->driver_info->status(dev, &urb);
+             if (dev->driver_info->flags &
+                 FLAG_LINK_UPDATE_BY_DRIVER)
+                     wait_for_completion(&dev->link_update_completion);
If a driver calls usbnet_link_updated() from the same workqueue this
will deadlock.
Good catch, I will fix it in -v1 by using system_freezable_wq.
quoted
+             dev_dbg(&dev->intf->dev, "%s: link updated\n", __func__);
+     } else
+             dev_dbg(&dev->intf->dev, "%s: link update failed %d\n",
+                             __func__, ret);
+     kfree(buf);
+}
[..]
quoted
@@ -795,6 +977,9 @@ int usbnet_open (struct net_device *net)
              if (retval < 0)
                      goto done_manage_power_error;
              usb_autopm_put_interface(dev->intf);
+     } else {
+             if (need_link_runtime_pm(dev))
+                     enable_link_runtime_pm(dev);
      }
      return retval;
@@ -1489,6 +1674,8 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
      if (dev->driver_info->flags & FLAG_LINK_INTR)
              usbnet_link_change(dev, 0, 0);

+     init_link_rpm(dev);
+
      return 0;

 out4:
@@ -1538,6 +1725,9 @@ int usbnet_suspend (struct usb_interface *intf, pm_message_t message)
               * wake the device
               */
              netif_device_attach (dev->net);
+
+             if (PMSG_IS_AUTO(message))
+                     start_link_detect(dev);
What happens if the device is autosuspended, then the system is suspended
and the work is executed while the suspension is underway?
IMO we can avoid the problem by scheduling 'link_detect_work' on the
workqueue of 'system_freezable_wq', which will be introduced in -v1.
quoted
      }
      return 0;
 }
@@ -1552,8 +1742,10 @@ int usbnet_resume (struct usb_interface *intf)

      if (!--dev->suspend_count) {
              /* resume interrupt URBs */
-             if (dev->interrupt && test_bit(EVENT_DEV_OPEN, &dev->flags))
-                     usb_submit_urb(dev->interrupt, GFP_NOIO);
+             if (dev->interrupt && test_bit(EVENT_DEV_OPEN, &dev->flags)) {
+                     if (!dev->link_checking)
That is impossible. If the device is resumed the interrupt URB must
be restarted in every case (if it exists).
The interrupt URB will be submitted later in link_detect_work() under
the situation of being resumed by link detect work.
You cannot assume that its only function is checking the link state.
And it introduces a race with the workqueue.
Looks no race because usbnet_resume() will be run in workqueue
task context under the situation of being resumed by link detect work.


Thanks,
--
Ming Lei
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help