diff options
author | alfred <alfred@FreeBSD.org> | 2009-01-04 00:12:01 +0000 |
---|---|---|
committer | alfred <alfred@FreeBSD.org> | 2009-01-04 00:12:01 +0000 |
commit | 29029a97b0f154c8bdeb9ce2cd9cf875d1c7206c (patch) | |
tree | 6a212d791777cd056f5b3466272b0be92152182e /sys/dev/usb2/core/usb2_hub.c | |
parent | e21427fbffccb679cf56cf4596ee409a9ab71340 (diff) | |
download | FreeBSD-src-29029a97b0f154c8bdeb9ce2cd9cf875d1c7206c.zip FreeBSD-src-29029a97b0f154c8bdeb9ce2cd9cf875d1c7206c.tar.gz |
Sync with usb4bsd:
src/lib/libusb20/libusb20_desc.c
Make "libusb20_desc_foreach()" more readable.
src/sys/dev/usb2/controller/*.[ch]
src/sys/dev/usb2/core/*.[ch]
Implement support for USB power save for all HC's.
Implement support for Big-endian EHCI.
Move Huawei quirks back into "u3g" driver.
Improve device enumeration.
src/sys/dev/usb2/ethernet/*[ch]
Patches for supporting new AXE Gigabit chipset.
src/sys/dev/usb2/serial/*[ch]
Fix IOCTL return code.
src/sys/dev/usb2/wlan/*[ch]
Sync with old USB stack.
Submitted by: hps
Diffstat (limited to 'sys/dev/usb2/core/usb2_hub.c')
-rw-r--r-- | sys/dev/usb2/core/usb2_hub.c | 530 |
1 files changed, 514 insertions, 16 deletions
diff --git a/sys/dev/usb2/core/usb2_hub.c b/sys/dev/usb2/core/usb2_hub.c index 34b275a..0d08b948 100644 --- a/sys/dev/usb2/core/usb2_hub.c +++ b/sys/dev/usb2/core/usb2_hub.c @@ -34,6 +34,7 @@ #include <dev/usb2/include/usb2_mfunc.h> #include <dev/usb2/include/usb2_error.h> #include <dev/usb2/include/usb2_standard.h> +#include <dev/usb2/include/usb2_ioctl.h> #define USB_DEBUG_VAR uhub_debug @@ -61,6 +62,11 @@ SYSCTL_INT(_hw_usb2_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0, "Debug level"); #endif +static int usb2_power_timeout = 30; /* seconds */ + +SYSCTL_INT(_hw_usb2, OID_AUTO, power_timeout, CTLFLAG_RW, + &usb2_power_timeout, 0, "USB power timeout"); + struct uhub_current_state { uint16_t port_change; uint16_t port_status; @@ -86,6 +92,8 @@ struct uhub_softc { static device_probe_t uhub_probe; static device_attach_t uhub_attach; static device_detach_t uhub_detach; +static device_suspend_t uhub_suspend; +static device_resume_t uhub_resume; static bus_driver_added_t uhub_driver_added; static bus_child_location_str_t uhub_child_location_string; @@ -94,6 +102,9 @@ static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string; static usb2_callback_t uhub_intr_callback; static usb2_callback_t uhub_intr_clear_stall_callback; +static void usb2_dev_resume_peer(struct usb2_device *udev); +static void usb2_dev_suspend_peer(struct usb2_device *udev); + static const struct usb2_config uhub_config[2] = { [0] = { @@ -133,8 +144,8 @@ static driver_t uhub_driver = DEVMETHOD(device_attach, uhub_attach), DEVMETHOD(device_detach, uhub_detach), - DEVMETHOD(device_suspend, bus_generic_suspend), - DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_suspend, uhub_suspend), + DEVMETHOD(device_resume, uhub_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(bus_child_location_str, uhub_child_location_string), @@ -302,8 +313,8 @@ repeat: /* first clear the port connection change bit */ - err = usb2_req_clear_port_feature - (udev, &Giant, portno, UHF_C_PORT_CONNECTION); + err = usb2_req_clear_port_feature(udev, &Giant, + portno, UHF_C_PORT_CONNECTION); if (err) { goto error; @@ -338,6 +349,12 @@ repeat: DPRINTF("Port %d is in Host Mode\n", portno); + if (sc->sc_st.port_status & UPS_SUSPEND) { + DPRINTF("Port %d was still " + "suspended, clearing.\n", portno); + err = usb2_req_clear_port_feature(sc->sc_udev, + &Giant, portno, UHF_PORT_SUSPEND); + } /* USB Host Mode */ /* wait for maximum device power up time */ @@ -346,8 +363,7 @@ repeat: /* reset port, which implies enabling it */ - err = usb2_req_reset_port - (udev, &Giant, portno); + err = usb2_req_reset_port(udev, &Giant, portno); if (err) { DPRINTFN(0, "port %d reset " @@ -411,8 +427,8 @@ error: } if (err == 0) { if (sc->sc_st.port_status & UPS_PORT_ENABLED) { - err = usb2_req_clear_port_feature - (sc->sc_udev, &Giant, + err = usb2_req_clear_port_feature( + sc->sc_udev, &Giant, portno, UHF_PORT_ENABLE); } } @@ -446,16 +462,17 @@ uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) /* first clear the port suspend change bit */ - err = usb2_req_clear_port_feature - (udev, &Giant, portno, UHF_C_PORT_SUSPEND); - + err = usb2_req_clear_port_feature(udev, &Giant, + portno, UHF_C_PORT_SUSPEND); if (err) { + DPRINTF("clearing suspend failed.\n"); goto done; } /* get fresh status */ err = uhub_read_port_status(sc, portno); if (err) { + DPRINTF("reading port status failed.\n"); goto done; } /* get current state */ @@ -465,12 +482,21 @@ uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) } else { is_suspend = 0; } + + DPRINTF("suspended=%u\n", is_suspend); + /* do the suspend or resume */ if (child) { - sx_xlock(child->default_sx + 1); - err = usb2_suspend_resume(child, is_suspend); - sx_unlock(child->default_sx + 1); + /* + * This code handle two cases: 1) Host Mode - we can only + * receive resume here 2) Device Mode - we can receive + * suspend and resume here + */ + if (is_suspend == 0) + usb2_dev_resume_peer(child); + else if (child->flags.usb2_mode == USB_MODE_DEVICE) + usb2_dev_suspend_peer(child); } done: return (err); @@ -502,6 +528,11 @@ uhub_explore(struct usb2_device *udev) if (udev->depth > USB_HUB_MAX_DEPTH) { return (USB_ERR_TOO_DEEP); } + if (udev->pwr_save.suspended) { + /* need to wait until the child signals resume */ + DPRINTF("Device is suspended!\n"); + return (0); + } for (x = 0; x != hub->nports; x++) { up = hub->ports + x; portno = x + 1; @@ -511,6 +542,15 @@ uhub_explore(struct usb2_device *udev) /* most likely the HUB is gone */ break; } + if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) { + DPRINTF("Overcurrent on port %u.\n", portno); + err = usb2_req_clear_port_feature( + udev, &Giant, portno, UHF_C_PORT_OVER_CURRENT); + if (err) { + /* most likely the HUB is gone */ + break; + } + } if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) { /* * Fake a connect status change so that the @@ -750,8 +790,8 @@ uhub_attach(device_t dev) } if (!err) { /* turn the power on */ - err = usb2_req_set_port_feature - (udev, &Giant, portno, UHF_PORT_POWER); + err = usb2_req_set_port_feature(udev, &Giant, + portno, UHF_PORT_POWER); } if (err) { DPRINTFN(0, "port %d power on failed, %s\n", @@ -774,6 +814,10 @@ uhub_attach(device_t dev) usb2_transfer_start(sc->sc_xfer[0]); USB_XFER_UNLOCK(sc->sc_xfer[0]); + /* Enable automatic power save on all USB HUBs */ + + usb2_set_power_mode(udev, USB_POWER_MODE_SAVE); + return (0); error: @@ -827,6 +871,22 @@ uhub_detach(device_t dev) return (0); } +static int +uhub_suspend(device_t dev) +{ + DPRINTF("\n"); + /* Sub-devices are not suspended here! */ + return (0); +} + +static int +uhub_resume(device_t dev) +{ + DPRINTF("\n"); + /* Sub-devices are not resumed here! */ + return (0); +} + static void uhub_driver_added(device_t dev, driver_t *driver) { @@ -1334,3 +1394,441 @@ usb2_needs_explore_all(void) max--; } } + +/*------------------------------------------------------------------------* + * usb2_bus_power_update + * + * This function will ensure that all USB devices on the given bus are + * properly suspended or resumed according to the device transfer + * state. + *------------------------------------------------------------------------*/ +void +usb2_bus_power_update(struct usb2_bus *bus) +{ + usb2_needs_explore(bus, 0 /* no probe */ ); +} + +/*------------------------------------------------------------------------* + * usb2_transfer_power_ref + * + * This function will modify the power save reference counts and + * wakeup the USB device associated with the given USB transfer, if + * needed. + *------------------------------------------------------------------------*/ +void +usb2_transfer_power_ref(struct usb2_xfer *xfer, int val) +{ + static const uint32_t power_mask[4] = { + [UE_CONTROL] = USB_HW_POWER_CONTROL, + [UE_BULK] = USB_HW_POWER_BULK, + [UE_INTERRUPT] = USB_HW_POWER_INTERRUPT, + [UE_ISOCHRONOUS] = USB_HW_POWER_ISOC, + }; + struct usb2_device *udev; + uint8_t needs_explore; + uint8_t needs_hw_power; + uint8_t xfer_type; + + udev = xfer->udev; + + if (udev->device_index == USB_ROOT_HUB_ADDR) { + /* no power save for root HUB */ + return; + } + USB_BUS_LOCK(udev->bus); + + xfer_type = xfer->pipe->edesc->bmAttributes & UE_XFERTYPE; + + udev->pwr_save.last_xfer_time = ticks; + udev->pwr_save.type_refs[xfer_type] += val; + + if (xfer->flags_int.control_xfr) { + udev->pwr_save.read_refs += val; + if (xfer->flags_int.usb2_mode == USB_MODE_HOST) { + /* + * it is not allowed to suspend during a control + * transfer + */ + udev->pwr_save.write_refs += val; + } + } else if (USB_GET_DATA_ISREAD(xfer)) { + udev->pwr_save.read_refs += val; + } else { + udev->pwr_save.write_refs += val; + } + + if (udev->pwr_save.suspended) + needs_explore = + (udev->pwr_save.write_refs != 0) || + ((udev->pwr_save.read_refs != 0) && + (usb2_peer_can_wakeup(udev) == 0)); + else + needs_explore = 0; + + if (!(udev->bus->hw_power_state & power_mask[xfer_type])) { + DPRINTF("Adding type %u to power state\n", xfer_type); + udev->bus->hw_power_state |= power_mask[xfer_type]; + needs_hw_power = 1; + } else { + needs_hw_power = 0; + } + + USB_BUS_UNLOCK(udev->bus); + + if (needs_explore) { + DPRINTF("update\n"); + usb2_bus_power_update(udev->bus); + } else if (needs_hw_power) { + DPRINTF("needs power\n"); + if (udev->bus->methods->set_hw_power != NULL) { + (udev->bus->methods->set_hw_power) (udev->bus); + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_bus_powerd + * + * This function implements the USB power daemon and is called + * regularly from the USB explore thread. + *------------------------------------------------------------------------*/ +void +usb2_bus_powerd(struct usb2_bus *bus) +{ + struct usb2_device *udev; + unsigned int temp; + unsigned int limit; + unsigned int mintime; + uint32_t type_refs[4]; + uint8_t x; + uint8_t rem_wakeup; + + limit = usb2_power_timeout; + if (limit == 0) + limit = hz; + else if (limit > 255) + limit = 255 * hz; + else + limit = limit * hz; + + DPRINTF("bus=%p\n", bus); + + USB_BUS_LOCK(bus); + + /* + * The root HUB device is never suspended + * and we simply skip it. + */ + for (x = USB_ROOT_HUB_ADDR + 1; + x != USB_MAX_DEVICES; x++) { + + udev = bus->devices[x]; + if (udev == NULL) + continue; + + rem_wakeup = usb2_peer_can_wakeup(udev); + + temp = ticks - udev->pwr_save.last_xfer_time; + + if ((udev->power_mode == USB_POWER_MODE_ON) || + (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) || + (udev->pwr_save.write_refs != 0) || + ((udev->pwr_save.read_refs != 0) && + (rem_wakeup == 0))) { + + /* check if we are suspended */ + if (udev->pwr_save.suspended != 0) { + USB_BUS_UNLOCK(bus); + usb2_dev_resume_peer(udev); + USB_BUS_LOCK(bus); + } + } else if (temp >= limit) { + + /* check if we are not suspended */ + if (udev->pwr_save.suspended == 0) { + USB_BUS_UNLOCK(bus); + usb2_dev_suspend_peer(udev); + USB_BUS_LOCK(bus); + } + } + } + + /* reset counters */ + + mintime = 0 - 1; + type_refs[0] = 0; + type_refs[1] = 0; + type_refs[2] = 0; + type_refs[3] = 0; + + /* Re-loop all the devices to get the actual state */ + + for (x = USB_ROOT_HUB_ADDR + 1; + x != USB_MAX_DEVICES; x++) { + + udev = bus->devices[x]; + if (udev == NULL) + continue; + + /* "last_xfer_time" can be updated by a resume */ + temp = ticks - udev->pwr_save.last_xfer_time; + + /* + * Compute minimum time since last transfer for the complete + * bus: + */ + if (temp < mintime) + mintime = temp; + + if (udev->pwr_save.suspended == 0) { + type_refs[0] += udev->pwr_save.type_refs[0]; + type_refs[1] += udev->pwr_save.type_refs[1]; + type_refs[2] += udev->pwr_save.type_refs[2]; + type_refs[3] += udev->pwr_save.type_refs[3]; + } + } + + if (mintime >= (1 * hz)) { + /* recompute power masks */ + DPRINTF("Recomputing power masks\n"); + bus->hw_power_state = 0; + if (type_refs[UE_CONTROL] != 0) + bus->hw_power_state |= USB_HW_POWER_CONTROL; + if (type_refs[UE_BULK] != 0) + bus->hw_power_state |= USB_HW_POWER_BULK; + if (type_refs[UE_INTERRUPT] != 0) + bus->hw_power_state |= USB_HW_POWER_INTERRUPT; + if (type_refs[UE_ISOCHRONOUS] != 0) + bus->hw_power_state |= USB_HW_POWER_ISOC; + } + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) { + /* always update hardware power! */ + (bus->methods->set_hw_power) (bus); + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_dev_resume_peer + * + * This function will resume an USB peer and do the required USB + * signalling to get an USB device out of the suspended state. + *------------------------------------------------------------------------*/ +static void +usb2_dev_resume_peer(struct usb2_device *udev) +{ + struct usb2_bus *bus; + int err; + + /* be NULL safe */ + if (udev == NULL) + return; + + /* check if already resumed */ + if (udev->pwr_save.suspended == 0) + return; + + /* we need a parent HUB to do resume */ + if (udev->parent_hub == NULL) + return; + + DPRINTF("udev=%p\n", udev); + + if ((udev->flags.usb2_mode == USB_MODE_DEVICE) && + (udev->flags.remote_wakeup == 0)) { + /* + * If the host did not set the remote wakeup feature, we can + * not wake it up either! + */ + DPRINTF("remote wakeup is not set!\n"); + return; + } + /* get bus pointer */ + bus = udev->bus; + + /* resume parent hub first */ + usb2_dev_resume_peer(udev->parent_hub); + + /* resume current port (Valid in Host and Device Mode) */ + err = usb2_req_clear_port_feature(udev->parent_hub, + &Giant, udev->port_no, UHF_PORT_SUSPEND); + if (err) { + DPRINTFN(0, "Resuming port failed!\n"); + return; + } + /* resume settle time */ + usb2_pause_mtx(&Giant, USB_PORT_RESUME_DELAY); + + if (bus->methods->device_resume != NULL) { + /* resume USB device on the USB controller */ + (bus->methods->device_resume) (udev); + } + USB_BUS_LOCK(bus); + /* set that this device is now resumed */ + udev->pwr_save.suspended = 0; + /* make sure that we don't go into suspend right away */ + udev->pwr_save.last_xfer_time = ticks; + + /* make sure the needed power masks are on */ + if (udev->pwr_save.type_refs[UE_CONTROL] != 0) + bus->hw_power_state |= USB_HW_POWER_CONTROL; + if (udev->pwr_save.type_refs[UE_BULK] != 0) + bus->hw_power_state |= USB_HW_POWER_BULK; + if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0) + bus->hw_power_state |= USB_HW_POWER_INTERRUPT; + if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) + bus->hw_power_state |= USB_HW_POWER_ISOC; + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) { + /* always update hardware power! */ + (bus->methods->set_hw_power) (bus); + } + sx_xlock(udev->default_sx + 1); + /* notify all sub-devices about resume */ + err = usb2_suspend_resume(udev, 0); + sx_unlock(udev->default_sx + 1); + + /* check if peer has wakeup capability */ + if (usb2_peer_can_wakeup(udev)) { + /* clear remote wakeup */ + err = usb2_req_clear_device_feature(udev, + &Giant, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Clearing device " + "remote wakeup failed: %s!\n", + usb2_errstr(err)); + } + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_dev_suspend_peer + * + * This function will suspend an USB peer and do the required USB + * signalling to get an USB device into the suspended state. + *------------------------------------------------------------------------*/ +static void +usb2_dev_suspend_peer(struct usb2_device *udev) +{ + struct usb2_device *hub; + struct usb2_device *child; + uint32_t temp; + int err; + uint8_t x; + uint8_t nports; + uint8_t suspend_parent; + +repeat: + /* be NULL safe */ + if (udev == NULL) + return; + + /* check if already suspended */ + if (udev->pwr_save.suspended) + return; + + /* we need a parent HUB to do suspend */ + if (udev->parent_hub == NULL) + return; + + DPRINTF("udev=%p\n", udev); + + /* check if all devices on the parent hub are suspended */ + hub = udev->parent_hub; + if (hub != NULL) { + nports = hub->hub->nports; + suspend_parent = 1; + + for (x = 0; x != nports; x++) { + + child = usb2_bus_port_get_device(hub->bus, + hub->hub->ports + x); + + if (child == NULL) + continue; + + if (child->pwr_save.suspended) + continue; + + if (child == udev) + continue; + + /* another device on the HUB is not suspended */ + suspend_parent = 0; + + break; + } + } else { + suspend_parent = 0; + } + + sx_xlock(udev->default_sx + 1); + /* notify all sub-devices about suspend */ + err = usb2_suspend_resume(udev, 1); + sx_unlock(udev->default_sx + 1); + + if (usb2_peer_can_wakeup(udev)) { + /* allow device to do remote wakeup */ + err = usb2_req_set_device_feature(udev, + &Giant, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Setting device " + "remote wakeup failed!\n"); + } + } + USB_BUS_LOCK(udev->bus); + /* + * Set that this device is suspended. This variable must be set + * before calling USB controller suspend callbacks. + */ + udev->pwr_save.suspended = 1; + USB_BUS_UNLOCK(udev->bus); + + if (udev->bus->methods->device_suspend != NULL) { + + /* suspend device on the USB controller */ + (udev->bus->methods->device_suspend) (udev); + + /* do DMA delay */ + temp = usb2_get_dma_delay(udev->bus); + usb2_pause_mtx(&Giant, temp); + + } + /* suspend current port */ + err = usb2_req_set_port_feature(udev->parent_hub, + &Giant, udev->port_no, UHF_PORT_SUSPEND); + if (err) { + DPRINTFN(0, "Suspending port failed\n"); + return; + } + if (suspend_parent) { + udev = udev->parent_hub; + goto repeat; + } + return; +} + +/*------------------------------------------------------------------------* + * usb2_set_power_mode + * + * This function will set the power mode, see USB_POWER_MODE_XXX for a + * USB device. + *------------------------------------------------------------------------*/ +void +usb2_set_power_mode(struct usb2_device *udev, uint8_t power_mode) +{ + /* filter input argument */ + if (power_mode != USB_POWER_MODE_ON) { + power_mode = USB_POWER_MODE_SAVE; + } + udev->power_mode = power_mode; /* update copy of power mode */ + + usb2_bus_power_update(udev->bus); + + return; +} |