From nobody Mon Nov 02 03:02:53 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4jHxMx62Khz6yVTG for ; Mon, 02 Nov 2026 03:02:53 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mx1.freebsd.org (Postfix) with ESMTPS id 4jHxMx43hjz4SR9 for ; Mon, 02 Nov 2026 03:02:53 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1793588573; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=fZjNTjYF4siZhPADX3Ul62J/uHXl/5JjtBs926rTwuI=; b=NFdizyVVowuUlZLD+xwEhNdpVFOEIitlF6GrNKyqygdpH1aefeUTphJGfnI0GrOtBUaVZL WBZ9/q4eAqCbFZzGLCcTQg2B0rKwy7aUXKLblmB13i3BwAvkjb5Cp6zozGOJktsy3Mbg1a nVXYPWYVhGoVpe4W/T4vLY/k5F2RRoPTwvVri0zIb9XdUKTL51VlHG/JwotroVpRnTTSWz 6viEl4v6+oBKyNSj7G4WItNpqLRbwThjtPeHtsnReeDbDK1jBMnlGeaYkilFzDdDYhbPjt q0OqWOfdE3+YAAq/wt6EqpO/l9B6VTsXmUtgcbp7xiCZOyhB8WOBdSL7pd8RVg== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1793588573; b=ua/EXSD4s6D+VHFkANdAjwNTkCcusaH+rFR9OdLnNboMQvelMJt41xqeMJ6h8lRrWrwxjl HlO2PCbKkeuI3FnKW1eEB6ugqQHf3n7VigN7TmI2nTbbYKDXQBv1Gc2T/0OCdlmdsU4m94 OqA6ZqjFhne81FBO683KPkw5/8LOUNOz0hkWwk/L41A93eP09kQQc69KiE5JcS5xods9o4 CaUGmGoHAS7VOyawQtj8jZicp09RBTABJKrzFI0mPrxdfWuEFDRZ+ASzDQiO46MaQnc6CI XfpveYacCfB68l+9kUu7hoZI6Z7R3hjgv62CJKSA0MUoApTdnDRGVWs/bBVFNg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1793588573; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=fZjNTjYF4siZhPADX3Ul62J/uHXl/5JjtBs926rTwuI=; b=niNzFxhPbCE5JuNAKhMZ/VVHctxQYFYcmHmd4TlDH8UYGlreJZUVLtrceQlZqdZ/Xt0JZn 44gJkF53BWlfuDhr2Fw5uVQdrAaLbNEAfYTrcPESQGYMmgnaPV4CxrRt9VqAqLq3osCV5Q Vo3yVHMsM350/JB5ak2tz01mApFCOX1YNhWnYV2MN4YtnhN0n3DhOzbIMFXn2mR6+WOD16 LeVJlif3o7n19y05YKpmCYnDMQVBHhfaT9UkWgfSJlKtRDGFHi3ZwiFve02UZci3vKFS5C yV07PWotAWZqCVA2zwY4crfvOUvPp7MaheeyqOVsFRLTJvGv3ZwsTGux9wjp0g== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4jHxMx3jqKzRPJ for ; Mon, 02 Nov 2026 03:02:53 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3b173 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 02 Nov 2026 03:02:53 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: ShengYi Hung Subject: git: fb470e8f1c58 - main - usb: Add support for SSP and SSPx2 hubs List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: Commit messages for the main branch of the src repository List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: aokblast X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: fb470e8f1c58f4fcdeb163d118fb1d6526da325d Auto-Submitted: auto-generated Date: Mon, 02 Nov 2026 03:02:53 +0000 Message-Id: <6ae7fd5d.3b173.3cd93bc@gitrepo.freebsd.org> The branch main has been updated by aokblast: URL: https://cgit.FreeBSD.org/src/commit/?id=fb470e8f1c58f4fcdeb163d118fb1d6526da325d commit fb470e8f1c58f4fcdeb163d118fb1d6526da325d Author: ShengYi Hung AuthorDate: 2026-05-23 07:00:45 +0000 Commit: ShengYi Hung CommitDate: 2026-11-02 03:02:11 +0000 usb: Add support for SSP and SSPx2 hubs An SSP hub reports its sublink speeds in the BOS descriptor, and Extended Port Status tells which one each port uses. Read it based on bcdUSB, since the ports of a hub can be faster than its upstream link. Without it, the ports are treated as SuperSpeed. Read the speed again in usbd_req_re_enumerate(), since a link can come back at another speed. Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D57204 --- sys/dev/usb/usb_hub.c | 226 ++++++++++++++++++++++++++++++++++++++++-- sys/dev/usb/usb_hub.h | 21 ++++ sys/dev/usb/usb_hub_private.h | 2 + sys/dev/usb/usb_request.c | 7 ++ 4 files changed, 248 insertions(+), 8 deletions(-) diff --git a/sys/dev/usb/usb_hub.c b/sys/dev/usb/usb_hub.c index ee9d8ab0c9bb..c8f5e8204391 100644 --- a/sys/dev/usb/usb_hub.c +++ b/sys/dev/usb/usb_hub.c @@ -588,6 +588,97 @@ uhub_read_port_status(struct uhub_softc *sc, uint8_t portno) return (err); } +static enum usb_dev_speed +usb_hub_sublink_status_to_speed(struct usb_hub_ss_sublinks *links, int id, + int lanes) +{ + uint32_t gbps; + uint8_t exp; + + DPRINTFN(4, "exp: %d, val: %d, lanes: %d\n", links->hub_status[id].exp, + links->hub_status[id].val, lanes); + + gbps = links->hub_status[id].val; + if (gbps == 0) + return (USB_SPEED_VARIABLE); + + for (exp = links->hub_status[id].exp; exp < 3; exp++) + gbps /= 1000; + + if (links->hub_status[id].lp != USB_DEVCAP_SSPLUS_LP_SSP) + return (USB_SPEED_SUPER); + if (lanes == 1) + return (gbps >= 10 ? USB_SPEED_SUPER_PLUS : USB_SPEED_SUPER); + if (gbps >= 10) + return (USB_SPEED_SUPER_PLUS_GEN2_X2); + if (gbps >= 5) + return (USB_SPEED_SUPER_PLUS_X2); + + return (USB_SPEED_VARIABLE); +} + +/*------------------------------------------------------------------------* + * uhub_get_ext_port_speed + * + * Returns: + * The speed of the link on the given HUB port. + * USB_SPEED_VARIABLE: The speed could not be determined. + *------------------------------------------------------------------------*/ +static enum usb_dev_speed +uhub_get_ext_port_speed(struct usb_device *udev, struct mtx *mtx, + uint8_t portno) +{ + struct usb_ext_status ps; + struct uhub_softc *sc; + enum usb_dev_speed rx, tx; + uint32_t status, rx_id, tx_id; + + sc = udev->hub->hubsoftc; + if ((sc->sc_flags & UHUB_FLAG_HAS_SUBLINKS) == 0) + return (USB_SPEED_VARIABLE); + if (usbd_req_get_ext_port_status(udev, mtx, &ps, portno) != 0) + return (USB_SPEED_VARIABLE); + + status = UGETDW(ps.dwExtPortStatus); + rx_id = UDS_RX_SUBLINK_SPEED_ID_BITS(status); + tx_id = UDS_TX_SUBLINK_SPEED_ID_BITS(status); + DPRINTFN(4, "port %d, rx_id: %d tx_id: %d\n", portno, rx_id, tx_id); + + /* The lane counts are zero based */ + rx = usb_hub_sublink_status_to_speed(&sc->sc_slinks, rx_id * 2, + UDS_RX_SUBLINK_LANE_COUNT_BITS(status) + 1); + tx = usb_hub_sublink_status_to_speed(&sc->sc_slinks, (tx_id * 2) + 1, + UDS_TX_SUBLINK_LANE_COUNT_BITS(status) + 1); + + return (MIN(rx, tx)); +} + +/*------------------------------------------------------------------------* + * uhub_update_ss_port_speed + * + * Reads the speed of the Enhanced SuperSpeed link on the given HUB port + * and records if a link above SuperSpeed has an unknown speed. + * + * Returns: + * The speed of the link, "speed" if it could not be determined. + *------------------------------------------------------------------------*/ +enum usb_dev_speed +uhub_update_ss_port_speed(struct usb_device *udev, struct mtx *mtx, + uint8_t portno, enum usb_dev_speed speed) +{ + enum usb_dev_speed ext_speed; + + if (udev->hub == NULL) + return (speed); + + ext_speed = uhub_get_ext_port_speed(udev, mtx, portno); + udev->hub->ports[portno - 1].ss_speed_unknown = + (ext_speed == USB_SPEED_VARIABLE && + udev->speed > USB_SPEED_SUPER); + + return (ext_speed != USB_SPEED_VARIABLE ? ext_speed : speed); +} + /*------------------------------------------------------------------------* * uhub_reattach_port * @@ -660,6 +751,9 @@ repeat: power_mask = UPS_PORT_POWER; break; case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER_PLUS_X2: + case USB_SPEED_SUPER_PLUS_GEN2_X2: if (udev->parent_hub == NULL) power_mask = 0; /* XXX undefined */ else @@ -753,9 +847,11 @@ repeat: case USB_SPEED_LOW: speed = USB_SPEED_LOW; break; + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER_PLUS_X2: + case USB_SPEED_SUPER_PLUS_GEN2_X2: case USB_SPEED_SUPER: if (udev->parent_hub == NULL) { - /* Root HUB - special case */ switch (sc->sc_st.port_status & UPS_OTHER_SPEED) { case 0: speed = USB_SPEED_FULL; @@ -773,22 +869,26 @@ repeat: } else { speed = USB_SPEED_SUPER; } + if (speed == USB_SPEED_SUPER) { + speed = uhub_update_ss_port_speed(udev, NULL, portno, + speed); + } break; default: /* same speed like parent */ speed = udev->speed; break; } - if (speed == USB_SPEED_SUPER) { - err = usbd_req_set_hub_u1_timeout(udev, NULL, - portno, 128 - (2 * udev->depth)); + if (speed >= USB_SPEED_SUPER) { + err = usbd_req_set_hub_u1_timeout(udev, NULL, portno, + 127 - (2 * udev->depth)); if (err) { DPRINTFN(0, "port %d U1 timeout " "failed, error=%s\n", portno, usbd_errstr(err)); } - err = usbd_req_set_hub_u2_timeout(udev, NULL, - portno, 128 - (2 * udev->depth)); + err = usbd_req_set_hub_u2_timeout(udev, NULL, portno, + 127 - (2 * udev->depth)); if (err) { DPRINTFN(0, "port %d U2 timeout " "failed, error=%s\n", @@ -973,6 +1073,9 @@ uhub_is_too_deep(struct usb_device *udev) return (1); break; case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER_PLUS_X2: + case USB_SPEED_SUPER_PLUS_GEN2_X2: if (udev->depth > USB_SS_HUB_DEPTH_MAX) return (1); break; @@ -1088,7 +1191,7 @@ uhub_explore(struct usb_device *udev) if (err != USB_ERR_NORMAL_COMPLETION) retval = err; } - if (udev->speed == USB_SPEED_SUPER && + if (udev->speed >= USB_SPEED_SUPER && (sc->sc_st.port_change & UPS_C_BH_PORT_RESET) != 0) { DPRINTF("Warm reset finished on port %u.\n", portno); err = usbd_req_clear_port_feature( @@ -1138,6 +1241,96 @@ uhub_probe(device_t dev) return (ENXIO); } +static usb_error_t +uhub_ss_caps_get(struct usb_bos_descriptor *bdesc, + struct usb_devcap_ss_plus_descriptor **desc) +{ + struct usb_bos_cap_descriptor *cap = NULL; + uint8_t i; + + *desc = NULL; + if (bdesc == NULL) + return (USB_ERR_INVAL); + + for (i = 0; i != bdesc->bNumDeviceCaps; i++) { + cap = usbd_bos_foreach(bdesc, cap); + if (cap == NULL) + break; + if (cap->bDescriptorType == UDESC_DEVICE_CAPABILITY && + cap->bDevCapabilityType == USB_DEVCAP_SUPERSPEED_PLUS) { + *desc = (struct usb_devcap_ss_plus_descriptor *)cap; + return (0); + } + } + return (USB_ERR_INVAL); +} + +static usb_error_t +uhub_ss_caps_to_hub_sslink(struct usb_devcap_ss_plus_descriptor *desc, + struct usb_hub_ss_sublinks *links) +{ + uint32_t attrs; + uint32_t n; + uint32_t link_val; + uint16_t val; + uint8_t exp, id, lp, st, first, last; + uint32_t i; + + if (desc->hdr.bLength < sizeof(*desc)) + return (USB_ERR_INVAL); + + attrs = UGETDW(desc->hdr.bmAttributes); + n = USB_DEVCAP_SSPLUS_SSAC_GET(attrs) + 1; + if (desc->hdr.bLength < sizeof(*desc) + n * sizeof(uDWord)) + return (USB_ERR_INVAL); + + for (i = 0; i < n; ++i) { + link_val = UGETDW(desc->bmSublinkSpeedAttr[i]); + id = USB_DEVCAP_SSPLUS_SSID_GET(link_val); + st = USB_DEVCAP_SSPLUS_ST_GET(link_val); + exp = USB_DEVCAP_SSPLUS_LSE_GET(link_val); + lp = USB_DEVCAP_SSPLUS_LP_GET(link_val); + val = USB_DEVCAP_SSPLUS_LSM_GET(link_val); + + /* + * A symmetric link applies to both directions + */ + first = id * 2 + ((st & 0x01) ? (st >> 1) : 0); + last = id * 2 + ((st & 0x01) ? (st >> 1) : 1); + for (; first <= last; first++) { + links->hub_status[first].exp = exp; + links->hub_status[first].lp = lp; + links->hub_status[first].val = val; + } + } + + return (USB_ERR_NORMAL_COMPLETION); +} + +static void +uhub_read_sublinks(struct uhub_softc *sc) +{ + struct usb_device *udev = sc->sc_udev; + struct usb_bos_descriptor *bdesc; + struct usb_devcap_ss_plus_descriptor *dev_desc; + usb_error_t err; + + err = usbd_req_get_bos_descriptor_full(udev, NULL, &bdesc); + if (err == 0) { + err = uhub_ss_caps_get(bdesc, &dev_desc); + if (err == 0) + err = uhub_ss_caps_to_hub_sslink(dev_desc, + &sc->sc_slinks); + free(bdesc, M_USBDEV); + } + if (err == 0) { + sc->sc_flags |= UHUB_FLAG_HAS_SUBLINKS; + } else if (udev->speed != USB_SPEED_SUPER) { + DPRINTFN(0, "Getting USB SS Plus sublinks failed, " + "error=%s\n", usbd_errstr(err)); + } +} + /* NOTE: The information returned by this function can be wrong. */ usb_error_t uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt) @@ -1172,7 +1365,9 @@ uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt) if (udev->speed == USB_SPEED_HIGH) tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3; break; - + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER_PLUS_X2: + case USB_SPEED_SUPER_PLUS_GEN2_X2: case USB_SPEED_SUPER: err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); if (err) { @@ -1302,7 +1497,19 @@ uhub_attach(device_t dev) } } break; + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER_PLUS_X2: + case USB_SPEED_SUPER_PLUS_GEN2_X2: case USB_SPEED_SUPER: + /* + * The ports of a HUB can be faster than its own upstream + * link, so check the revision instead of the speed. The + * HUB stays usable without sublink information, its ports + * are then treated as plain SuperSpeed. Only an SSP capable + * HUB is required to have it. + */ + if (UGETW(udev->ddesc.bcdUSB) >= 0x0310) + uhub_read_sublinks(sc); if (udev->parent_hub != NULL) { err = usbd_req_set_hub_depth(udev, NULL, udev->depth - 1); @@ -1474,6 +1681,9 @@ uhub_attach(device_t dev) removable++; break; case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER_PLUS_X2: + case USB_SPEED_SUPER_PLUS_GEN2_X2: if (!UHD_NOT_REMOV(&hubdesc30, portno)) removable++; break; diff --git a/sys/dev/usb/usb_hub.h b/sys/dev/usb/usb_hub.h index ffeabc7c0f05..23ec62697714 100644 --- a/sys/dev/usb/usb_hub.h +++ b/sys/dev/usb/usb_hub.h @@ -36,11 +36,30 @@ struct usb_port { #define USB_RESTART_MAX 5 uint8_t device_index; /* zero means not valid */ enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t ss_speed_unknown; /* SSP link of unknown speed */ #if USB_HAVE_TT_SUPPORT struct usb_device_request req_reset_tt __aligned(4); #endif }; +#define UHUB_SS_SUBLINKS_MAX_IDS 16 + +/* + * The following structure define the usb_sublink_information + */ +struct usb_hub_ss_sublinks { + struct { + uint8_t exp; + uint8_t lp; + uint16_t val; + /* + * Max 4 bit. For each we have 1 rx and 1 tx. + * IDs may be sparse; val is zero if not advertised. + * val is the speed of one lane, not of the link. + */ + } hub_status[(UHUB_SS_SUBLINKS_MAX_IDS) * 2]; +}; + /* * The following structure defines an USB HUB. */ @@ -77,5 +96,7 @@ void usb_bus_powerd(struct usb_bus *bus); void uhub_root_intr(struct usb_bus *, const uint8_t *, uint8_t); usb_error_t uhub_query_info(struct usb_device *, uint8_t *, uint8_t *); void uhub_explore_handle_re_enumerate(struct usb_device *); +enum usb_dev_speed uhub_update_ss_port_speed(struct usb_device *, struct mtx *, + uint8_t, enum usb_dev_speed); #endif /* _USB_HUB_H_ */ diff --git a/sys/dev/usb/usb_hub_private.h b/sys/dev/usb/usb_hub_private.h index 5ca4d1ca0a2a..e0d641bf3812 100644 --- a/sys/dev/usb/usb_hub_private.h +++ b/sys/dev/usb/usb_hub_private.h @@ -65,6 +65,8 @@ struct uhub_softc { #define UHUB_USB_PORT_ERRORS_MAX 4 uint8_t sc_flags; #define UHUB_FLAG_DID_EXPLORE 0x01 +#define UHUB_FLAG_HAS_SUBLINKS 0x02 /* sc_slinks is valid */ + struct usb_hub_ss_sublinks sc_slinks; }; struct hub_result { struct usb_device *udev; diff --git a/sys/dev/usb/usb_request.c b/sys/dev/usb/usb_request.c index 2a57c75a85d2..f67c9bf51761 100644 --- a/sys/dev/usb/usb_request.c +++ b/sys/dev/usb/usb_request.c @@ -68,6 +68,7 @@ #include #include +#include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ @@ -2198,6 +2199,12 @@ retry: goto done; } + /* The link may have been trained at another speed */ + if (udev->speed >= USB_SPEED_SUPER) { + udev->speed = uhub_update_ss_port_speed(parent_hub, mtx, + udev->port_no, udev->speed); + } + /* * After that the port has been reset our device should be at * address zero: