📄 hub.c
字号:
for (i = 0; i < PORT_RESET_TRIES; i++) { status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_RESET); if (status) dev_err(hub->intfdev, "cannot reset port %d (err = %d)\n", port1, status); else { status = hub_port_wait_reset(hub, port1, udev, delay); if (status && status != -ENOTCONN) dev_dbg(hub->intfdev, "port_wait_reset: err = %d\n", status); } /* return on disconnect or reset */ switch (status) { case 0: /* TRSTRCY = 10 ms; plus some extra */ msleep(10 + 40); /* FALL THROUGH */ case -ENOTCONN: case -ENODEV: clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_RESET); /* FIXME need disconnect() for NOTATTACHED device */ usb_set_device_state(udev, status ? USB_STATE_NOTATTACHED : USB_STATE_DEFAULT); return status; } dev_dbg (hub->intfdev, "port %d not enabled, trying reset again...\n", port1); delay = HUB_LONG_RESET_TIME; } dev_err (hub->intfdev, "Cannot enable port %i. Maybe the USB cable is bad?\n", port1); return status;}/* * Disable a port and mark a logical connnect-change event, so that some * time later khubd will disconnect() any existing usb_device on the port * and will re-enumerate if there actually is a device attached. */static void hub_port_logical_disconnect(struct usb_hub *hub, int port1){ dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1); hub_port_disable(hub, port1, 1); /* FIXME let caller ask to power down the port: * - some devices won't enumerate without a VBUS power cycle * - SRP saves power that way * - ... new call, TBD ... * That's easy if this hub can switch power per-port, and * khubd reactivates the port later (timer, SRP, etc). * Powerdown must be optional, because of reset/DFU. */ set_bit(port1, hub->change_bits); kick_khubd(hub);}#ifdef CONFIG_USB_SUSPEND/* * Selective port suspend reduces power; most suspended devices draw * less than 500 uA. It's also used in OTG, along with remote wakeup. * All devices below the suspended port are also suspended. * * Devices leave suspend state when the host wakes them up. Some devices * also support "remote wakeup", where the device can activate the USB * tree above them to deliver data, such as a keypress or packet. In * some cases, this wakes the USB host. */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 (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 */ 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); msleep(10); } return status;}/* * Devices on USB hub ports have only one "suspend" state, corresponding * to ACPI D2, "may cause the device to lose some context". * State transitions include: * * - suspend, resume ... when the VBUS power link stays live * - 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. * * If CONFIG_USB_SUSPEND isn't enabled, devices only really suspend when * the root hub for their bus goes into global suspend ... so we don't * (falsely) update the device power state to say it suspended. */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; if (udev->state == USB_STATE_SUSPENDED || udev->state == USB_STATE_NOTATTACHED) { return 0; } /* all interfaces must already be suspended */ 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)) { 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. */ if (udev->parent) status = hub_port_suspend(hdev_to_hub(udev->parent), port1, udev); if (status == 0) udev->dev.power.power_state = PMSG_SUSPEND; return status;}#endif/* * usb_suspend_device - suspend a usb device * @udev: device that's no longer in active use * Context: must be able to sleep; device not locked; pm locks held * * 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. * * This only affects the USB hardware for a device; its interfaces * (and, for hubs, child devices) must already have been 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){#ifdef CONFIG_USB_SUSPEND int port1, status; port1 = locktree(udev); if (port1 < 0) return port1; status = __usb_suspend_device(udev, port1); usb_unlock_device(udev); return status;#else /* NOTE: udev->state unchanged, it's not lying ... */ udev->dev.power.power_state = PMSG_SUSPEND; return 0;#endif}/* * If the USB "suspend" state is in use (rather than "global suspend"), * many devices will be individually taken out of suspend state using * special" resume" signaling. These routines kick in shortly after * 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_device_resume(struct usb_device *udev){ int status; u16 devstatus; /* caller owns the udev device lock */ dev_dbg(&udev->dev, "finish resume\n"); /* 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. */ usb_set_device_state(udev, 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; int (*resume)(struct device *); le16_to_cpus(&devstatus); if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP) && udev->parent) { 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 * may have a child resume event to deal with soon */ resume = udev->dev.bus->resume; for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) (void) resume(&udev->actconfig->interface[i]->dev); status = 0; } else if (udev->devnum <= 0) { dev_dbg(&udev->dev, "bogus resume!\n"); status = -EINVAL; } return status;}#ifdef CONFIG_USB_SUSPENDstatic inthub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev){ int status; // dev_dbg(hub->intfdev, "resume port %d\n", port1); /* see 7.1.7.7; affects power usage, but not budgeting */ status = clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND); if (status) { dev_dbg(hub->intfdev, "can't resume port %d, status %d\n", port1, status); } else { u16 devstatus; u16 portchange; /* drive resume for at least 20 msec */ if (udev) 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(hub, port1, &devstatus, &portchange); if (status < 0 || (devstatus & LIVE_FLAGS) != LIVE_FLAGS || (devstatus & USB_PORT_STAT_SUSPEND) != 0 ) { dev_dbg(hub->intfdev, "port %d status %04x.%04x after resume, %d\n", port1, portchange, devstatus, status); } else { /* TRSMRCY = 10 msec */ msleep(10); if (udev) status = finish_device_resume(udev); } } if (status < 0) hub_port_logical_disconnect(hub, port1); return status;}#endif/* * usb_resume_device - re-activate a suspended usb device * @udev: device to re-activate * Context: must be able to sleep; device not locked; pm locks held * * 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 port1, status; port1 = locktree(udev); if (port1 < 0) return port1;#ifdef CONFIG_USB_SUSPEND /* selective resume of one downstream hub-to-device port */ if (udev->parent) { if (udev->state == USB_STATE_SUSPENDED) { // NOTE swsusp may bork us, device state being wrong... // NOTE this fails if parent is also suspended... status = hub_port_resume(hdev_to_hub(udev->parent), port1, udev); } else status = 0; } else#endif status = finish_device_resume(udev); if (status < 0) dev_dbg(&udev->dev, "can't resume, status %d\n", status); usb_unlock_device(udev); /* rebind drivers that had no suspend() */ if (status == 0) { usb_lock_all_devices(); bus_rescan_devices(&usb_bus_type); usb_unlock_all_devices(); } return status;}static int remote_wakeup(struct usb_device *udev){ int status = 0;#ifdef CONFIG_USB_SUSPEND /* 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_device_resume(udev); } up(&udev->serialize);#endif return status;}static int hub_suspend(struct usb_interface *intf, pm_message_t msg){ struct usb_hub *hub = usb_get_intfdata (intf); struct usb_device *hdev = hub->hdev; unsigned port1; /* fail if children aren't already suspended */ for (port1 = 1; port1 <= hdev->maxchild; port1++) { struct usb_device *udev; udev = hdev->children [port1-1]; if (udev && (udev->dev.power.power_state.event == PM_EVENT_ON#ifdef CONFIG_USB_SUSPEND || udev->state != USB_STATE_SUSPENDED#endif )) { dev_dbg(&intf->dev, "port %d nyet suspended\n", port1); return -EBUSY; } } /* "global suspend" of the downstream HC-to-USB interface */ if (!hdev->parent) { struct usb_bus *bus = hdev->bus; if (bus) { int status = hcd_bus_suspend (bus); if (status != 0) { dev_dbg(&hdev->dev, "'global' suspend %d\n", status); return status; } } else return -EOPNOTSUPP; } /* stop khubd and related activity */ hub_quiesce(hub); 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); int status; /* "global resume" of the downstream HC-to-USB interface */ if (!hdev->parent) { struct usb_bus *bus = hdev->bus; if (bus) { status = hcd_bus_resume (bus); if (status) { dev_dbg(&intf->dev, "'global' resume %d\n", status); return status; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -