Skip to content

Commit 31f5e13

Browse files
P33Mpopcornmix
authored andcommitted
drivers: usb: xhci: prevent a theoretical race on non-coherent platforms
For platforms that have xHCI controllers attached over PCIe, and non-coherent routes to main memory, a theoretical race exists between posting new TRBs to a ring, and writing to the doorbell register. In a contended system, write traffic from the CPU may be stalled before the memory controller, whereas the CPU to Endpoint route is separate and not likely to be contended. Similarly, the DMA route from the endpoint to main memory may be separate and uncontended. Therefore the xHCI can receive a doorbell write and find a stale view of a transfer ring. In cases where only a single TRB is ping-ponged at a time, this can cause the endpoint to not get polled at all. Adding a readl() before the write forces a round-trip transaction across PCIe, definitively serialising the CPU along the PCI producer-consumer ordering rules. Signed-off-by: Jonathan Bell <[email protected]>
1 parent 96be536 commit 31f5e13

File tree

1 file changed

+13
-0
lines changed

1 file changed

+13
-0
lines changed

drivers/usb/host/xhci-ring.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,19 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
508508

509509
trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id));
510510

511+
/*
512+
* For non-coherent systems with PCIe DMA (such as Pi 4, Pi 5) there
513+
* is a theoretical race between the TRB write and barrier, which
514+
* is reported complete as soon as the write leaves the CPU domain,
515+
* the doorbell write, which may be reported as complete by the RC
516+
* at some arbitrary point, and the visibility of new TRBs in system
517+
* RAM by the endpoint DMA engine.
518+
*
519+
* This read before the write positively serialises the CPU state
520+
* by incurring a round-trip across the link.
521+
*/
522+
readl(db_addr);
523+
511524
writel(DB_VALUE(ep_index, stream_id), db_addr);
512525
/* flush the write */
513526
readl(db_addr);

0 commit comments

Comments
 (0)