[PATCH v3 1/4] xhci: sideband: fix ring sg table for sub-page TRB segments

From: Wesley Cheng

Date: Fri Sep 04 2026 - 03:40:28 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, multiple ring segments can share the
same physical page on larger-PAGE_SIZE kernels (16K/64K), which these
helpers cannot correctly represent.

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 (resolved via
is_vmalloc_addr()/vmalloc_to_page() or virt_to_page()),
TRB_SEGMENT_SIZE as the length, and offset_in_page() for the exact
intra-page offset. This guarantees each segment gets its own sg
entry regardless of page sharing.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Wesley Cheng <wesley.cheng@xxxxxxxxxxxxxxxx>
---
drivers/usb/host/xhci-sideband.c | 57 +++++++++++++---------------------------
1 file changed, 18 insertions(+), 39 deletions(-)

diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index a5deeee4d5dc..beb637407e47 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c
@@ -9,57 +9,42 @@
*/

#include <linux/usb/xhci-sideband.h>
-#include <linux/dma-direct.h>

#include "xhci.h"

/* sideband internal helpers */
static struct sg_table *
-xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring)
+xhci_ring_to_sgtable(struct xhci_ring *ring)
{
struct xhci_segment *seg;
struct sg_table *sgt;
- unsigned int n_pages;
- struct page **pages;
- struct device *dev;
- size_t sz;
+ 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;
+
+ if (sg_alloc_table(sgt, ring->num_segs, GFP_KERNEL)) {
+ kfree(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);
+ if (is_vmalloc_addr(seg->trbs))
+ page = vmalloc_to_page(seg->trbs);
+ else
+ page = virt_to_page(seg->trbs);
+
+ sg_set_page(&sgt->sgl[i], page, TRB_SEGMENT_SIZE,
+ offset_in_page(seg->trbs));
seg = seg->next;
}

- if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL))
- goto err;
-
- kvfree(pages);
-
/*
* Save first segment dma address to sg dma_address field for the sideband
* client to have access to the IOVA of the ring.
@@ -67,12 +52,6 @@ xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring)
sg_dma_address(sgt->sgl) = ring->first_seg->dma;

return sgt;
-
-err:
- kvfree(pages);
- kfree(sgt);
-
- return NULL;
}

/* Caller must hold sb->mutex */
@@ -254,7 +233,7 @@ xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb,
if (!ep || !ep->ring || !ep->sideband || ep->sideband != sb)
return NULL;

- return xhci_ring_to_sgtable(sb, ep->ring);
+ return xhci_ring_to_sgtable(ep->ring);
}
EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer);

@@ -276,7 +255,7 @@ xhci_sideband_get_event_buffer(struct xhci_sideband *sb)
if (!sb || !sb->ir)
return NULL;

- return xhci_ring_to_sgtable(sb, sb->ir->event_ring);
+ return xhci_ring_to_sgtable(sb->ir->event_ring);
}
EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer);


--
2.34.1