git: 20b19982545e - main - usb: support SSP and SSPx2 xHCI
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 02 Nov 2026 03:02:54 UTC
The branch main has been updated by aokblast:
URL: https://cgit.FreeBSD.org/src/commit/?id=20b19982545eb96160a20d0224f4b074f5fcb9af
commit 20b19982545eb96160a20d0224f4b074f5fcb9af
Author: ShengYi Hung <aokblast@FreeBSD.org>
AuthorDate: 2025-09-01 14:54:11 +0000
Commit: ShengYi Hung <aokblast@FreeBSD.org>
CommitDate: 2026-11-02 03:02:11 +0000
usb: support SSP and SSPx2 xHCI
Only the default PSI IDs are supported, other controllers fall back to
USB 3.0. Record the speed IDs the controller has: the root hub BOS
lists those, and a link without one uses the closest lower ID.
Per xHCI 6.2.2, take the parent hub slot ID and port number of SS and
SSP devices from the closest higher-rank hub.
Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D52347
---
sys/dev/usb/controller/xhci.c | 305 ++++++++++++++++++++++++++++++++++-----
sys/dev/usb/controller/xhci.h | 14 ++
sys/dev/usb/controller/xhcireg.h | 12 ++
3 files changed, 294 insertions(+), 37 deletions(-)
diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c
index acc6eee525b0..3f3ab299647b 100644
--- a/sys/dev/usb/controller/xhci.c
+++ b/sys/dev/usb/controller/xhci.c
@@ -485,6 +485,60 @@ xhci_reset_controller(struct xhci_softc *sc)
return (0);
}
+static bool
+xhci_parse_protocol(struct xhci_softc *sc, uint32_t eecp, uint32_t eec)
+{
+ enum usb_revision rev;
+ uint32_t val;
+ uint16_t psiv;
+ uint8_t minor;
+ uint8_t psic;
+ uint8_t i;
+
+ minor = XHCI_XECP_MINOR(eec);
+ psic = XHCI_XECP_PSIC_GET(XREAD4(sc, capa, eecp + 0x08));
+ psiv = XHCI_PSIV_BIT(XHCI_PS_SPEED_SS);
+ rev = USB_REV_3_0;
+
+ if (psic == 0) {
+ /*
+ * The port revision only tells which speeds the port may
+ * have, so be careful about what we claim for the bus.
+ */
+ if (minor >= 0x10) {
+ psiv |= XHCI_PSIV_BIT(XHCI_PS_SPEED_SS_PLUS_GEN2);
+ rev = USB_REV_3_1;
+ }
+ if (minor >= 0x20) {
+ psiv |= XHCI_PSIV_BIT(XHCI_PS_SPEED_SS_PLUS_GEN1_x2) |
+ XHCI_PSIV_BIT(XHCI_PS_SPEED_SS_PLUS_GEN2_x2);
+ }
+ }
+
+ for (i = 0; i != psic; i++) {
+ val = XREAD4(sc, capa, eecp + 0x10 + i * 4);
+ val &= ~(XHCI_XECP_PSI_RSVD | XHCI_XECP_PSI_LP);
+ switch (val) {
+ case 0x00050134: /* 4: 5 Gbps Gen1x1 */
+ break;
+ case 0x000a0135: /* 5: 10 Gbps Gen2x1 */
+ case 0x000a0136: /* 6: 10 Gbps Gen1x2 (5G/lane x 2) */
+ rev = MAX(rev, USB_REV_3_1);
+ break;
+ case 0x00140137: /* 7: 20 Gbps Gen2x2 (10G/lane x 2) */
+ rev = USB_REV_3_2;
+ break;
+ default:
+ return (false);
+ }
+ psiv |= XHCI_PSIV_BIT(XHCI_XECP_PSIV_GET(val));
+ }
+
+ sc->sc_ss_psiv |= psiv;
+ sc->sc_bus.usbrev = MAX(sc->sc_bus.usbrev, rev);
+ return (true);
+}
+
usb_error_t
xhci_init(struct xhci_softc *sc, device_t self, uint8_t dma32)
{
@@ -497,6 +551,7 @@ xhci_init(struct xhci_softc *sc, device_t self, uint8_t dma32)
/* set the bus revision */
sc->sc_bus.usbrev = USB_REV_3_0;
+ sc->sc_ss_psiv = XHCI_PSIV_BIT(XHCI_PS_SPEED_SS);
/* set up the bus struct */
sc->sc_bus.methods = &xhci_bus_methods;
@@ -642,6 +697,7 @@ xhci_get_xecp(struct xhci_softc *sc)
uint32_t eec;
uint32_t eecp;
bool first = true;
+ bool default_psi = true;
hccp1 = XREAD4(sc, capa, XHCI_HCCPARAMS1);
@@ -686,6 +742,9 @@ xhci_get_xecp(struct xhci_softc *sc)
break;
case XHCI_ID_PROTOCOLS:
printf("PROTO");
+ if (XHCI_XECP_MAJOR(eec) == 0x03 &&
+ !xhci_parse_protocol(sc, eecp, eec))
+ default_psi = false;
break;
case XHCI_ID_POWER_MGMT:
printf("POWER");
@@ -714,6 +773,13 @@ xhci_get_xecp(struct xhci_softc *sc)
first = false;
}
printf(">\n");
+
+ if (!default_psi) {
+ device_printf(sc->sc_bus.parent,
+ "xECP: not support non-default mapping, fallback to usb 3.0\n");
+ sc->sc_bus.usbrev = USB_REV_3_0;
+ sc->sc_ss_psiv = XHCI_PSIV_BIT(XHCI_PS_SPEED_SS);
+ }
}
static void
@@ -2531,6 +2597,9 @@ xhci_configure_endpoint(struct usb_device *udev,
switch (udev->speed) {
case USB_SPEED_SUPER:
+ case USB_SPEED_SUPER_PLUS:
+ case USB_SPEED_SUPER_PLUS_X2:
+ case USB_SPEED_SUPER_PLUS_GEN2_X2:
if (mult > 3)
mult = 3;
temp |= XHCI_EPCTX_0_MULT_SET(mult - 1);
@@ -2650,6 +2719,67 @@ xhci_configure_endpoint_by_xfer(struct usb_xfer *xfer)
xfer->max_frame_size, xfer->endpoint->ep_mode));
}
+static uint8_t
+xhci_ss_psiv(struct xhci_softc *sc, enum usb_dev_speed speed)
+{
+ switch (speed) {
+ case USB_SPEED_SUPER_PLUS_GEN2_X2:
+ if (sc->sc_ss_psiv &
+ XHCI_PSIV_BIT(XHCI_PS_SPEED_SS_PLUS_GEN2_x2))
+ return (XHCI_PS_SPEED_SS_PLUS_GEN2_x2);
+ /* FALLTHROUGH */
+ case USB_SPEED_SUPER_PLUS:
+ if (sc->sc_ss_psiv & XHCI_PSIV_BIT(XHCI_PS_SPEED_SS_PLUS_GEN2))
+ return (XHCI_PS_SPEED_SS_PLUS_GEN2);
+ /* FALLTHROUGH */
+ case USB_SPEED_SUPER_PLUS_X2:
+ if (sc->sc_ss_psiv &
+ XHCI_PSIV_BIT(XHCI_PS_SPEED_SS_PLUS_GEN1_x2))
+ return (XHCI_PS_SPEED_SS_PLUS_GEN1_x2);
+ /* FALLTHROUGH */
+ default:
+ return (XHCI_PS_SPEED_SS);
+ }
+}
+
+static enum usb_dev_speed
+xhci_ss_highest(struct xhci_softc *sc)
+{
+ switch (xhci_ss_psiv(sc, USB_SPEED_SUPER_PLUS_GEN2_X2)) {
+ case XHCI_PS_SPEED_SS_PLUS_GEN2_x2:
+ return (USB_SPEED_SUPER_PLUS_GEN2_X2);
+ case XHCI_PS_SPEED_SS_PLUS_GEN2:
+ return (USB_SPEED_SUPER_PLUS);
+ case XHCI_PS_SPEED_SS_PLUS_GEN1_x2:
+ return (USB_SPEED_SUPER_PLUS_X2);
+ default:
+ return (USB_SPEED_SUPER);
+ }
+}
+
+static struct usb_device *
+xhci_ss_parent_hub(struct xhci_softc *sc, struct usb_device *udev,
+ uint8_t *port_no)
+{
+ struct usb_device *adev = udev;
+ struct usb_device *hubdev = udev->parent_hub;
+
+ if (udev->speed >= xhci_ss_highest(sc))
+ return (NULL);
+
+ for (; hubdev != NULL && hubdev->parent_hub != NULL;
+ hubdev = hubdev->parent_hub) {
+ if (hubdev->hub->ports[adev->port_index].ss_speed_unknown)
+ return (NULL);
+ if (hubdev->speed > udev->speed) {
+ *port_no = adev->port_no;
+ return (hubdev);
+ }
+ adev = hubdev;
+ }
+ return (NULL);
+}
+
static usb_error_t
xhci_configure_device(struct usb_device *udev)
{
@@ -2664,6 +2794,7 @@ xhci_configure_device(struct usb_device *udev)
uint8_t is_hub;
uint8_t index;
uint8_t depth;
+ uint8_t port_no;
index = udev->controller_slot_id;
@@ -2737,13 +2868,12 @@ xhci_configure_device(struct usb_device *udev)
}
break;
default:
- temp |= XHCI_SCTX_0_SPEED_SET(4);
+ temp |= XHCI_SCTX_0_SPEED_SET(xhci_ss_psiv(sc, udev->speed));
break;
}
is_hub = sc->sc_hw.devs[index].nports != 0 &&
- (udev->speed == USB_SPEED_SUPER ||
- udev->speed == USB_SPEED_HIGH);
+ (udev->speed >= USB_SPEED_HIGH);
if (is_hub)
temp |= XHCI_SCTX_0_HUB_SET(1);
@@ -2779,6 +2909,23 @@ xhci_configure_device(struct usb_device *udev)
udev->hs_port_no);
}
break;
+ case USB_SPEED_SUPER:
+ case USB_SPEED_SUPER_PLUS:
+ case USB_SPEED_SUPER_PLUS_X2:
+ case USB_SPEED_SUPER_PLUS_GEN2_X2:
+ /*
+ * Per xHCI 1.2 ยง6.2.2 the parent hub slot ID and port number
+ * of a SS/SSP device identify the hub which isolates it, that
+ * is the closest higher rank hub. They must be zero if there
+ * is none. The root hub has no slot and never qualifies.
+ */
+ hubdev = xhci_ss_parent_hub(sc, udev, &port_no);
+ if (hubdev != NULL) {
+ temp |= XHCI_SCTX_2_TT_HUB_SID_SET(
+ hubdev->controller_slot_id);
+ temp |= XHCI_SCTX_2_TT_PORT_NUM_SET(port_no);
+ }
+ break;
default:
break;
}
@@ -3277,39 +3424,95 @@ struct usb_device_descriptor xhci_devd =
.bNumConfigurations = 1, /* # of configurations */
};
+/* The capabilities every root HUB BOS descriptor starts with */
+#define XHCI_BOSD_COMMON(desc, ncaps) \
+ .bosd = { \
+ .bLength = sizeof(desc.bosd), \
+ .bDescriptorType = UDESC_BOS, \
+ HSETW(.wTotalLength, sizeof(desc)), \
+ .bNumDeviceCaps = (ncaps), \
+ }, \
+ .usb2extd = { \
+ .bLength = sizeof(desc.usb2extd), \
+ .bDescriptorType = UDESC_DEVICE_CAPABILITY, \
+ .bDevCapabilityType = 2, \
+ .bmAttributes[0] = 2, \
+ }, \
+ .usbdcd = { \
+ .bLength = sizeof(desc.usbdcd), \
+ .bDescriptorType = UDESC_DEVICE_CAPABILITY, \
+ .bDevCapabilityType = 3, \
+ .bmAttributes = 0, /* XXX */ \
+ HSETW(.wSpeedsSupported, 0x000C), \
+ .bFunctionalitySupport = 8, \
+ .bU1DevExitLat = 255, /* dummy - not used */ \
+ .wU2DevExitLat = { 0x00, 0x08 }, \
+ }, \
+ .cidd = { \
+ .bLength = sizeof(desc.cidd), \
+ .bDescriptorType = UDESC_DEVICE_CAPABILITY, \
+ .bDevCapabilityType = 4, \
+ .bReserved = 0, \
+ .bContainerID = 0, /* XXX */ \
+ }
+
static const
-struct xhci_bos_desc xhci_bosd = {
- .bosd = {
- .bLength = sizeof(xhci_bosd.bosd),
- .bDescriptorType = UDESC_BOS,
- HSETW(.wTotalLength, sizeof(xhci_bosd)),
- .bNumDeviceCaps = 3,
- },
- .usb2extd = {
- .bLength = sizeof(xhci_bosd.usb2extd),
- .bDescriptorType = 1,
- .bDevCapabilityType = 2,
- .bmAttributes[0] = 2,
- },
- .usbdcd = {
- .bLength = sizeof(xhci_bosd.usbdcd),
+struct xhci_bos_desc xhci_30_bosd = {
+ XHCI_BOSD_COMMON(xhci_30_bosd, 3),
+};
+
+static const
+struct xhci_ssp_bos_desc xhci_ssp_bosd = {
+ XHCI_BOSD_COMMON(xhci_ssp_bosd, 4),
+ .ssplus = {
.bDescriptorType = UDESC_DEVICE_CAPABILITY,
- .bDevCapabilityType = 3,
- .bmAttributes = 0, /* XXX */
- HSETW(.wSpeedsSupported, 0x000C),
- .bFunctionalitySupport = 8,
- .bU1DevExitLat = 255, /* dummy - not used */
- .wU2DevExitLat = { 0x00, 0x08 },
- },
- .cidd = {
- .bLength = sizeof(xhci_bosd.cidd),
- .bDescriptorType = 1,
- .bDevCapabilityType = 4,
+ .bDevCapabilityType = USB_DEVCAP_SUPERSPEED_PLUS,
.bReserved = 0,
- .bContainerID = 0, /* XXX */
+ .wFunctionalitySupport = {0x04, 0x11}, /* SSID 4, Rx: 1, Tx: 1 */
+ .wReserved = {0},
+ },
+ .caps = {
+ /* The speed is per lane */
+ [0] = {0x34, 0x00, 0x05, 0x00}, /* 4: 5 Gbps Gen1x1, Rx */
+ [1] = {0xb4, 0x00, 0x05, 0x00}, /* 4: 5 Gbps Gen1x1, Tx */
+ [2] = {0x35, 0x40, 0x0a, 0x00}, /* 5: 10 Gbps Gen2x1, Rx */
+ [3] = {0xb5, 0x40, 0x0a, 0x00}, /* 5: 10 Gbps Gen2x1, Tx */
+ [4] = {0x36, 0x40, 0x05, 0x00}, /* 6: 5 Gbps Gen1x2, Rx */
+ [5] = {0xb6, 0x40, 0x05, 0x00}, /* 6: 5 Gbps Gen1x2, Tx */
+ [6] = {0x37, 0x40, 0x0a, 0x00}, /* 7: 10 Gbps Gen2x2, Rx */
+ [7] = {0xb7, 0x40, 0x0a, 0x00}, /* 7: 10 Gbps Gen2x2, Tx */
},
};
+static uint16_t
+xhci_ssp_bos_build(struct xhci_softc *sc)
+{
+ struct xhci_ssp_bos_desc *bos = &sc->sc_hub_desc.ssp_bos;
+ uint16_t len;
+ uint8_t i;
+ uint8_t n;
+
+ *bos = xhci_ssp_bosd;
+
+ for (i = n = 0; i != nitems(bos->caps); i += 2) {
+ if ((sc->sc_ss_psiv & XHCI_PSIV_BIT(USB_DEVCAP_SSPLUS_SSID_GET(
+ UGETDW(xhci_ssp_bosd.caps[i])))) == 0)
+ continue;
+ memcpy(bos->caps[n], xhci_ssp_bosd.caps[i],
+ 2 * sizeof(bos->caps[0]));
+ n += 2;
+ }
+
+ len = offsetof(struct xhci_ssp_bos_desc, caps) +
+ n * sizeof(bos->caps[0]);
+ USETW(bos->bosd.wTotalLength, len);
+ bos->ssplus.bLength = sizeof(bos->ssplus) + n * sizeof(bos->caps[0]);
+ USETDW(bos->ssplus.bmAttributes, USB_DEVCAP_SSPLUS_SSAC_SET(n - 1) |
+ USB_DEVCAP_SSPLUS_SSIC_SET((n / 2) - 1));
+
+ return (len);
+}
+
static const
struct xhci_config_desc xhci_confd = {
.confd = {
@@ -3404,8 +3607,18 @@ xhci_roothub_exec(struct usb_device *udev,
err = USB_ERR_IOERROR;
goto done;
}
+ sc->sc_hub_desc.devd = xhci_devd;
+ switch (sc->sc_bus.usbrev) {
+ case USB_REV_3_2:
+ USETW(sc->sc_hub_desc.devd.bcdUSB, 0x0320);
+ break;
+ case USB_REV_3_1:
+ USETW(sc->sc_hub_desc.devd.bcdUSB, 0x0310);
+ break;
+ default:
+ break;
+ }
len = sizeof(xhci_devd);
- ptr = (const void *)&xhci_devd;
break;
case UDESC_BOS:
@@ -3413,8 +3626,12 @@ xhci_roothub_exec(struct usb_device *udev,
err = USB_ERR_IOERROR;
goto done;
}
- len = sizeof(xhci_bosd);
- ptr = (const void *)&xhci_bosd;
+ if (sc->sc_bus.usbrev < USB_REV_3_1) {
+ len = sizeof(xhci_30_bosd);
+ ptr = (const void *)&xhci_30_bosd;
+ } else {
+ len = xhci_ssp_bos_build(sc);
+ }
break;
case UDESC_CONFIG:
@@ -3609,8 +3826,9 @@ xhci_roothub_exec(struct usb_device *udev,
case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
DPRINTFN(9, "UR_GET_STATUS i=%d\n", index);
- if ((index < 1) ||
- (index > sc->sc_noport)) {
+ if ((index < 1) || (index > sc->sc_noport) ||
+ (value != 0 && value != 2) ||
+ (value == 2 && sc->sc_bus.usbrev < USB_REV_3_1)) {
err = USB_ERR_IOERROR;
goto done;
}
@@ -3650,6 +3868,18 @@ xhci_roothub_exec(struct usb_device *udev,
#endif
USETW(sc->sc_hub_desc.ps.wPortStatus, i);
+ if (value == 2) {
+ /*
+ * Only the default mapping gets here, where the speed
+ * ID tells the lane count, which is zero based.
+ */
+ i = XHCI_PS_SPEED_GET(v);
+ j = (i == XHCI_PS_SPEED_SS_PLUS_GEN1_x2 ||
+ i == XHCI_PS_SPEED_SS_PLUS_GEN2_x2) ? 1 : 0;
+ USETDW(sc->sc_hub_desc.ext_ps.dwExtPortStatus,
+ i | (i << 4) | (j << 8) | (j << 12));
+ }
+
i = 0;
if (v & XHCI_PS_CSC)
i |= UPS_C_CONNECT_STATUS;
@@ -3667,7 +3897,8 @@ xhci_roothub_exec(struct usb_device *udev,
i |= UPS_C_PORT_CONFIG_ERROR;
USETW(sc->sc_hub_desc.ps.wPortChange, i);
- len = sizeof(sc->sc_hub_desc.ps);
+ len = (value == 2) ? sizeof(sc->sc_hub_desc.ext_ps) :
+ sizeof(sc->sc_hub_desc.ps);
break;
case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
@@ -4441,7 +4672,7 @@ xhci_device_state_change(struct usb_device *udev)
DPRINTF("\n");
if (usb_get_device_state(udev) == USB_STATE_CONFIGURED) {
- err = uhub_query_info(udev, &sc->sc_hw.devs[index].nports,
+ err = uhub_query_info(udev, &sc->sc_hw.devs[index].nports,
&sc->sc_hw.devs[index].tt);
if (err != 0)
sc->sc_hw.devs[index].nports = 0;
diff --git a/sys/dev/usb/controller/xhci.h b/sys/dev/usb/controller/xhci.h
index c2e6cde8ad77..9a84243eb8f5 100644
--- a/sys/dev/usb/controller/xhci.h
+++ b/sys/dev/usb/controller/xhci.h
@@ -478,10 +478,22 @@ struct xhci_bos_desc {
struct usb_devcap_container_id_descriptor cidd;
} __packed;
+struct xhci_ssp_bos_desc {
+ struct usb_bos_descriptor bosd;
+ struct usb_devcap_usb2ext_descriptor usb2extd;
+ struct usb_devcap_ss_descriptor usbdcd;
+ struct usb_devcap_container_id_descriptor cidd;
+ struct usb_devcap_ss_plus_descriptor_header ssplus;
+ uDWord caps[8];
+} __packed;
+
union xhci_hub_desc {
struct usb_status stat;
struct usb_port_status ps;
struct usb_hub_ss_descriptor hubd;
+ struct usb_ext_status ext_ps;
+ struct usb_device_descriptor devd;
+ struct xhci_ssp_bos_desc ssp_bos;
uint8_t temp[128];
};
@@ -548,6 +560,8 @@ struct xhci_softc {
uint8_t sc_command_ccs;
/* number of XHCI device slots */
uint8_t sc_noslot;
+ /* Enhanced SuperSpeed speed IDs of the controller, see XHCI_PSIV_BIT */
+ uint16_t sc_ss_psiv;
/* number of ports on root HUB */
uint8_t sc_noport;
/* root HUB device configuration */
diff --git a/sys/dev/usb/controller/xhcireg.h b/sys/dev/usb/controller/xhcireg.h
index c4f62586a0d0..ac6c7393ab49 100644
--- a/sys/dev/usb/controller/xhcireg.h
+++ b/sys/dev/usb/controller/xhcireg.h
@@ -127,6 +127,9 @@
#define XHCI_PS_SPEED_LOW 0x2 /* Low Speed USB */
#define XHCI_PS_SPEED_HIGH 0x3 /* High Speed USB */
#define XHCI_PS_SPEED_SS 0x4 /* Super Speed USB */
+#define XHCI_PS_SPEED_SS_PLUS_GEN2 0x5 /* Super Speed Plus Gen2x1 */
+#define XHCI_PS_SPEED_SS_PLUS_GEN1_x2 0x6 /* Super Speed Plus Gen1x2 */
+#define XHCI_PS_SPEED_SS_PLUS_GEN2_x2 0x7 /* Super Speed Plus Gen2x2 */
#define XHCI_PS_PIC_GET(x) (((x) >> 14) & 0x3) /* RW - port indicator */
#define XHCI_PS_PIC_SET(x) (((x) & 0x3) << 14) /* RW - port indicator */
#define XHCI_PS_LWS 0x00010000 /* RW - port link state write strobe */
@@ -195,9 +198,18 @@
/* XHCI legacy support */
#define XHCI_XECP_ID(x) ((x) & 0xFF)
#define XHCI_XECP_NEXT(x) (((x) >> 8) & 0xFF)
+#define XHCI_XECP_MAJOR(x) (((x) >> 24) & 0xFF)
+#define XHCI_XECP_MINOR(x) (((x) >> 16) & 0xFF)
#define XHCI_XECP_BIOS_SEM 0x0002
#define XHCI_XECP_OS_SEM 0x0003
+/* Support protocol */
+#define XHCI_XECP_PSIC_GET(x) (((x) >> 28) & 0x0F)
+#define XHCI_XECP_PSI_RSVD 0x00003E00
+#define XHCI_XECP_PSI_LP 0x0000C000
+#define XHCI_XECP_PSIV_GET(x) ((x) & 0x0F)
+#define XHCI_PSIV_BIT(x) (1U << (x))
+
/* XHCI capability ID's */
#define XHCI_ID_USB_LEGACY 0x0001
#define XHCI_ID_PROTOCOLS 0x0002