usb: xhci: add XHCI_VLI_HUB_TT_QUIRK
authorJonathan Bell <jonathan@raspberrypi.com>
Thu, 1 Dec 2022 16:59:44 +0000 (16:59 +0000)
committerDom Cobley <popcornmix@gmail.com>
Mon, 19 Feb 2024 11:33:37 +0000 (11:33 +0000)
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 <jonathan@raspberrypi.com>
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 <jonathan@raspberrypi.com>
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h

index 4faba8d..911b46b 100644 (file)
@@ -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
@@ -290,6 +292,16 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev)
        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);
@@ -485,6 +497,8 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
                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 &&
index a383051..2a406f7 100644 (file)
@@ -3619,6 +3619,48 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
        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)
@@ -3775,6 +3817,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
        }
 
        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;
@@ -3910,6 +3954,8 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
                        /* 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;
index 9968b57..e55933f 100644 (file)
@@ -1912,6 +1912,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;