From c7a7a483ca5a4f3c63bbb549c728d2920073ac5d Mon Sep 17 00:00:00 2001 From: hselasky Date: Wed, 18 Jan 2012 07:57:17 +0000 Subject: [PATCH] MFC r230032, r230050, r230090, r230091 and r228493. - Various XHCI and USB 3.0 related issues. - USB 3.0 HUBs should work after this change. git-svn-id: svn://svn.freebsd.org/base/stable/9@230302 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- sys/dev/usb/controller/xhci.c | 28 ++++++---- sys/dev/usb/usb.h | 1 + sys/dev/usb/usb_hub.c | 62 ++++++++++++++++------ sys/dev/usb/usb_request.c | 98 ++++++++++++++++++++++++++++------- sys/dev/usb/usb_request.h | 2 + 5 files changed, 149 insertions(+), 42 deletions(-) diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c index b9d01c066..72ab55ad3 100644 --- a/sys/dev/usb/controller/xhci.c +++ b/sys/dev/usb/controller/xhci.c @@ -2211,9 +2211,10 @@ xhci_configure_device(struct usb_device *udev) struct usb_device *hubdev; uint32_t temp; uint32_t route; + uint32_t rh_port; uint8_t is_hub; uint8_t index; - uint8_t rh_port; + uint8_t depth; index = udev->controller_slot_id; @@ -2235,6 +2236,8 @@ xhci_configure_device(struct usb_device *udev) if (hubdev->parent_hub == NULL) break; + depth = hubdev->parent_hub->depth; + /* * NOTE: HS/FS/LS devices and the SS root HUB can have * more than 15 ports @@ -2242,17 +2245,18 @@ xhci_configure_device(struct usb_device *udev) rh_port = hubdev->port_no; - if (hubdev->parent_hub->parent_hub == NULL) + if (depth == 0) break; - route *= 16; - if (rh_port > 15) - route |= 15; - else - route |= rh_port; + rh_port = 15; + + if (depth < 6) + route |= rh_port << (4 * (depth - 1)); } + DPRINTF("Route=0x%08x\n", route); + temp = XHCI_SCTX_0_ROUTE_SET(route); switch (sc->sc_hw.devs[index].state) { @@ -2260,7 +2264,7 @@ xhci_configure_device(struct usb_device *udev) temp |= XHCI_SCTX_0_CTX_NUM_SET(XHCI_MAX_ENDPOINTS - 1); break; default: - temp = XHCI_SCTX_0_CTX_NUM_SET(1); + temp |= XHCI_SCTX_0_CTX_NUM_SET(1); break; } @@ -3063,6 +3067,7 @@ xhci_roothub_exec(struct usb_device *udev, case UHF_C_PORT_CONFIG_ERROR: XWRITE4(sc, oper, port, v | XHCI_PS_CEC); break; + case UHF_C_PORT_SUSPEND: case UHF_C_PORT_LINK_STATE: XWRITE4(sc, oper, port, v | XHCI_PS_PLC); break; @@ -3189,8 +3194,13 @@ xhci_roothub_exec(struct usb_device *udev, i |= UPS_OVERCURRENT_INDICATOR; if (v & XHCI_PS_PR) i |= UPS_RESET; - if (v & XHCI_PS_PP) + if (v & XHCI_PS_PP) { + /* + * The USB 3.0 RH is using the + * USB 2.0's power bit + */ i |= UPS_PORT_POWER; + } USETW(sc->sc_hub_desc.ps.wPortStatus, i); i = 0; diff --git a/sys/dev/usb/usb.h b/sys/dev/usb/usb.h index fb1c8e677..eac27620a 100644 --- a/sys/dev/usb/usb.h +++ b/sys/dev/usb/usb.h @@ -688,6 +688,7 @@ struct usb_port_status { #define UPS_PORT_LS_LOOPBACK 0x0B #define UPS_PORT_LS_RESUME 0x0F #define UPS_PORT_POWER 0x0100 +#define UPS_PORT_POWER_SS 0x0200 /* super-speed only */ #define UPS_LOW_SPEED 0x0200 #define UPS_HIGH_SPEED 0x0400 #define UPS_OTHER_SPEED 0x0600 /* currently FreeBSD specific */ diff --git a/sys/dev/usb/usb_hub.c b/sys/dev/usb/usb_hub.c index f48e5058c..e986df1dc 100644 --- a/sys/dev/usb/usb_hub.c +++ b/sys/dev/usb/usb_hub.c @@ -327,6 +327,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); @@ -369,10 +370,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)) { @@ -609,13 +627,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_RESUME: + 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; } } @@ -632,8 +652,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: @@ -775,7 +794,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 */ @@ -2064,7 +2084,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))); } @@ -2244,6 +2263,14 @@ 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 */ @@ -2285,8 +2312,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); @@ -2347,8 +2373,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": @@ -2380,8 +2405,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); @@ -2437,6 +2461,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; diff --git a/sys/dev/usb/usb_request.c b/sys/dev/usb/usb_request.c index d692b933f..c403a703b 100644 --- a/sys/dev/usb/usb_request.c +++ b/sys/dev/usb/usb_request.c @@ -785,12 +785,17 @@ 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; #endif + + DPRINTF("\n"); + /* clear any leftover port reset changes first */ usbd_req_clear_port_feature( udev, mtx, port, UHF_C_PORT_RESET); @@ -817,9 +822,6 @@ usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) #endif n = 0; while (1) { - uint16_t status; - uint16_t change; - #ifdef USB_DEBUG /* wait for the device to recover from reset */ usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); @@ -830,9 +832,9 @@ usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) n += USB_PORT_RESET_DELAY; #endif err = usbd_req_get_port_status(udev, mtx, &ps, port); - if (err) { + if (err) goto done; - } + status = UGETW(ps.wPortStatus); change = UGETW(ps.wPortChange); @@ -862,9 +864,9 @@ 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; @@ -898,21 +900,50 @@ 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; #endif - err = usbd_req_set_port_feature(udev, mtx, port, UHF_BH_PORT_RESET); - if (err) { + + DPRINTF("\n"); + + 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); } + + /* 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; + #ifdef USB_DEBUG /* range check input parameters */ pr_poll_delay = usb_pr_poll_delay; @@ -938,17 +969,20 @@ usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) n += USB_PORT_RESET_DELAY; #endif 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; @@ -959,9 +993,9 @@ 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; @@ -2004,6 +2038,10 @@ retry: } } + /* 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) { @@ -2164,3 +2202,27 @@ usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx, 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)); +} diff --git a/sys/dev/usb/usb_request.h b/sys/dev/usb/usb_request.h index ac7a7c160..74823af28 100644 --- a/sys/dev/usb/usb_request.h +++ b/sys/dev/usb/usb_request.h @@ -89,5 +89,7 @@ usb_error_t usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx, uint8_t port); 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); +usb_error_t usbd_req_set_port_link_state(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint8_t link_state); #endif /* _USB_REQUEST_H_ */ -- 2.45.0