[PATCH v5 1/4] usb: xhci: sideband: fix ring sg table for sub-page TRB segments
From: Wesley Cheng
Date: Tue Nov 03 2026 - 18:42:33 EST
xhci_ring_to_sgtable() populated its sg_table via dma_get_sgtable()
per segment and sg_alloc_table_from_pages(), both of which only
operate at whole PAGE_SIZE granularity. Since TRB_SEGMENT_SIZE (4096)
can be smaller than PAGE_SIZE, these helpers describe a full page for
each ring segment on larger-PAGE_SIZE kernels (16K/64K), and cannot
correctly represent segments that share the same physical page.
Build the sg_table directly instead: allocate one sg entry per ring
segment with sg_alloc_table(), and fill each entry explicitly with
sg_set_page() using the segment's own page, TRB_SEGMENT_SIZE as the
length, and offset_in_page() for the exact intra-page offset. Walk the
table with for_each_sg() so this also works for chained sg tables.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Wesley Cheng <wesley.cheng@xxxxxxxxxxxxxxxx>
---
drivers/usb/host/xhci-sideband.c | 80 ++++++++++++++++++++++------------------
1 file changed, 45 insertions(+), 35 deletions(-)
diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index a5deeee4d5dc..d0c0f75d6085 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c
@@ -8,68 +8,78 @@
* Author: Mathias Nyman
*/
+#include <linux/dma-mapping.h>
#include <linux/usb/xhci-sideband.h>
-#include <linux/dma-direct.h>
#include "xhci.h"
/* sideband internal helpers */
+static struct page *
+xhci_segment_to_page(struct device *dev, struct xhci_segment *seg)
+{
+ struct sg_table sgt;
+ struct page *page;
+
+ /*
+ * Remapped coherent memory is in the vmalloc area, where
+ * vmalloc_to_page() finds the page for any offset into the allocation.
+ * dma_get_sgtable() could describe the buffer from the start of its
+ * allocation instead.
+ */
+ if (is_vmalloc_addr(seg->trbs))
+ return vmalloc_to_page(seg->trbs);
+
+ if (dma_get_sgtable(dev, &sgt, seg->trbs, seg->dma, TRB_SEGMENT_SIZE))
+ return NULL;
+
+ page = sg_page(sgt.sgl);
+ sg_free_table(&sgt);
+
+ return page;
+}
+
static struct sg_table *
xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring)
{
+ struct device *dev = xhci_to_hcd(sb->xhci)->self.sysdev;
struct xhci_segment *seg;
- struct sg_table *sgt;
- unsigned int n_pages;
- struct page **pages;
- struct device *dev;
- size_t sz;
+ struct sg_table *sgt;
+ struct scatterlist *sg;
+ struct page *page;
int i;
- dev = xhci_to_hcd(sb->xhci)->self.sysdev;
- sz = ring->num_segs * TRB_SEGMENT_SIZE;
- n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
- pages = kvmalloc_objs(struct page *, n_pages);
- if (!pages)
+ seg = ring->first_seg;
+ if (!seg)
return NULL;
sgt = kzalloc_obj(*sgt);
- if (!sgt) {
- kvfree(pages);
+ if (!sgt)
return NULL;
- }
- seg = ring->first_seg;
- if (!seg)
- goto err;
- /*
- * Rings can potentially have multiple segments, create an array that
- * carries page references to allocated segments. Utilize the
- * sg_alloc_table_from_pages() to create the sg table, and to ensure
- * that page links are created.
- */
- for (i = 0; i < ring->num_segs; i++) {
- dma_get_sgtable(dev, sgt, seg->trbs, seg->dma,
- TRB_SEGMENT_SIZE);
- pages[i] = sg_page(sgt->sgl);
- sg_free_table(sgt);
- seg = seg->next;
+ if (sg_alloc_table(sgt, ring->num_segs, GFP_KERNEL)) {
+ kfree(sgt);
+ return NULL;
}
- if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL))
- goto err;
+ for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) {
+ page = xhci_segment_to_page(dev, seg);
+ if (!page)
+ goto err;
- kvfree(pages);
+ sg_set_page(sg, page, TRB_SEGMENT_SIZE, offset_in_page(seg->trbs));
+ seg = seg->next;
+ }
/*
- * Save first segment dma address to sg dma_address field for the sideband
- * client to have access to the IOVA of the ring.
+ * Save the first segment DMA address for the sideband client. The returned
+ * table is metadata and must not be passed to dma_map_sg().
*/
sg_dma_address(sgt->sgl) = ring->first_seg->dma;
return sgt;
err:
- kvfree(pages);
+ sg_free_table(sgt);
kfree(sgt);
return NULL;
--
2.43.0