📄 hub.c
字号:
* - suspend, disconnect ... VBUS lost * * Once VBUS drop breaks the circuit, the port it's using has to go through * normal re-enumeration procedures, starting with enabling VBUS power. * Other than re-initializing the hub (plug/unplug, except for root hubs), * Linux (2.6) currently has NO mechanisms to initiate that: no khubd * timer, no SRP, no requests through sysfs. */static int __usb_suspend_device (struct usb_device *udev, int port, u32 state){ int status; if (port < 0) return port; /* NOTE: udev->serialize released on all real returns! */ if (state <= udev->dev.power.power_state || state < PM_SUSPEND_MEM || udev->state == USB_STATE_SUSPENDED || udev->state == USB_STATE_NOTATTACHED) { up(&udev->serialize); return 0; } /* suspend interface drivers; if this is a hub, it * suspends the child devices */ if (udev->actconfig) { int i; for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { struct usb_interface *intf; struct usb_driver *driver; intf = udev->actconfig->interface[i]; if (state <= intf->dev.power.power_state) continue; if (!intf->dev.driver) continue; driver = to_usb_driver(intf->dev.driver); if (driver->suspend) { status = driver->suspend(intf, state); if (intf->dev.power.power_state != state || status) dev_err(&intf->dev, "suspend %d fail, code %d\n", state, status); } /* only drivers with suspend() can ever resume(); * and after power loss, even they won't. * bus_rescan_devices() can rebind drivers later. * * FIXME the PM core self-deadlocks when unbinding * drivers during suspend/resume ... everything grabs * dpm_sem (not a spinlock, ugh). we want to unbind, * since we know every driver's probe/disconnect works * even for drivers that can't suspend. */ if (!driver->suspend || state > PM_SUSPEND_MEM) {#if 1 dev_warn(&intf->dev, "resume is unsafe!\n");#else down_write(&usb_bus_type.rwsem); device_release_driver(&intf->dev); up_write(&usb_bus_type.rwsem);#endif } } } /* * FIXME this needs port power off call paths too, to help force * USB into the "generic" PM model. At least for devices on * ports that aren't using ganged switching (usually root hubs). * * NOTE: SRP-capable links should adopt more aggressive poweroff * policies (when HNP doesn't apply) once we have mechanisms to * turn power back on! (Likely not before 2.7...) */ if (state > PM_SUSPEND_MEM) { dev_warn(&udev->dev, "no poweroff yet, suspending instead\n"); state = PM_SUSPEND_MEM; } /* "global suspend" of the HC-to-USB interface (root hub), or * "selective suspend" of just one hub-device link. */ if (!udev->parent) { struct usb_bus *bus = udev->bus; if (bus && bus->op->hub_suspend) status = bus->op->hub_suspend (bus); else status = -EOPNOTSUPP; } else status = hub_port_suspend(udev->parent, port + 1); if (status == 0) udev->dev.power.power_state = state; up(&udev->serialize); return status;}/** * usb_suspend_device - suspend a usb device * @udev: device that's no longer in active use * @state: PM_SUSPEND_MEM to suspend * Context: must be able to sleep; device not locked * * Suspends a USB device that isn't in active use, conserving power. * Devices may wake out of a suspend, if anything important happens, * using the remote wakeup mechanism. They may also be taken out of * suspend by the host, using usb_resume_device(). It's also routine * to disconnect devices while they are suspended. * * Suspending OTG devices may trigger HNP, if that's been enabled * between a pair of dual-role devices. That will change roles, such * as from A-Host to A-Peripheral or from B-Host back to B-Peripheral. * * Returns 0 on success, else negative errno. */int usb_suspend_device(struct usb_device *udev, u32 state){ return __usb_suspend_device(udev, locktree(udev), state);}/* * hardware resume signaling is finished, either because of selective * resume (by host) or remote wakeup (by device) ... now see what changed * in the tree that's rooted at this device. */static int finish_port_resume(struct usb_device *udev){ int status; u16 devstatus; /* caller owns udev->serialize */ dev_dbg(&udev->dev, "usb resume\n"); udev->dev.power.power_state = PM_SUSPEND_ON; /* usb ch9 identifies four variants of SUSPENDED, based on what * state the device resumes to. Linux currently won't see the * first two on the host side; they'd be inside hub_port_init() * during many timeouts, but khubd can't suspend until later. */ udev->state = udev->actconfig ? USB_STATE_CONFIGURED : USB_STATE_ADDRESS; /* 10.5.4.5 says be sure devices in the tree are still there. * For now let's assume the device didn't go crazy on resume, * and device drivers will know about any resume quirks. */ status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus); if (status < 0) dev_dbg(&udev->dev, "gone after usb resume? status %d\n", status); else if (udev->actconfig) { unsigned i; le16_to_cpus(&devstatus); if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) { status = 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); if (status) { dev_dbg(&udev->dev, "disable remote " "wakeup, status %d\n", status); status = 0; } } /* resume interface drivers; if this is a hub, it * resumes the child devices */ for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { struct usb_interface *intf; struct usb_driver *driver; intf = udev->actconfig->interface[i]; if (intf->dev.power.power_state == PM_SUSPEND_ON) continue; if (!intf->dev.driver) { /* FIXME maybe force to alt 0 */ continue; } driver = to_usb_driver(intf->dev.driver); /* bus_rescan_devices() may rebind drivers */ if (!driver->resume) continue; /* can we do better than just logging errors? */ status = driver->resume(intf); if (intf->dev.power.power_state != PM_SUSPEND_ON || status) dev_dbg(&intf->dev, "resume fail, state %d code %d\n", intf->dev.power.power_state, status); } status = 0; } else if (udev->devnum <= 0) { dev_dbg(&udev->dev, "bogus resume!\n"); status = -EINVAL; } return status;}static inthub_port_resume(struct usb_device *hdev, int port){ int status; struct usb_device *udev; udev = hdev->children[port - 1]; // dev_dbg(hubdev(hdev), "resume port %d\n", port); /* see 7.1.7.7; affects power usage, but not budgeting */ status = clear_port_feature(hdev, port, USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(&hdev->actconfig->interface[0]->dev, "can't resume port %d, status %d\n", port, status); } else { u16 devstatus; u16 portchange; /* drive resume for at least 20 msec */ dev_dbg(&udev->dev, "RESUME\n"); msleep(25);#define LIVE_FLAGS ( USB_PORT_STAT_POWER \ | USB_PORT_STAT_ENABLE \ | USB_PORT_STAT_CONNECTION) /* Virtual root hubs can trigger on GET_PORT_STATUS to * stop resume signaling. Then finish the resume * sequence. */ devstatus = portchange = 0; status = hub_port_status(hdev, port - 1, &devstatus, &portchange); if (status < 0 || (devstatus & LIVE_FLAGS) != LIVE_FLAGS || (devstatus & USB_PORT_STAT_SUSPEND) != 0 ) { dev_dbg(&hdev->actconfig->interface[0]->dev, "port %d status %04x.%04x after resume, %d\n", port, portchange, devstatus, status); } else { /* TRSMRCY = 10 msec */ msleep(10); status = finish_port_resume(udev); } } if (status < 0) status = hub_port_disable(hdev, port); return status;}static int hub_resume (struct usb_interface *intf);/** * usb_resume_device - re-activate a suspended usb device * @udev: device to re-activate * Context: must be able to sleep; device not locked * * This will re-activate the suspended device, increasing power usage * while letting drivers communicate again with its endpoints. * USB resume explicitly guarantees that the power session between * the host and the device is the same as it was when the device * suspended. * * Returns 0 on success, else negative errno. */int usb_resume_device(struct usb_device *udev){ int port, status; port = locktree(udev); if (port < 0) return port; /* "global resume" of the HC-to-USB interface (root hub), or * selective resume of one hub-to-device port */ if (!udev->parent) { struct usb_bus *bus = udev->bus; if (bus && bus->op->hub_resume) status = bus->op->hub_resume (bus); else status = -EOPNOTSUPP; if (status == 0) { /* TRSMRCY = 10 msec */ msleep(10); status = hub_resume (bus->root_hub ->actconfig->interface[0]); } } else if (udev->state == USB_STATE_SUSPENDED) { status = hub_port_resume(udev->parent, port + 1); } else { status = 0; udev->dev.power.power_state = PM_SUSPEND_ON; } if (status < 0) { dev_dbg(&udev->dev, "can't resume, status %d\n", status); } up(&udev->serialize); /* rebind drivers that had no suspend() */ bus_rescan_devices(&usb_bus_type); return status;}static int remote_wakeup(struct usb_device *udev){ int status = 0; /* don't repeat RESUME sequence if this device * was already woken up by some other task */ down(&udev->serialize); if (udev->state == USB_STATE_SUSPENDED) { dev_dbg(&udev->dev, "RESUME (wakeup)\n"); /* TRSMRCY = 10 msec */ msleep(10); status = finish_port_resume(udev); } up(&udev->serialize); return status;}static int hub_suspend(struct usb_interface *intf, u32 state){ struct usb_hub *hub = usb_get_intfdata (intf); struct usb_device *hdev = hub->hdev; unsigned port; int status; /* stop khubd and related activity */ hub_quiesce(hub); /* then suspend every port */ for (port = 0; port < hdev->maxchild; port++) { struct usb_device *udev; udev = hdev->children [port]; if (!udev) continue; down(&udev->serialize); status = __usb_suspend_device(udev, port, state); if (status < 0) dev_dbg(&intf->dev, "suspend port %d --> %d\n", port, status); } intf->dev.power.power_state = state; return 0;}static int hub_resume(struct usb_interface *intf){ struct usb_device *hdev = interface_to_usbdev(intf); struct usb_hub *hub = usb_get_intfdata (intf); unsigned port; int status; for (port = 0; port < hdev->maxchild; port++) { struct usb_device *udev; u16 portstat, portchange; udev = hdev->children [port]; status = hub_port_status(hdev, port, &portstat, &portchange); if (status == 0) { if (portchange & USB_PORT_STAT_C_SUSPEND) { clear_port_feature(hdev, port + 1, USB_PORT_FEAT_C_SUSPEND); portchange &= ~USB_PORT_STAT_C_SUSPEND; } /* let khubd handle disconnects etc */ if (portchange) continue; } if (!udev) continue; down (&udev->serialize); if (portstat & USB_PORT_STAT_SUSPEND) status = hub_port_resume(hdev, port + 1); else { status = finish_port_resume(udev); if (status < 0) status = hub_port_disable(hdev, port); if (status < 0) dev_dbg(&intf->dev, "resume port %d --> %d\n", port, status); } up(&udev->serialize); } intf->dev.power.power_state = PM_SUSPEND_ON; hub_reactivate(hub); return 0;}#else /* !CONFIG_USB_SUSPEND */int usb_suspend_device(struct usb_device *udev, u32 state){ return 0;}int usb_resume_device(struct usb_device *udev){ return 0;}#define hub_suspend NULL#define hub_resume NULL#define remote_wakeup(x) 0#endif /* CONFIG_USB_SUSPEND */EXPORT_SYMBOL(usb_suspend_device);EXPORT_SYMBOL(usb_resume_device);/* USB 2.0 spec, 7.1.7.3 / fig 7-29: * * Between connect detection and reset signaling there must be a delay * of 100ms at least for debounce and power-settling. The corresponding * timer shall restart whenever the downstream port detects a disconnect. * * Apparently there are some bluetooth and irda-dongles and a number of * low-speed devices for which this debounce period may last over a second. * Not covered by the spec - but easy to deal with. * * This implementation uses a 1500ms total debounce timeout; if the * connection isn't stable by then it returns -ETIMEDOUT. It checks * every 25ms for transient disconnects. When the port status has been * unchanged for 100ms it returns the port status. */#define HUB_DEBOUNCE_TIMEOUT 1500#define HUB_DEBOUNCE_STEP 25#define HUB_DEBOUNCE_STABLE 100static int hub_port_debounce(struct usb_device *hdev, int port){ int ret; int total_time, stable_time = 0; u16 portchange, portstatus; unsigned connection = 0xffff; for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { ret = hub_port_status(hdev, port, &portstatus, &portchange); if (ret < 0) return ret; if (!(portchange & USB_PORT_STAT_C_CONNECTION) && (portstatus & USB_PORT_STAT_CONNECTION) == connection) { stable_time += HUB_DEBOUNCE_STEP; if (stable_time >= HUB_DEBOUNCE_STABLE) break; } else { stable_time = 0; connection = portstatus & USB_PORT_STAT_CONNECTION; } if (portchange & USB_PORT_STAT_C_CONNECTION) { clear_port_feature(hdev, port+1, USB_PORT_FEAT_C_CONNECTION); } if (total_time >= HUB_DEBOUNCE_TIMEOUT)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -