📄 hub.c
字号:
return 0;fail: usb_set_device_state(udev, USB_STATE_NOTATTACHED); return err;}static int hub_port_status(struct usb_hub *hub, int port1, u16 *status, u16 *change){ int ret; ret = get_port_status(hub->hdev, port1, &hub->status->port); if (ret < 0) dev_err (hub->intfdev, "%s failed (err = %d)\n", __FUNCTION__, ret); else { *status = le16_to_cpu(hub->status->port.wPortStatus); *change = le16_to_cpu(hub->status->port.wPortChange); ret = 0; } return ret;}#define PORT_RESET_TRIES 5#define SET_ADDRESS_TRIES 2#define GET_DESCRIPTOR_TRIES 2#define SET_CONFIG_TRIES (2 * (use_both_schemes + 1))#define USE_NEW_SCHEME(i) ((i) / 2 == old_scheme_first)#define HUB_ROOT_RESET_TIME 50 /* times are in msec */#define HUB_SHORT_RESET_TIME 10#define HUB_LONG_RESET_TIME 200#define HUB_RESET_TIMEOUT 500static int hub_port_wait_reset(struct usb_hub *hub, int port1, struct usb_device *udev, unsigned int delay){ int delay_time, ret; u16 portstatus; u16 portchange; for (delay_time = 0; delay_time < HUB_RESET_TIMEOUT; delay_time += delay) { /* wait to give the device a chance to reset */ msleep(delay); /* read and decode port status */ ret = hub_port_status(hub, port1, &portstatus, &portchange); if (ret < 0) return ret; /* Device went away? */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) return -ENOTCONN; /* bomb out completely if something weird happened */ if ((portchange & USB_PORT_STAT_C_CONNECTION)) return -EINVAL; /* if we`ve finished resetting, then break out of the loop */ if (!(portstatus & USB_PORT_STAT_RESET) && (portstatus & USB_PORT_STAT_ENABLE)) { if (portstatus & USB_PORT_STAT_HIGH_SPEED) udev->speed = USB_SPEED_HIGH; else if (portstatus & USB_PORT_STAT_LOW_SPEED) udev->speed = USB_SPEED_LOW; else udev->speed = USB_SPEED_FULL; return 0; } /* switch to the long delay after two short delay failures */ if (delay_time >= 2 * HUB_SHORT_RESET_TIME) delay = HUB_LONG_RESET_TIME; dev_dbg (hub->intfdev, "port %d not reset yet, waiting %dms\n", port1, delay); } return -EBUSY;}static int hub_port_reset(struct usb_hub *hub, int port1, struct usb_device *udev, unsigned int delay){ int i, status; /* Reset the port */ 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 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}/* * 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); udev->dev.power.power_state = PMSG_ON; /* 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 < 2) 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++) { struct device *dev = &udev->actconfig->interface[i]->dev; down(&dev->sem); (void) resume(dev); up(&dev->sem); } 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 status; if (udev->state == USB_STATE_NOTATTACHED) return -ENODEV; /* selective resume of one downstream hub-to-device port */ if (udev->parent) {#ifdef CONFIG_USB_SUSPEND 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), udev->portnum, udev); } else#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -