[PATCH v4 2/5] usb: xhci: Allow to specify a different segment pool for allocations
WARM2d
From: Wesley Cheng <hidden>
Date: 2026-09-10 01:51:08
Also in:
linux-sound, lkml
Subsystem:
the rest, usb subsystem, usb xhci driver · Maintainers:
Linus Torvalds, Greg Kroah-Hartman, Mathias Nyman
Ring segments are normally allocated from a shared DMA pool sized and aligned to TRB_SEGMENT_SIZE (4096 bytes). On kernels built with a larger PAGE_SIZE (e.g. 16K or 64K page arches), a segment can end up at a non-page-aligned offset within its enclosing CPU page, and multiple segments can share the same physical page. Modify existing xHCI allocation operations to accommodate for a separate DMA pool for segment allocations. This will safely partition ownership of segments between Linux xHCI and offload entities. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Wesley Cheng <redacted> --- drivers/usb/host/xhci-mem.c | 63 ++++++++++++++++++++++++++-------------- drivers/usb/host/xhci-sideband.c | 4 +-- drivers/usb/host/xhci.h | 6 +++- 3 files changed, 48 insertions(+), 25 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 7a21ac81f9c8..6e52cefd6619 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c@@ -28,6 +28,7 @@ * "All components of all Command and Transfer TRBs shall be initialized to '0'" */ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, + struct dma_pool *pool, unsigned int max_packet, unsigned int num, gfp_t flags)
@@ -40,7 +41,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, if (!seg) return NULL; - seg->trbs = dma_pool_zalloc(xhci->segment_pool, flags, &dma); + seg->trbs = dma_pool_zalloc(pool, flags, &dma); if (!seg->trbs) { kfree(seg); return NULL;
@@ -50,7 +51,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, seg->bounce_buf = kzalloc_node(max_packet, flags, dev_to_node(dev)); if (!seg->bounce_buf) { - dma_pool_free(xhci->segment_pool, seg->trbs, dma); + dma_pool_free(pool, seg->trbs, dma); kfree(seg); return NULL; }
@@ -62,10 +63,11 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, return seg; } -static void xhci_segment_free(struct xhci_hcd *xhci, struct xhci_segment *seg) +static void xhci_segment_free(struct xhci_hcd *xhci, struct dma_pool *pool, + struct xhci_segment *seg) { if (seg->trbs) { - dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma); + dma_pool_free(pool, seg->trbs, seg->dma); seg->trbs = NULL; } kfree(seg->bounce_buf);
@@ -81,7 +83,7 @@ static void xhci_ring_segments_free(struct xhci_hcd *xhci, struct xhci_ring *rin while (seg) { next = seg->next; - xhci_segment_free(xhci, seg); + xhci_segment_free(xhci, ring->segment_pool, seg); seg = next; } }
@@ -334,7 +336,7 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, struct xhci_ring struct xhci_segment *prev; unsigned int num = 0; - prev = xhci_segment_alloc(xhci, ring->bounce_buf_len, num, flags); + prev = xhci_segment_alloc(xhci, ring->segment_pool, ring->bounce_buf_len, num, flags); if (!prev) return -ENOMEM; num++;
@@ -343,7 +345,8 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, struct xhci_ring while (num < ring->num_segs) { struct xhci_segment *next; - next = xhci_segment_alloc(xhci, ring->bounce_buf_len, num, flags); + next = xhci_segment_alloc(xhci, ring->segment_pool, ring->bounce_buf_len, + num, flags); if (!next) goto free_segments;
@@ -362,15 +365,10 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, struct xhci_ring return -ENOMEM; } -/* - * Create a new ring with zero or more segments. - * - * Link each segment together into a ring. - * Set the end flag and the cycle toggle bit on the last segment. - * See section 4.9.1 and figures 15 and 16. - */ -struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, - enum xhci_ring_type type, unsigned int max_packet, gfp_t flags) +static struct xhci_ring * +xhci_ring_alloc_from_pool(struct xhci_hcd *xhci, unsigned int num_segs, + enum xhci_ring_type type, unsigned int max_packet, + struct dma_pool *pool, gfp_t flags) { struct xhci_ring *ring; int ret;
@@ -382,6 +380,7 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, ring->num_segs = num_segs; ring->bounce_buf_len = max_packet; + ring->segment_pool = pool; INIT_LIST_HEAD(&ring->td_list); ring->type = type; if (num_segs == 0)
@@ -398,6 +397,20 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, return NULL; } +/* + * Create a new ring with zero or more segments. + * + * Link each segment together into a ring. + * Set the end flag and the cycle toggle bit on the last segment. + * See section 4.9.1 and figures 15 and 16. + */ +struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, + enum xhci_ring_type type, unsigned int max_packet, gfp_t flags) +{ + return xhci_ring_alloc_from_pool(xhci, num_segs, type, max_packet, + xhci->segment_pool, flags); +} + void xhci_free_endpoint_ring(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, unsigned int ep_index)
@@ -422,6 +435,7 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, new_ring.num_segs = num_new_segs; new_ring.bounce_buf_len = ring->bounce_buf_len; new_ring.type = ring->type; + new_ring.segment_pool = ring->segment_pool; ret = xhci_alloc_segments_for_ring(xhci, &new_ring, flags); if (ret) return -ENOMEM;
@@ -1424,6 +1438,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, unsigned int mult; unsigned int avg_trb_len; unsigned int err_count = 0; + struct dma_pool *pool; ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
@@ -1487,8 +1502,10 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, avg_trb_len = 8; /* Set up the endpoint ring */ + pool = virt_dev->eps[ep_index].priv_seg_pool ? + virt_dev->eps[ep_index].priv_seg_pool : xhci->segment_pool; virt_dev->eps[ep_index].new_ring = - xhci_ring_alloc(xhci, 2, ring_type, max_packet, mem_flags); + xhci_ring_alloc_from_pool(xhci, 2, ring_type, max_packet, pool, mem_flags); if (!virt_dev->eps[ep_index].new_ring) return -ENOMEM;
@@ -2291,7 +2308,8 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) } static struct xhci_interrupter * -xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) +xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, + struct dma_pool *pool, gfp_t flags) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; struct xhci_interrupter *ir;
@@ -2308,7 +2326,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) if (!ir) return NULL; - ir->event_ring = xhci_ring_alloc(xhci, segs, TYPE_EVENT, 0, flags); + ir->event_ring = xhci_ring_alloc_from_pool(xhci, segs, TYPE_EVENT, 0, pool, flags); if (!ir->event_ring) { xhci_warn(xhci, "Failed to allocate interrupter event ring\n"); kfree(ir);
@@ -2356,7 +2374,8 @@ void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num) struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, - u32 imod_interval, unsigned int intr_num) + struct dma_pool *pool, u32 imod_interval, + unsigned int intr_num) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_interrupter *ir;
@@ -2367,7 +2386,7 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, intr_num >= xhci->max_interrupters) return NULL; - ir = xhci_alloc_interrupter(xhci, segs, GFP_KERNEL); + ir = xhci_alloc_interrupter(xhci, segs, pool ? pool : xhci->segment_pool, GFP_KERNEL); if (!ir) return NULL;
@@ -2498,7 +2517,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) if (!xhci->interrupters) goto fail; - xhci->interrupters[0] = xhci_alloc_interrupter(xhci, 0, flags); + xhci->interrupters[0] = xhci_alloc_interrupter(xhci, 0, xhci->segment_pool, flags); if (!xhci->interrupters[0]) goto fail;
diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index beb637407e47..274da38f333d 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c@@ -315,8 +315,8 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, return -EBUSY; sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), - num_seg, imod_interval, - intr_num); + num_seg, NULL, + imod_interval, intr_num); if (!sb->ir) return -ENOMEM;
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index c7bfa7f028d3..15ce0bb7aa3f 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h@@ -709,6 +709,8 @@ struct xhci_virt_ep { bool use_extended_tbc; /* set if this endpoint is controlled via sideband access*/ struct xhci_sideband *sideband; + /* dma pool to allocate this endpoint's ring segments from, if set */ + struct dma_pool *priv_seg_pool; }; enum xhci_overhead_type {
@@ -1380,6 +1382,7 @@ struct xhci_ring { enum xhci_ring_type type; u32 old_trb_comp_code; struct radix_tree_root *trb_address_map; + struct dma_pool *segment_pool; }; struct xhci_erst_entry {
@@ -1865,7 +1868,8 @@ void xhci_free_port_bw_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx); struct xhci_interrupter * xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, - u32 imod_interval, unsigned int intr_num); + struct dma_pool *pool, u32 imod_interval, + unsigned int intr_num); void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir); void xhci_skip_sec_intr_events(struct xhci_hcd *xhci,
--
2.34.1