[PATCH 2/2] xhci: fix ASM3242 port speed report for Gen 2x2 links after cold boot

From: Kean Ren

Date: Sat Sep 19 2026 - 22:54:08 EST


The ASMedia ASM3242 (1b21:3242) reports the SuperSpeed speed ID (4) in
PORTSC[13:10], together with two active lanes, for a link that is
actually running at Gen 2x2 after a cold boot.
xhci_get_ext_port_status() copies that ID into dwExtPortStatus, so the
USB core matches SSID 4 (whose LP is SuperSpeed, not SuperSpeedPlus),
returns USB_SSP_GEN_UNKNOWN and falls back to USB_SPEED_SUPER:

$ lsusb -t
|__ Port 001: Dev 002, If 0, Class=Mass Storage, Driver=uas, 5000M/x2
$ cat /sys/bus/usb/devices/2-1/speed
5000

while the link really runs at 20 Gbps:

GetPortStatus(HUB_EXT_PORT_STATUS) -> dwExtPortStatus = 0x00001144
(RX/TX speed id 4, RX/TX lanes 2) after a cold boot
GetPortStatus(HUB_EXT_PORT_STATUS) -> dwExtPortStatus = 0x00001177
(RX/TX speed id 7, RX/TX lanes 2) after an S3 resume

O_DIRECT throughput, cross-checked with /proc/diskstats:
1.49 - 1.60 GB/s (11.9 - 12.8 Gbps) in the "5000M" state
1.63 - 1.83 GB/s (13.0 - 14.7 Gbps) in the "20000M" state

Both exceed the physical ceiling of a 10 Gbps link (~1.25 GB/s), so the
link is Gen 2x2 in both states and only the controller's report is
wrong. Only an S3 suspend/resume makes the controller report speed ID
7; a device re-plug, a port disable/enable, an xhci_hcd unbind/bind, a
PCI function reset and a PCI remove+rescan all leave the wrong value in
place.

Add XHCI_ASM3242_SPEED_QUIRK and, when it is set, report the Gen 2x2
speed ID when the controller reports SuperSpeed with two active lanes.

Reported-by: Kean Ren <rh_king@xxxxxxx>
Link: https://lore.kernel.org/linux-usb/<your-report-message-id>/
Signed-off-by: Kean Ren <rh_king@xxxxxxx>
---
drivers/usb/host/xhci-hub.c | 17 +++++++++++++++--
drivers/usb/host/xhci-pci.c | 4 ++++
drivers/usb/host/xhci.h | 1 +
3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 470bafe1802b..ef2b35b31492 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1001,13 +1001,26 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port,
return 0;
}

-static u32 xhci_get_ext_port_status(u32 portsc, u32 port_li)
+static u32 xhci_get_ext_port_status(struct xhci_hcd *xhci, u32 portsc,
+ u32 port_li)
{
u32 ext_stat = 0;
int speed_id;

/* only support rx and tx lane counts of 1 in usb3.1 spec */
speed_id = DEV_PORT_SPEED(portsc);
+
+ /*
+ * The ASM3242 reports the SuperSpeed speed ID (4) together with two
+ * active lanes for a link that is actually running at Gen 2x2 after a
+ * cold boot. Reporting that ID makes the USB core fall back to
+ * USB_SPEED_SUPER (5000 Mbps) although the link runs at 20 Gbps, so
+ * report the Gen 2x2 speed ID (7) instead.
+ */
+ if ((xhci->quirks & XHCI_ASM3242_SPEED_QUIRK) && speed_id == 4 &&
+ PORT_RX_LANES(port_li) && PORT_TX_LANES(port_li))
+ speed_id = 7;
+
ext_stat |= speed_id; /* bits 3:0, RX speed id */
ext_stat |= speed_id << 4; /* bits 7:4, TX speed id */

@@ -1265,7 +1278,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
break;
}
port_li = readl(&port->port_reg->portli);
- status = xhci_get_ext_port_status(portsc, port_li);
+ status = xhci_get_ext_port_status(xhci, portsc, port_li);
put_unaligned_le32(status, &buf[4]);
}
break;
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index a8889081ae82..6219151d9eb0 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -471,6 +471,10 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
pdev->device == PCI_DEVICE_ID_ASMEDIA_3242_XHCI))
xhci->quirks |= XHCI_NO_64BIT_SUPPORT;

+ if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA &&
+ pdev->device == PCI_DEVICE_ID_ASMEDIA_3242_XHCI)
+ xhci->quirks |= XHCI_ASM3242_SPEED_QUIRK;
+
if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA &&
pdev->device == PCI_DEVICE_ID_ASMEDIA_1042A_XHCI)
xhci->quirks |= XHCI_ASMEDIA_MODIFY_FLOWCONTROL;
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index c7bfa7f028d3..77fab2f29693 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1647,6 +1647,7 @@ struct xhci_hcd {
#define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48)
#define XHCI_ETRON_HOST BIT_ULL(49)
#define XHCI_LIMIT_ENDPOINT_INTERVAL_9 BIT_ULL(50)
+#define XHCI_ASM3242_SPEED_QUIRK BIT_ULL(51)

unsigned int num_active_eps;
unsigned int limit_active_eps;