Analysis of USB suspend/resume source code under Linux

Analysis of USB suspend/resume source code under Linux

Author:aaron

 

This article mainly uses an example of a USB driver developed by myself to explain in depth how the linux kernel supports the sleep and wake-up of USB devices.

Recently, I am writing a driver under linux for a module of our company. One of them is to support the USB sleep and wake-up problem. In fact, the support of the linux kernel for this function of USB is relatively new, that is, in recent years. .

 

1 Turn on/off the USB suspend/resuem function

To make linux support usb suspend/resuem, of course, the code of this function in the kernel must be compiled into it, that is, the support for this function must be turned on when make menuconfig.

The first item to open is CONFIG_PM, which is the power management of the entire system. USB suspend/resuem is just a self-system of the entire power management. Only when this function is turned on can this feature of USB be used.

The second thing to turn on is the USB's own switch CONFIG_USB_SUSPEND. That is, after turning on this function, we can make our device sleep by simply calling the function interface provided by the USB core in our own driver.

 

Two source code analysis

The USB automatic hibernation function is not supported in the code before 2.6.19. It can only hibernate the USB device when the host is hibernating. So if we want to make our device hibernate when it is not in use We have to add the corresponding code by ourselves. Fortunately, we do not need to add complex code to achieve this purpose, because the USB core provides several interfaces that can be directly called by our driver to put our device into sleep state.

Let's take the code of 2.6.16 as an example to analyze how the USB device goes to sleep.

Drivers/usr/core/hub.c:

int usb_suspend_device(struct usb_device *udev)

{

#ifdef      CONFIG_USB_SUSPEND

       if (udev->state == USB_STATE_NOTATTACHED)

              return -ENODEV;

       return __usb_suspend_device(udev, udev->portnum);

#else

       /* NOTE:  udev->state unchanged, it's not lying ... */

       udev->dev.power.power_state = PMSG_SUSPEND;

       return 0;

#endif

}

Yes, as long as we call this function in the appropriate place in our driver, we can make our device sleep. But it should be noted that the kernel does not have the EXPORT function, so if our driver is to be compiled into a module, we only have Modify the kernel to EXPORT this function.

In fact, the real function is __usb_suspend_device

Drivers/usr/core/hub.c:

static int __usb_suspend_device (struct usb_device *udev, int port1)

{

       int   status = 0;

 

       /* caller owns the udev device lock */

       if (port1 < 0)

              return port1;

    /* Return directly if the device is dormant or not attached*/

       if (udev->state == USB_STATE_SUSPENDED

                     || udev->state == USB_STATE_NOTATTACHED) {

              return 0;

       }

 

       /* all interfaces must already be suspended */

    /*To sleep the device, first of all, every interface under the device must be able to sleep */

       if (udev->actconfig) {

              int   i;

 

              for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {

                     struct usb_interface       *intf;

 

                     intf = udev->actconfig->interface[i];

                     if (is_active(intf)) { /*Cannot sleep if an interface is active*/

                            dev_dbg(&intf->dev, "nyet suspended/n");

                            return -EBUSY;

                     }

              }

       }

 

       /* we only change a device's upstream USB link.

        * root hubs have no upstream USB link.

        */

    /* A function that does business */

       if (udev->parent)

              status = hub_port_suspend(hdev_to_hub(udev->parent), port1,

                            udev); 

 

       if (status == 0)

              udev->dev.power.power_state = PMSG_SUSPEND; /*Save device state*/

       return status;

}

The first parameter is the USB device to sleep, and the second parameter is a port of the hub to which the USB device is connected.

From this function, we can roughly guess that the principle of wanting a device to sleep is to sleep the port to which the device is attached.

Yes, the USB spec stipulates that as long as the port on which the device is attached is disabled, there will be no transmission on this path. After a period of time, the device should generate a suspend interrupt to put the device into sleep. condition.

Drivers/usr/core/hub.c:

static int hub_port_suspend(struct usb_hub *hub, int port1,

              struct usb_device *udev)

{

       int   status;

 

       // dev_dbg(hub->intfdev, "suspend port %d/n", port1);

 

       /* enable remote wakeup when appropriate; this lets the device

        * wake up the upstream hub (including maybe the root hub).

        *

        * NOTE:  OTG devices may issue remote wakeup (or SRP) even when

        * we don't explicitly enable it here.

        */

/*If the device supports the remote wakeup function, turn on this function*/

       if (device_may_wakeup(&udev->dev)) {

              status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),

                            USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,

                            USB_DEVICE_REMOTE_WAKEUP, 0,

                            NULL, 0,

                            USB_CTRL_SET_TIMEOUT);

              if (status)

                     dev_dbg(&udev->dev,

                            "won't remote wakeup, status %d/n",

                            status);

       }

 

       /* see 7.1.7.6 */

   /*^_^ Look at usb spec 7.1.7.6, this command is to disable the port of the hub, so as to achieve the purpose of hibernation*/

       status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND);

       if (status) {

              dev_dbg(hub->intfdev,

                     "can't suspend port %d, status %d/n",

                     port1, status);

              /* paranoia:  "should not happen" */

              (void) usb_control_msg(udev, usb_sndctrlpipe(udev, 0),

                            USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,

                            USB_DEVICE_REMOTE_WAKEUP, 0,

                            NULL, 0,

                            USB_CTRL_SET_TIMEOUT);

       } else {

              /* device has up to 10 msec to fully suspend */

              dev_dbg(&udev->dev, "usb suspend/n");

              usb_set_device_state(udev, USB_STATE_SUSPENDED); /*Set device state*/

              msleep(10);

       }

       return status;

}

OK, it is very simple to disable the port on the USB device attach, and the purpose of hibernation can be achieved (of course, in fact, it only stops the transmission on the link where the port is located, as for whether the device will really sleep depends on it. Considering the device itself, under normal circumstances, the device hardware will detect whether there is a transmission on the bus, and if not, the device will be transferred to the suspend state).

OK, understand how the USB device sleeps, then it is very clear how the USB device wakes up, just re-enable the port. I won't analyze it here.

Therefore, if we say that our device needs to prohibit sleep after opening, and allow sleep after closing, what should we do? Oh, just resume the device in the open function of the driver, and suspend the device in the close function.

As for the version after 2.6.19, the function of automatic sleep has been added. For the specific implementation principle, please refer to <<HUB of Linux Things>> written by fudan_abc

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325565670&siteId=291194637