[PATCH v4 1/5] usb: xhci: sideband: fix ring sg table for sub-page TRB segments
From: Wesley Cheng <hidden>
Date: 2026-09-10 01:51:07
Also in:
linux-usb, lkml
Subsystem:
the rest, usb subsystem, usb xhci driver · Maintainers:
Linus Torvalds, Greg Kroah-Hartman, Mathias Nyman
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 <redacted> --- 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