diff options
Diffstat (limited to 'freebsd/sys/dev/usb/usb_hub.c')
-rw-r--r-- | freebsd/sys/dev/usb/usb_hub.c | 363 |
1 files changed, 211 insertions, 152 deletions
diff --git a/freebsd/sys/dev/usb/usb_hub.c b/freebsd/sys/dev/usb/usb_hub.c index c20bde30..c5887797 100644 --- a/freebsd/sys/dev/usb/usb_hub.c +++ b/freebsd/sys/dev/usb/usb_hub.c @@ -40,7 +40,6 @@ #include <sys/systm.h> #include <sys/kernel.h> #include <sys/bus.h> -#include <sys/linker_set.h> #include <sys/module.h> #include <rtems/bsd/sys/lock.h> #include <sys/mutex.h> @@ -80,9 +79,8 @@ static int uhub_debug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB"); -SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0, +SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, &uhub_debug, 0, "Debug level"); - TUNABLE_INT("hw.usb.uhub.debug", &uhub_debug); #endif @@ -112,6 +110,7 @@ struct uhub_softc { #define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol) #define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) #define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) +#define UHUB_IS_MULTI_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBMTT) #define UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB) /* prototypes for type checking: */ @@ -243,11 +242,18 @@ uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up) /* check if device should be re-enumerated */ if (child->flags.usb_mode == USB_MODE_HOST) { - usbd_enum_lock(child); + uint8_t do_unlock; + + do_unlock = usbd_enum_lock(child); if (child->re_enumerate_wait) { - err = usbd_set_config_index(child, USB_UNCONFIG_INDEX); - if (err == 0) - err = usbd_req_re_enumerate(child, NULL); + err = usbd_set_config_index(child, + USB_UNCONFIG_INDEX); + if (err != 0) { + DPRINTF("Unconfigure failed: " + "%s: Ignored.\n", + usbd_errstr(err)); + } + err = usbd_req_re_enumerate(child, NULL); if (err == 0) err = usbd_set_config_index(child, 0); if (err == 0) { @@ -257,7 +263,8 @@ uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up) child->re_enumerate_wait = 0; err = 0; } - usbd_enum_unlock(child); + if (do_unlock) + usbd_enum_unlock(child); } /* check if probe and attach should be done */ @@ -325,6 +332,7 @@ uhub_reattach_port(struct uhub_softc *sc, uint8_t portno) enum usb_dev_speed speed; enum usb_hc_mode mode; usb_error_t err; + uint16_t power_mask; uint8_t timeout; DPRINTF("reattaching port %d\n", portno); @@ -367,10 +375,27 @@ repeat: } /* check if there is no power on the port and print a warning */ - if (!(sc->sc_st.port_status & UPS_PORT_POWER)) { + switch (udev->speed) { + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + case USB_SPEED_LOW: + power_mask = UPS_PORT_POWER; + break; + case USB_SPEED_SUPER: + if (udev->parent_hub == NULL) + power_mask = UPS_PORT_POWER; + else + power_mask = UPS_PORT_POWER_SS; + break; + default: + power_mask = 0; + break; + } + if (!(sc->sc_st.port_status & power_mask)) { DPRINTF("WARNING: strange, connected port %d " "has no power\n", portno); } + /* check if the device is in Host Mode */ if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) { @@ -394,7 +419,7 @@ repeat: /* wait for maximum device power up time */ usb_pause_mtx(NULL, - USB_MS_TO_TICKS(USB_PORT_POWERUP_DELAY)); + USB_MS_TO_TICKS(usb_port_powerup_delay)); /* reset port, which implies enabling it */ @@ -607,12 +632,15 @@ uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) } } else { switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) { - case UPS_PORT_LS_U0: - case UPS_PORT_LS_U1: + case UPS_PORT_LS_U3: + is_suspend = 1; + break; + case UPS_PORT_LS_SS_INA: + usbd_req_warm_reset_port(udev, NULL, portno); is_suspend = 0; break; default: - is_suspend = 1; + is_suspend = 0; break; } } @@ -629,8 +657,7 @@ uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) */ if (is_suspend == 0) usb_dev_resume_peer(child); - else if ((child->flags.usb_mode == USB_MODE_DEVICE) || - (usb_device_20_compatible(child) == 0)) + else if (child->flags.usb_mode == USB_MODE_DEVICE) usb_dev_suspend_peer(child); } done: @@ -688,6 +715,7 @@ uhub_explore(struct usb_device *udev) usb_error_t err; uint8_t portno; uint8_t x; + uint8_t do_unlock; hub = udev->hub; sc = hub->hubsoftc; @@ -704,6 +732,13 @@ uhub_explore(struct usb_device *udev) DPRINTF("Device is suspended!\n"); return (0); } + + /* + * Make sure we don't race against user-space applications + * like LibUSB: + */ + do_unlock = usbd_enum_lock(udev); + for (x = 0; x != hub->nports; x++) { up = hub->ports + x; portno = x + 1; @@ -765,7 +800,8 @@ uhub_explore(struct usb_device *udev) break; } } - if (sc->sc_st.port_change & (UPS_C_SUSPEND | UPS_C_PORT_LINK_STATE)) { + if (sc->sc_st.port_change & (UPS_C_SUSPEND | + UPS_C_PORT_LINK_STATE)) { err = uhub_suspend_resume_port(sc, portno); if (err) { /* most likely the HUB is gone */ @@ -781,6 +817,9 @@ uhub_explore(struct usb_device *udev) up->restartcnt = 0; } + if (do_unlock) + usbd_enum_unlock(udev); + /* initial status checked */ sc->sc_flags |= UHUB_FLAG_DID_EXPLORE; @@ -916,6 +955,16 @@ uhub_attach(device_t dev) "bus powered HUB. HUB ignored\n"); goto error; } + + if (UHUB_IS_MULTI_TT(sc)) { + err = usbd_set_alt_interface_index(udev, 0, 1); + if (err) { + device_printf(dev, "MTT could not be enabled\n"); + goto error; + } + device_printf(dev, "MTT enabled\n"); + } + /* get HUB descriptor */ DPRINTFN(2, "Getting HUB descriptor\n"); @@ -936,7 +985,7 @@ uhub_attach(device_t dev) /* get power delay */ pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + - USB_EXTRA_POWER_UP_TIME); + usb_extra_power_up_time); /* get complete HUB descriptor */ if (nports >= 8) { @@ -981,7 +1030,7 @@ uhub_attach(device_t dev) /* get power delay */ pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + - USB_EXTRA_POWER_UP_TIME); + usb_extra_power_up_time); /* get complete HUB descriptor */ if (nports >= 8) { @@ -1010,7 +1059,7 @@ uhub_attach(device_t dev) /* default number of ports */ nports = 1; /* default power delay */ - pwrdly = ((10 * UHD_PWRON_FACTOR) + USB_EXTRA_POWER_UP_TIME); + pwrdly = ((10 * UHD_PWRON_FACTOR) + usb_extra_power_up_time); break; } if (nports == 0) { @@ -1025,10 +1074,6 @@ uhub_attach(device_t dev) } udev->hub = hub; -#if USB_HAVE_TT_SUPPORT - /* init FULL-speed ISOCHRONOUS schedule */ - usbd_fs_isoc_schedule_init_all(hub->fs_isoc_schedule); -#endif /* initialize HUB structure */ hub->hubsoftc = sc; hub->explore = &uhub_explore; @@ -1332,15 +1377,21 @@ uhub_child_pnpinfo_string(device_t parent, device_t child, "devclass=0x%02x devsubclass=0x%02x " "sernum=\"%s\" " "release=0x%04x " - "intclass=0x%02x intsubclass=0x%02x", + "mode=%s " + "intclass=0x%02x intsubclass=0x%02x " + "intprotocol=0x%02x " "%s%s", UGETW(res.udev->ddesc.idVendor), UGETW(res.udev->ddesc.idProduct), res.udev->ddesc.bDeviceClass, res.udev->ddesc.bDeviceSubClass, usb_get_serial(res.udev), UGETW(res.udev->ddesc.bcdDevice), + (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", iface->idesc->bInterfaceClass, - iface->idesc->bInterfaceSubClass); + iface->idesc->bInterfaceSubClass, + iface->idesc->bInterfaceProtocol, + iface->pnpinfo ? " " : "", + iface->pnpinfo ? iface->pnpinfo : ""); } else { if (buflen) { buf[0] = '\0'; @@ -1393,7 +1444,7 @@ static uint8_t usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start, uint8_t end, uint8_t mask) { - usb_size_t min = 0 - 1; + usb_size_t min = (usb_size_t)-1; usb_size_t sum; uint8_t x; uint8_t y; @@ -1618,42 +1669,6 @@ usb_hs_bandwidth_free(struct usb_xfer *xfer) } /*------------------------------------------------------------------------* - * usbd_fs_isoc_schedule_init_sub - * - * This function initialises an USB FULL speed isochronous schedule - * entry. - *------------------------------------------------------------------------*/ -#if USB_HAVE_TT_SUPPORT -static void -usbd_fs_isoc_schedule_init_sub(struct usb_fs_isoc_schedule *fss) -{ - fss->total_bytes = (USB_FS_ISOC_UFRAME_MAX * - USB_FS_BYTES_PER_HS_UFRAME); - fss->frame_bytes = (USB_FS_BYTES_PER_HS_UFRAME); - fss->frame_slot = 0; -} -#endif - -/*------------------------------------------------------------------------* - * usbd_fs_isoc_schedule_init_all - * - * This function will reset the complete USB FULL speed isochronous - * bandwidth schedule. - *------------------------------------------------------------------------*/ -#if USB_HAVE_TT_SUPPORT -void -usbd_fs_isoc_schedule_init_all(struct usb_fs_isoc_schedule *fss) -{ - struct usb_fs_isoc_schedule *fss_end = fss + USB_ISOC_TIME_MAX; - - while (fss != fss_end) { - usbd_fs_isoc_schedule_init_sub(fss); - fss++; - } -} -#endif - -/*------------------------------------------------------------------------* * usb_isoc_time_expand * * This function will expand the time counter from 7-bit to 16-bit. @@ -1685,114 +1700,130 @@ usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr) } /*------------------------------------------------------------------------* - * usbd_fs_isoc_schedule_isoc_time_expand + * usbd_fs_isoc_schedule_alloc_slot * - * This function does multiple things. First of all it will expand the - * passed isochronous time, which is the return value. Then it will - * store where the current FULL speed isochronous schedule is - * positioned in time and where the end is. See "pp_start" and - * "pp_end" arguments. + * This function will allocate bandwidth for an isochronous FULL speed + * transaction in the FULL speed schedule. * * Returns: - * Expanded version of "isoc_time". - * - * NOTE: This function depends on being called regularly with - * intervals less than "USB_ISOC_TIME_MAX". + * <8: Success + * Else: Error *------------------------------------------------------------------------*/ #if USB_HAVE_TT_SUPPORT -uint16_t -usbd_fs_isoc_schedule_isoc_time_expand(struct usb_device *udev, - struct usb_fs_isoc_schedule **pp_start, - struct usb_fs_isoc_schedule **pp_end, - uint16_t isoc_time) +uint8_t +usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time) { - struct usb_fs_isoc_schedule *fss_end; - struct usb_fs_isoc_schedule *fss_a; - struct usb_fs_isoc_schedule *fss_b; - struct usb_hub *hs_hub; + struct usb_xfer *xfer; + struct usb_xfer *pipe_xfer; + struct usb_bus *bus; + usb_frlength_t len; + usb_frlength_t data_len; + uint16_t delta; + uint16_t slot; + uint8_t retval; - isoc_time = usb_isoc_time_expand(udev->bus, isoc_time); + data_len = 0; + slot = 0; - hs_hub = udev->parent_hs_hub->hub; + bus = isoc_xfer->xroot->bus; - if (hs_hub != NULL) { + TAILQ_FOREACH(xfer, &bus->intr_q.head, wait_entry) { - fss_a = hs_hub->fs_isoc_schedule + - (hs_hub->isoc_last_time % USB_ISOC_TIME_MAX); + /* skip self, if any */ - hs_hub->isoc_last_time = isoc_time; + if (xfer == isoc_xfer) + continue; + + /* check if this USB transfer is going through the same TT */ + + if (xfer->xroot->udev->parent_hs_hub != + isoc_xfer->xroot->udev->parent_hs_hub) { + continue; + } + if ((isoc_xfer->xroot->udev->parent_hs_hub-> + ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) && + (xfer->xroot->udev->hs_port_no != + isoc_xfer->xroot->udev->hs_port_no)) { + continue; + } + if (xfer->endpoint->methods != isoc_xfer->endpoint->methods) + continue; - fss_b = hs_hub->fs_isoc_schedule + - (isoc_time % USB_ISOC_TIME_MAX); + /* check if isoc_time is part of this transfer */ - fss_end = hs_hub->fs_isoc_schedule + USB_ISOC_TIME_MAX; + delta = xfer->isoc_time_complete - isoc_time; + if (delta > 0 && delta <= xfer->nframes) { + delta = xfer->nframes - delta; - *pp_start = hs_hub->fs_isoc_schedule; - *pp_end = fss_end; + len = xfer->frlengths[delta]; + len += 8; + len *= 7; + len /= 6; - while (fss_a != fss_b) { - if (fss_a == fss_end) { - fss_a = hs_hub->fs_isoc_schedule; + data_len += len; + } + + /* check double buffered transfers */ + + TAILQ_FOREACH(pipe_xfer, &xfer->endpoint->endpoint_q.head, + wait_entry) { + + /* skip self, if any */ + + if (pipe_xfer == isoc_xfer) continue; + + /* check if isoc_time is part of this transfer */ + + delta = pipe_xfer->isoc_time_complete - isoc_time; + if (delta > 0 && delta <= pipe_xfer->nframes) { + delta = pipe_xfer->nframes - delta; + + len = pipe_xfer->frlengths[delta]; + len += 8; + len *= 7; + len /= 6; + + data_len += len; } - usbd_fs_isoc_schedule_init_sub(fss_a); - fss_a++; } + } - } else { - - *pp_start = NULL; - *pp_end = NULL; + while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) { + data_len -= USB_FS_BYTES_PER_HS_UFRAME; + slot++; } - return (isoc_time); -} -#endif -/*------------------------------------------------------------------------* - * usbd_fs_isoc_schedule_alloc - * - * This function will allocate bandwidth for an isochronous FULL speed - * transaction in the FULL speed schedule. The microframe slot where - * the transaction should be started is stored in the byte pointed to - * by "pstart". The "len" argument specifies the length of the - * transaction in bytes. - * - * Returns: - * 0: Success - * Else: Error - *------------------------------------------------------------------------*/ -#if USB_HAVE_TT_SUPPORT -uint8_t -usbd_fs_isoc_schedule_alloc(struct usb_fs_isoc_schedule *fss, - uint8_t *pstart, uint16_t len) -{ - uint8_t slot = fss->frame_slot; + /* check for overflow */ + + if (slot >= USB_FS_ISOC_UFRAME_MAX) + return (255); - /* Compute overhead and bit-stuffing */ + retval = slot; - len += 8; + delta = isoc_xfer->isoc_time_complete - isoc_time; + if (delta > 0 && delta <= isoc_xfer->nframes) { + delta = isoc_xfer->nframes - delta; - len *= 7; - len /= 6; + len = isoc_xfer->frlengths[delta]; + len += 8; + len *= 7; + len /= 6; - if (len > fss->total_bytes) { - *pstart = 0; /* set some dummy value */ - return (1); /* error */ + data_len += len; } - if (len > 0) { - fss->total_bytes -= len; + while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) { + data_len -= USB_FS_BYTES_PER_HS_UFRAME; + slot++; + } - while (len >= fss->frame_bytes) { - len -= fss->frame_bytes; - fss->frame_bytes = USB_FS_BYTES_PER_HS_UFRAME; - fss->frame_slot++; - } + /* check for overflow */ - fss->frame_bytes -= len; - } - *pstart = slot; - return (0); /* success */ + if (slot >= USB_FS_ISOC_UFRAME_MAX) + return (255); + + return (retval); } #endif @@ -2046,7 +2077,6 @@ usb_peer_should_wakeup(struct usb_device *udev) (udev->pwr_save.write_refs != 0) || ((udev->pwr_save.read_refs != 0) && (udev->flags.usb_mode == USB_MODE_HOST) && - (usb_device_20_compatible(udev) != 0) && (usb_peer_can_wakeup(udev) == 0))); } @@ -2112,7 +2142,7 @@ usb_bus_powerd(struct usb_bus *bus) /* reset counters */ - mintime = 0 - 1; + mintime = (usb_ticks_t)-1; type_refs[0] = 0; type_refs[1] = 0; type_refs[2] = 0; @@ -2149,7 +2179,7 @@ usb_bus_powerd(struct usb_bus *bus) } } - if (mintime >= (1 * hz)) { + if (mintime >= (usb_ticks_t)(1 * hz)) { /* recompute power masks */ DPRINTF("Recomputing power masks\n"); bus->hw_power_state = 0; @@ -2226,10 +2256,18 @@ usb_dev_resume_peer(struct usb_device *udev) DPRINTFN(0, "Resuming port failed\n"); return; } + } else { + /* resume current port (Valid in Host and Device Mode) */ + err = usbd_req_set_port_link_state(udev->parent_hub, + NULL, udev->port_no, UPS_PORT_LS_U0); + if (err) { + DPRINTFN(0, "Resuming port failed\n"); + return; + } } /* resume settle time */ - usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY)); + usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay)); if (bus->methods->device_resume != NULL) { /* resume USB device on the USB controller */ @@ -2267,8 +2305,7 @@ usb_dev_resume_peer(struct usb_device *udev) usbd_sr_unlock(udev); /* check if peer has wakeup capability */ - if (usb_peer_can_wakeup(udev) && - usb_device_20_compatible(udev)) { + if (usb_peer_can_wakeup(udev)) { /* clear remote wakeup */ err = usbd_req_clear_device_feature(udev, NULL, UF_DEVICE_REMOTE_WAKEUP); @@ -2329,8 +2366,7 @@ repeat: } } - if (usb_peer_can_wakeup(udev) && - usb_device_20_compatible(udev)) { + if (usb_peer_can_wakeup(udev)) { /* * This request needs to be done before we set * "udev->flags.self_suspended": @@ -2362,8 +2398,7 @@ repeat: USB_BUS_UNLOCK(udev->bus); if (err != 0) { - if (usb_peer_can_wakeup(udev) && - usb_device_20_compatible(udev)) { + if (usb_peer_can_wakeup(udev)) { /* allow device to do remote wakeup */ err = usbd_req_clear_device_feature(udev, NULL, UF_DEVICE_REMOTE_WAKEUP); @@ -2385,7 +2420,7 @@ repeat: NULL, udev->port_no, UHF_PORT_SUSPEND); /* resume settle time */ - usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY)); + usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay)); } DPRINTF("Suspend was cancelled!\n"); return; @@ -2419,6 +2454,14 @@ repeat: DPRINTFN(0, "Suspending port failed\n"); return; } + } else { + /* suspend current port */ + err = usbd_req_set_port_link_state(udev->parent_hub, + NULL, udev->port_no, UPS_PORT_LS_U3); + if (err) { + DPRINTFN(0, "Suspending port failed\n"); + return; + } } udev = udev->parent_hub; @@ -2472,3 +2515,19 @@ usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode) /* use fixed power mode given by hardware driver */ return (temp); } + +/*------------------------------------------------------------------------* + * usbd_start_re_enumerate + * + * This function starts re-enumeration of the given USB device. This + * function does not need to be called BUS-locked. This function does + * not wait until the re-enumeration is completed. + *------------------------------------------------------------------------*/ +void +usbd_start_re_enumerate(struct usb_device *udev) +{ + if (udev->re_enumerate_wait == 0) { + udev->re_enumerate_wait = 1; + usb_needs_explore(udev->bus, 0); + } +} |