[PATCH 1/2] xhci: sideband: support page-aligned ring segment allocation
Wesley Cheng <[email protected]>
| Newsgroups | gmane.linux.kernel,gmane.linux.usb.general,gmane.linux.sound |
|---|---|
| Message-ID | <[email protected]> |
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. A sideband client that maps a ring buffer directly via the IOMMU (which operates at page granularity) needs the ring to start at a page boundary and occupy whole pages, otherwise the IOVA it is given does not correspond to the actual start of the ring. Add an alignment_req parameter to the ring/segment allocation path (xhci_ring_alloc(), xhci_segment_alloc(), xhci_endpoint_init(), xhci_alloc_interrupter(), xhci_create_secondary_interrupter()) and to the sideband API (xhci_sideband_add_endpoint(), xhci_sideband_create_interrupter()) so a sideband client can request a specific alignment for its endpoint ring and interrupter event ring. When the requested alignment exceeds TRB_SEGMENT_SIZE, allocate the segment directly with dma_alloc_coherent() at that size instead of from the shared pool. Store the alignment requirement on struct xhci_ring so later segments added via ring expansion keep the same alignment for the life of the ring. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Wesley Cheng <[email protected]> --- drivers/usb/host/xhci-mem.c | 70 ++++++++++++++++++++++++++++----------- drivers/usb/host/xhci-sideband.c | 9 +++-- drivers/usb/host/xhci.h | 11 ++++-- include/linux/usb/xhci-sideband.h | 7 ++-- 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 7a21ac81f9c8..5f09b36c127b 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -15,6 +15,7 @@ #include <linux/dmapool.h> #include <linux/dma-mapping.h> #include <linux/bitfield.h> +#include <linux/usb/xhci-sideband.h> #include "xhci.h" #include "xhci-trace.h" @@ -30,6 +31,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, unsigned int max_packet, unsigned int num, + unsigned int alignment_req, gfp_t flags) { struct xhci_segment *seg; @@ -40,7 +42,14 @@ 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); + if (alignment_req > TRB_SEGMENT_SIZE) { + seg->trbs = dma_alloc_coherent(dev, alignment_req, &dma, flags); + if (seg->trbs) + seg->alloc_size = alignment_req; + } else { + seg->trbs = dma_pool_zalloc(xhci->segment_pool, flags, &dma); + } + if (!seg->trbs) { kfree(seg); return NULL; @@ -50,7 +59,10 @@ 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); + if (seg->alloc_size) + dma_free_coherent(dev, seg->alloc_size, seg->trbs, dma); + else + dma_pool_free(xhci->segment_pool, seg->trbs, dma); kfree(seg); return NULL; } @@ -65,7 +77,11 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, static void xhci_segment_free(struct xhci_hcd *xhci, struct xhci_segment *seg) { if (seg->trbs) { - dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma); + if (seg->alloc_size) + dma_free_coherent(xhci_to_hcd(xhci)->self.sysdev, + seg->alloc_size, seg->trbs, seg->dma); + else + dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma); seg->trbs = NULL; } kfree(seg->bounce_buf); @@ -334,7 +350,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->bounce_buf_len, num, ring->alignment_req, flags); if (!prev) return -ENOMEM; num++; @@ -343,7 +359,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->bounce_buf_len, num, + ring->alignment_req, flags); if (!next) goto free_segments; @@ -370,7 +387,8 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, struct xhci_ring * 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) + enum xhci_ring_type type, unsigned int max_packet, + unsigned int alignment_req, gfp_t flags) { struct xhci_ring *ring; int ret; @@ -382,6 +400,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->alignment_req = alignment_req; INIT_LIST_HEAD(&ring->td_list); ring->type = type; if (num_segs == 0) @@ -421,6 +440,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.alignment_req = ring->alignment_req; new_ring.type = ring->type; ret = xhci_alloc_segments_for_ring(xhci, &new_ring, flags); if (ret) @@ -663,7 +683,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { stream_info->stream_rings[cur_stream] = - xhci_ring_alloc(xhci, 2, TYPE_STREAM, max_packet, mem_flags); + xhci_ring_alloc(xhci, 2, TYPE_STREAM, max_packet, 0, mem_flags); cur_ring = stream_info->stream_rings[cur_stream]; if (!cur_ring) goto cleanup_rings; @@ -1007,7 +1027,7 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, } /* Allocate endpoint 0 ring */ - dev->eps[0].ring = xhci_ring_alloc(xhci, 2, TYPE_CTRL, 0, flags); + dev->eps[0].ring = xhci_ring_alloc(xhci, 2, TYPE_CTRL, 0, 0, flags); if (!dev->eps[0].ring) goto fail; @@ -1486,11 +1506,20 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version >= 0x100) avg_trb_len = 8; - /* Set up the endpoint ring */ - virt_dev->eps[ep_index].new_ring = - xhci_ring_alloc(xhci, 2, ring_type, max_packet, mem_flags); - if (!virt_dev->eps[ep_index].new_ring) - return -ENOMEM; + if (virt_dev->eps[ep_index].sideband && virt_dev->eps[ep_index].sideband->alignment_req) { + virt_dev->eps[ep_index].new_ring = + xhci_ring_alloc(xhci, 2, ring_type, max_packet, + virt_dev->eps[ep_index].sideband->alignment_req, + mem_flags); + if (!virt_dev->eps[ep_index].new_ring) + return -ENOMEM; + } else { + /* Set up the endpoint ring */ + virt_dev->eps[ep_index].new_ring = + xhci_ring_alloc(xhci, 2, ring_type, max_packet, 0, mem_flags); + if (!virt_dev->eps[ep_index].new_ring) + return -ENOMEM; + } virt_dev->eps[ep_index].skip = false; virt_dev->eps[ep_index].next_uframe = -1; @@ -2291,7 +2320,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, + unsigned int alignment_req, gfp_t flags) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; struct xhci_interrupter *ir; @@ -2307,8 +2337,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); if (!ir) return NULL; - - ir->event_ring = xhci_ring_alloc(xhci, segs, TYPE_EVENT, 0, flags); + ir->event_ring = xhci_ring_alloc(xhci, segs, TYPE_EVENT, 0, alignment_req, flags); if (!ir->event_ring) { xhci_warn(xhci, "Failed to allocate interrupter event ring\n"); kfree(ir); @@ -2356,7 +2385,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) + u32 imod_interval, unsigned int intr_num, + unsigned int alignment_req) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_interrupter *ir; @@ -2367,7 +2397,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, alignment_req, GFP_KERNEL); if (!ir) return NULL; @@ -2485,7 +2515,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) goto fail; /* Set up the command ring to have one segments for now. */ - xhci->cmd_ring = xhci_ring_alloc(xhci, 1, TYPE_COMMAND, 0, flags); + xhci->cmd_ring = xhci_ring_alloc(xhci, 1, TYPE_COMMAND, 0, 0, flags); if (!xhci->cmd_ring) goto fail; @@ -2498,7 +2528,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, 0, flags); if (!xhci->interrupters[0]) goto fail; diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c index a5deeee4d5dc..471219d7d86d 100644 --- a/drivers/usb/host/xhci-sideband.c +++ b/drivers/usb/host/xhci-sideband.c @@ -144,7 +144,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_notify_ep_ring_free); */ int xhci_sideband_add_endpoint(struct xhci_sideband *sb, - struct usb_host_endpoint *host_ep) + struct usb_host_endpoint *host_ep, + unsigned int alignment_req) { struct xhci_virt_ep *ep; unsigned int ep_index; @@ -171,6 +172,7 @@ xhci_sideband_add_endpoint(struct xhci_sideband *sb, if (sb->eps[ep_index] || ep->sideband) return -EBUSY; + sb->alignment_req = alignment_req; ep->sideband = sb; sb->eps[ep_index] = ep; @@ -322,7 +324,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_check); */ int xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, - bool ip_autoclear, u32 imod_interval, int intr_num) + bool ip_autoclear, u32 imod_interval, int intr_num, + unsigned int alignment_req) { if (!sb || !sb->xhci) return -ENODEV; @@ -337,7 +340,7 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), num_seg, imod_interval, - intr_num); + intr_num, alignment_req); if (!sb->ir) return -ENOMEM; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c7bfa7f028d3..e356405c6351 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1290,6 +1290,10 @@ struct xhci_segment { void *bounce_buf; unsigned int bounce_offs; unsigned int bounce_len; + /* nonzero if trbs was allocated via dma_alloc_coherent() at this size, + * instead of from xhci->segment_pool + */ + unsigned int alloc_size; }; enum xhci_cancelled_td_status { @@ -1377,6 +1381,7 @@ struct xhci_ring { unsigned int stream_id; unsigned int num_segs; unsigned int bounce_buf_len; + unsigned int alignment_req; enum xhci_ring_type type; u32 old_trb_comp_code; struct radix_tree_root *trb_address_map; @@ -1823,7 +1828,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_device *udev, struct usb_host_endpoint *ep, gfp_t mem_flags); 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); + enum xhci_ring_type type, unsigned int max_packet, + unsigned int alignment_req, gfp_t flags); void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int num_trbs, gfp_t flags); @@ -1865,7 +1871,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); + u32 imod_interval, unsigned int intr_num, + unsigned int alignment_req); void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir); void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h index 005257085dcb..8ab4e1cb0fc6 100644 --- a/include/linux/usb/xhci-sideband.h +++ b/include/linux/usb/xhci-sideband.h @@ -62,6 +62,7 @@ struct xhci_sideband { struct usb_interface *intf; int (*notify_client)(struct usb_interface *intf, struct xhci_sideband_event *evt); + unsigned int alignment_req; }; struct xhci_sideband * @@ -72,7 +73,8 @@ void xhci_sideband_unregister(struct xhci_sideband *sb); int xhci_sideband_add_endpoint(struct xhci_sideband *sb, - struct usb_host_endpoint *host_ep); + struct usb_host_endpoint *host_ep, + unsigned int alignment_req); int xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct usb_host_endpoint *host_ep); @@ -94,7 +96,8 @@ static inline bool xhci_sideband_check(struct usb_hcd *hcd) int xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, - bool ip_autoclear, u32 imod_interval, int intr_num); + bool ip_autoclear, u32 imod_interval, int intr_num, + unsigned int alignment_req); void xhci_sideband_remove_interrupter(struct xhci_sideband *sb); int -- 2.34.1