From 9d4246df36c1766b2761fd68a103b7ef329d8d83 Mon Sep 17 00:00:00 2001 From: Jonathan Bell Date: Thu, 1 Dec 2022 16:59:44 +0000 Subject: [PATCH 0485/1085] usb: xhci: add XHCI_VLI_HUB_TT_QUIRK The integrated USB2.0 hub in the VL805 chipset has a bug where it incorrectly determines the remaining available frame time before the host next sends a SOF packet with an incremented frame_number. See the USB2.0 specification sections 11.3 and 11.14.2.3. The hub's non-periodic TT handler can transmit the IN/OUT handshake token too late, so a following 64-byte DATA0/1 packet causes the ACK handshake to collide with the propagated SOF. This causes port babble. Avoid ringing doorbells for vulnerable endpoints during uFrame 7 if the TR is Idle to stop one source of babble. An IN transfer for a Running TR may happen at any time, so there's not much we can do about that. Ideally a hub firmware update to properly implement frame timeouts is needed, and to avoid spinning for up to 125us when submitting TDs to Idle rings. Signed-off-by: Jonathan Bell xhci: constrain XHCI_VLI_HUB_TT_QUIRK to old firmware versions VLI have a firmware update for the VL805 which resolves the incorrect frame time calculation in the hub's TT. Limit applying the quirk to known-bad firmwares. Signed-off-by: Jonathan Bell --- drivers/usb/host/xhci-pci.c | 14 +++++++++++ drivers/usb/host/xhci-ring.c | 46 ++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci.h | 1 + 3 files changed, 61 insertions(+) --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -27,6 +27,8 @@ #define SPARSE_DISABLE_BIT 17 #define SPARSE_CNTL_ENABLE 0xC12C +#define VL805_FW_VER_0138C0 0x0138C0 + /* Device for a quirk */ #define PCI_VENDOR_ID_FRESCO_LOGIC 0x1b73 #define PCI_DEVICE_ID_FRESCO_LOGIC_PDK 0x1000 @@ -295,6 +297,16 @@ static int xhci_pci_reinit(struct xhci_h return 0; } +static u32 xhci_vl805_get_fw_version(struct pci_dev *dev) +{ + int ret; + u32 ver; + + ret = pci_read_config_dword(dev, 0x50, &ver); + /* Default to a fw version of 0 instead of ~0 */ + return ret ? 0 : ver; +} + static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) { struct pci_dev *pdev = to_pci_dev(dev); @@ -496,6 +508,8 @@ static void xhci_pci_quirks(struct devic xhci->quirks |= XHCI_AVOID_DQ_ON_LINK; xhci->quirks |= XHCI_ZHAOXIN_TRB_FETCH; xhci->quirks |= XHCI_VLI_SS_BULK_OUT_BUG; + if (xhci_vl805_get_fw_version(pdev) < VL805_FW_VER_0138C0) + xhci->quirks |= XHCI_VLI_HUB_TT_QUIRK; } if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3671,6 +3671,48 @@ static int xhci_align_td(struct xhci_hcd return 1; } +static void xhci_vl805_hub_tt_quirk(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_ring *ring) +{ + struct list_head *tmp; + struct usb_device *udev = urb->dev; + unsigned int timeout = 0; + unsigned int single_td = 0; + + /* + * Adding a TD to an Idle ring for a FS nonperiodic endpoint + * that is behind the internal hub's TT will run the risk of causing a + * downstream port babble if submitted late in uFrame 7. + * Wait until we've moved on into at least uFrame 0 + * (MFINDEX references the next SOF to be transmitted). + * + * Rings for IN endpoints in the Running state also risk causing + * babble if the returned data is large, but there's not much we can do + * about it here. + */ + if (udev->route & 0xffff0 || udev->speed != USB_SPEED_FULL) + return; + + list_for_each(tmp, &ring->td_list) { + single_td++; + if (single_td == 2) { + single_td = 0; + break; + } + } + if (single_td) { + while (timeout < 20 && + (readl(&xhci->run_regs->microframe_index) & 0x7) == 0) { + udelay(10); + timeout++; + } + if (timeout >= 20) + xhci_warn(xhci, "MFINDEX didn't advance - %u.%u dodged\n", + readl(&xhci->run_regs->microframe_index) >> 3, + readl(&xhci->run_regs->microframe_index) & 7); + } +} + /* This is very similar to what ehci-q.c qtd_fill() does */ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) @@ -3827,6 +3869,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd * } check_trb_math(urb, enqd_len); + if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK) + xhci_vl805_hub_tt_quirk(xhci, urb, ring); giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id, start_cycle, start_trb); return 0; @@ -3962,6 +4006,8 @@ int xhci_queue_ctrl_tx(struct xhci_hcd * /* Event on completion */ field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state); + if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK) + xhci_vl805_hub_tt_quirk(xhci, urb, ep_ring); giveback_first_trb(xhci, slot_id, ep_index, 0, start_cycle, start_trb); return 0; --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1921,6 +1921,7 @@ struct xhci_hcd { /* Downstream VLI fixes */ #define XHCI_AVOID_DQ_ON_LINK BIT_ULL(56) #define XHCI_VLI_SS_BULK_OUT_BUG BIT_ULL(57) +#define XHCI_VLI_HUB_TT_QUIRK BIT_ULL(58) unsigned int num_active_eps; unsigned int limit_active_eps;