diff options
Diffstat (limited to 'freebsd/sys/dev/usb/usb_request.c')
-rw-r--r-- | freebsd/sys/dev/usb/usb_request.c | 365 |
1 files changed, 247 insertions, 118 deletions
diff --git a/freebsd/sys/dev/usb/usb_request.c b/freebsd/sys/dev/usb/usb_request.c index 8d17c202..cb3b030c 100644 --- a/freebsd/sys/dev/usb/usb_request.c +++ b/freebsd/sys/dev/usb/usb_request.c @@ -36,7 +36,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> @@ -70,22 +69,19 @@ #include <dev/usb/usb_bus.h> #include <sys/ctype.h> -#ifdef USB_DEBUG -static int usb_pr_poll_delay = USB_PORT_RESET_DELAY; -static int usb_pr_recovery_delay = USB_PORT_RESET_RECOVERY; +static int usb_no_cs_fail; -SYSCTL_INT(_hw_usb, OID_AUTO, pr_poll_delay, CTLFLAG_RW, - &usb_pr_poll_delay, 0, "USB port reset poll delay in ms"); -SYSCTL_INT(_hw_usb, OID_AUTO, pr_recovery_delay, CTLFLAG_RW, - &usb_pr_recovery_delay, 0, "USB port reset recovery delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, no_cs_fail, CTLFLAG_RW, + &usb_no_cs_fail, 0, "USB clear stall failures are ignored, if set"); +#ifdef USB_DEBUG #ifdef USB_REQ_DEBUG /* The following structures are used in connection to fault injection. */ struct usb_ctrl_debug { int bus_index; /* target bus */ int dev_index; /* target address */ int ds_fail; /* fail data stage */ - int ss_fail; /* fail data stage */ + int ss_fail; /* fail status stage */ int ds_delay; /* data stage delay in ms */ int ss_delay; /* status stage delay in ms */ int bmRequestType_value; @@ -241,6 +237,10 @@ usb_do_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: +tr_transferred: + /* reset error counter */ + udev->clear_stall_errors = 0; + if (ep == NULL) goto tr_setup; /* device was unconfigured */ if (ep->edesc && @@ -292,8 +292,30 @@ tr_setup: goto tr_setup; default: - if (xfer->error == USB_ERR_CANCELLED) { + if (error == USB_ERR_CANCELLED) break; + + DPRINTF("Clear stall failed.\n"); + + /* + * Some VMs like VirtualBox always return failure on + * clear-stall which we sometimes should just ignore. + */ + if (usb_no_cs_fail) + goto tr_transferred; + if (udev->clear_stall_errors == USB_CS_RESET_LIMIT) + goto tr_setup; + + if (error == USB_ERR_TIMEOUT) { + udev->clear_stall_errors = USB_CS_RESET_LIMIT; + DPRINTF("Trying to re-enumerate.\n"); + usbd_start_re_enumerate(udev); + } else { + udev->clear_stall_errors++; + if (udev->clear_stall_errors == USB_CS_RESET_LIMIT) { + DPRINTF("Trying to re-enumerate.\n"); + usbd_start_re_enumerate(udev); + } } goto tr_setup; } @@ -362,9 +384,8 @@ usbd_get_hr_func(struct usb_device *udev) * than 30 seconds is treated like a 30 second timeout. This USB stack * does not allow control requests without a timeout. * - * NOTE: This function is thread safe. All calls to - * "usbd_do_request_flags" will be serialised by the use of an - * internal "sx_lock". + * NOTE: This function is thread safe. All calls to "usbd_do_request_flags" + * will be serialized by the use of the USB device enumeration lock. * * Returns: * 0: Success @@ -388,7 +409,7 @@ usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, uint16_t length; uint16_t temp; uint16_t acttemp; - uint8_t enum_locked; + uint8_t do_unlock; if (timeout < 50) { /* timeout is too small */ @@ -400,8 +421,6 @@ usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, } length = UGETW(req->wLength); - enum_locked = usbd_enum_is_locked(udev); - DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x " "wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n", udev, req->bmRequestType, req->bRequest, @@ -432,17 +451,16 @@ usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, } /* - * We need to allow suspend and resume at this point, else the - * control transfer will timeout if the device is suspended! + * Grab the USB device enumeration SX-lock serialization is + * achieved when multiple threads are involved: */ - if (enum_locked) - usbd_sr_unlock(udev); + do_unlock = usbd_enum_lock(udev); /* - * Grab the default sx-lock so that serialisation - * is achieved when multiple threads are involved: + * We need to allow suspend and resume at this point, else the + * control transfer will timeout if the device is suspended! */ - sx_xlock(&udev->ctrl_sx); + usbd_sr_unlock(udev); hr_func = usbd_get_hr_func(udev); @@ -489,7 +507,7 @@ usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, } } else #endif - bcopy(desc, data, length); + memcpy(data, desc, length); } goto done; /* success */ } @@ -686,10 +704,10 @@ usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, USB_XFER_UNLOCK(xfer); done: - sx_xunlock(&udev->ctrl_sx); + usbd_sr_lock(udev); - if (enum_locked) - usbd_sr_lock(udev); + if (do_unlock) + usbd_enum_unlock(udev); if ((mtx != NULL) && (mtx != &Giant)) mtx_lock(mtx); @@ -757,52 +775,52 @@ usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) struct usb_port_status ps; usb_error_t err; uint16_t n; + uint16_t status; + uint16_t change; -#ifdef USB_DEBUG - uint16_t pr_poll_delay; - uint16_t pr_recovery_delay; + DPRINTF("\n"); -#endif - err = usbd_req_set_port_feature(udev, mtx, port, UHF_PORT_RESET); - if (err) { + /* clear any leftover port reset changes first */ + usbd_req_clear_port_feature( + udev, mtx, port, UHF_C_PORT_RESET); + + /* assert port reset on the given port */ + err = usbd_req_set_port_feature( + udev, mtx, port, UHF_PORT_RESET); + + /* check for errors */ + if (err) goto done; - } #ifdef USB_DEBUG - /* range check input parameters */ - pr_poll_delay = usb_pr_poll_delay; - if (pr_poll_delay < 1) { - pr_poll_delay = 1; - } else if (pr_poll_delay > 1000) { - pr_poll_delay = 1000; - } - pr_recovery_delay = usb_pr_recovery_delay; - if (pr_recovery_delay > 1000) { - pr_recovery_delay = 1000; - } #endif n = 0; while (1) { -#ifdef USB_DEBUG /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); - n += pr_poll_delay; -#else - /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); - n += USB_PORT_RESET_DELAY; -#endif + usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_delay)); + n += usb_port_reset_delay; err = usbd_req_get_port_status(udev, mtx, &ps, port); - if (err) { + if (err) goto done; - } + + status = UGETW(ps.wPortStatus); + change = UGETW(ps.wPortChange); + /* if the device disappeared, just give up */ - if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) { + if (!(status & UPS_CURRENT_CONNECT_STATUS)) goto done; - } + /* check if reset is complete */ - if (UGETW(ps.wPortChange) & UPS_C_PORT_RESET) { + if (change & UPS_C_PORT_RESET) break; - } + + /* + * Some Virtual Machines like VirtualBox 4.x fail to + * generate a port reset change event. Check if reset + * is no longer asserted. + */ + if (!(status & UPS_RESET)) + break; + /* check for timeout */ if (n > 1000) { n = 0; @@ -813,21 +831,16 @@ usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) /* clear port reset first */ err = usbd_req_clear_port_feature( udev, mtx, port, UHF_C_PORT_RESET); - if (err) { + if (err) goto done; - } + /* check for timeout */ if (n == 0) { err = USB_ERR_TIMEOUT; goto done; } -#ifdef USB_DEBUG - /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay)); -#else /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY)); -#endif + usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_recovery)); done: DPRINTFN(2, "port %d reset returning error=%s\n", @@ -849,57 +862,64 @@ done: * disabled. *------------------------------------------------------------------------*/ usb_error_t -usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) +usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx, + uint8_t port) { struct usb_port_status ps; usb_error_t err; uint16_t n; + uint16_t status; + uint16_t change; -#ifdef USB_DEBUG - uint16_t pr_poll_delay; - uint16_t pr_recovery_delay; + DPRINTF("\n"); -#endif - err = usbd_req_set_port_feature(udev, mtx, port, UHF_BH_PORT_RESET); - if (err) { + err = usbd_req_get_port_status(udev, mtx, &ps, port); + if (err) goto done; + + status = UGETW(ps.wPortStatus); + + switch (UPS_PORT_LINK_STATE_GET(status)) { + case UPS_PORT_LS_U3: + case UPS_PORT_LS_COMP_MODE: + case UPS_PORT_LS_LOOPBACK: + case UPS_PORT_LS_SS_INA: + break; + default: + DPRINTF("Wrong state for warm reset\n"); + return (0); } -#ifdef USB_DEBUG - /* range check input parameters */ - pr_poll_delay = usb_pr_poll_delay; - if (pr_poll_delay < 1) { - pr_poll_delay = 1; - } else if (pr_poll_delay > 1000) { - pr_poll_delay = 1000; - } - pr_recovery_delay = usb_pr_recovery_delay; - if (pr_recovery_delay > 1000) { - pr_recovery_delay = 1000; - } -#endif + + /* clear any leftover warm port reset changes first */ + usbd_req_clear_port_feature(udev, mtx, + port, UHF_C_BH_PORT_RESET); + + /* set warm port reset */ + err = usbd_req_set_port_feature(udev, mtx, + port, UHF_BH_PORT_RESET); + if (err) + goto done; + n = 0; while (1) { -#ifdef USB_DEBUG - /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); - n += pr_poll_delay; -#else /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); - n += USB_PORT_RESET_DELAY; -#endif + usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_delay)); + n += usb_port_reset_delay; err = usbd_req_get_port_status(udev, mtx, &ps, port); - if (err) { + if (err) goto done; - } + + status = UGETW(ps.wPortStatus); + change = UGETW(ps.wPortChange); + /* if the device disappeared, just give up */ - if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) { + if (!(status & UPS_CURRENT_CONNECT_STATUS)) goto done; - } + /* check if reset is complete */ - if (UGETW(ps.wPortChange) & UPS_C_BH_PORT_RESET) { + if (change & UPS_C_BH_PORT_RESET) break; - } + /* check for timeout */ if (n > 1000) { n = 0; @@ -910,21 +930,16 @@ usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) /* clear port reset first */ err = usbd_req_clear_port_feature( udev, mtx, port, UHF_C_BH_PORT_RESET); - if (err) { + if (err) goto done; - } + /* check for timeout */ if (n == 0) { err = USB_ERR_TIMEOUT; goto done; } -#ifdef USB_DEBUG - /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay)); -#else /* wait for the device to recover from reset */ - usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY)); -#endif + usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_recovery)); done: DPRINTFN(2, "port %d warm reset returning error=%s\n", @@ -1119,14 +1134,21 @@ usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, char *buf, } /* - * Filter by default - we don't allow greater and less than - * signs because they might confuse the dmesg printouts! + * Filter by default - We only allow alphanumerical + * and a few more to avoid any problems with scripts + * and daemons. */ - if ((*s == '<') || (*s == '>') || (!isprint(*s))) { - /* silently skip bad character */ - continue; + if (isalpha(*s) || + isdigit(*s) || + *s == '-' || + *s == '+' || + *s == ' ' || + *s == '.' || + *s == ',') { + /* allowed */ + s++; } - s++; + /* silently skip bad character */ } *s = 0; /* zero terminate resulting string */ return (USB_ERR_NORMAL_COMPLETION); @@ -1222,7 +1244,7 @@ usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx, goto done; } /* Extra sanity checking */ - if (UGETW(d->wTotalLength) < sizeof(*d)) { + if (UGETW(d->wTotalLength) < (uint16_t)sizeof(*d)) { err = USB_ERR_INVAL; } done: @@ -1476,7 +1498,7 @@ usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t addr) done: /* allow device time to set new address */ usb_pause_mtx(mtx, - USB_MS_TO_TICKS(USB_SET_ADDRESS_SETTLE)); + USB_MS_TO_TICKS(usb_set_address_settle)); return (err); } @@ -1725,7 +1747,7 @@ usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, void *data, struct usb_interface *iface = usbd_get_iface(udev, iface_index); struct usb_device_request req; - if ((iface == NULL) || (iface->idesc == NULL) || (id == 0)) { + if ((iface == NULL) || (iface->idesc == NULL)) { return (USB_ERR_INVAL); } DPRINTFN(5, "len=%d\n", len); @@ -1932,6 +1954,27 @@ usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx) return (USB_ERR_INVAL); } retry: + /* + * Try to reset the High Speed parent HUB of a LOW- or FULL- + * speed device, if any. + */ + if (udev->parent_hs_hub != NULL && + udev->speed != USB_SPEED_HIGH) { + DPRINTF("Trying to reset parent High Speed TT.\n"); + err = usbd_req_reset_tt(udev->parent_hs_hub, NULL, + udev->hs_port_no); + if (err) { + DPRINTF("Resetting parent High " + "Speed TT failed (%s).\n", + usbd_errstr(err)); + } + } + + /* Try to warm reset first */ + if (parent_hub->speed == USB_SPEED_SUPER) + usbd_req_warm_reset_port(parent_hub, mtx, udev->port_no); + + /* Try to reset the parent HUB port. */ err = usbd_req_reset_port(parent_hub, mtx, udev->port_no); if (err) { DPRINTFN(0, "addr=%d, port reset failed, %s\n", @@ -2029,3 +2072,89 @@ usbd_req_set_device_feature(struct usb_device *udev, struct mtx *mtx, USETW(req.wLength, 0); return (usbd_do_request(udev, mtx, &req, 0)); } + +/*------------------------------------------------------------------------* + * usbd_req_reset_tt + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx, + uint8_t port) +{ + struct usb_device_request req; + + /* For single TT HUBs the port should be 1 */ + + if (udev->ddesc.bDeviceClass == UDCLASS_HUB && + udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT) + port = 1; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_RESET_TT; + USETW(req.wValue, 0); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_tt_buffer + * + * For single TT HUBs the port should be 1. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint) +{ + struct usb_device_request req; + uint16_t wValue; + + /* For single TT HUBs the port should be 1 */ + + if (udev->ddesc.bDeviceClass == UDCLASS_HUB && + udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT) + port = 1; + + wValue = (endpoint & 0xF) | ((addr & 0x7F) << 4) | + ((endpoint & 0x80) << 8) | ((type & 3) << 12); + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_TT_BUFFER; + USETW(req.wValue, wValue); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_port_link_state + * + * USB 3.0 specific request + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_port_link_state(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t link_state) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UHF_PORT_LINK_STATE); + req.wIndex[0] = port; + req.wIndex[1] = link_state; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} |