[PATCH v4 2/5] usb: xhci: Allow to specify a different segment pool for allocations
From: Wesley Cheng
Date: Wed Sep 09 2026 - 21:53:05 EST
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 <wesley.cheng@xxxxxxxxxxxxxxxx>
---
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