[PATCH v4 5/5] ALSA: usb-audio: qcom: fix xfer ring IOMMU unmap on 16K+ page kernels

From: Wesley Cheng

Date: Wed Sep 09 2026 - 21:54:47 EST


TRB_SEGMENT_SIZE is hardcoded to 4096 bytes, but on kernels built with
a larger PAGE_SIZE (e.g. 16K or 64K page arches) the IOMMU still maps
and unmaps in units of PAGE_SIZE. A ring segment's physical page can
therefore start at a non-page-aligned offset relative to the segment
itself, and the DMA address handed back for the ring
(sg_dma_address()) carries that same intra-page offset.

Add that offset back onto the mapped iova before sending it to the
ADSP over QMI, so the reported address resolves to the start of the
segment rather than the start of its containing page, and report the
true TRB_SEGMENT_SIZE instead of PAGE_SIZE as the ring size.

This broke the reverse direction: recovering the raw, page-aligned
iova for iommu_unmap() by masking off the low PAGE_SIZE bits of the
QMI-reported iova only works if that iova happens to already be
page-aligned before the offset was added, which is not guaranteed.
Add RING_IOVA_BASE(), which instead subtracts the exact offset that
was added at setup time, and use it for both the cached
data/sync_xfer_ring_va and the drop_sync_ep/drop_data_ep unmap error
paths.

Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Wesley Cheng <wesley.cheng@xxxxxxxxxxxxxxxx>
---
sound/usb/qcom/qc_audio_offload.c | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c
index bb5a0d54cd72..2e6397f9cab6 100644
--- a/sound/usb/qcom/qc_audio_offload.c
+++ b/sound/usb/qcom/qc_audio_offload.c
@@ -66,6 +66,12 @@

#define MAX_XFER_BUFF_LEN (24 * PAGE_SIZE)

+/* recover the raw xfer ring iova by subtracting the intra-page offset added at setup */
+static inline u64 ring_iova_base(struct mem_info_v01 mem)
+{
+ return IOVA_MASK(mem.iova) - (mem.dma & ~PAGE_MASK);
+}
+
struct iova_info {
struct list_head list;
unsigned long start_iova;
@@ -1241,8 +1247,10 @@ uaudio_endpoint_setup(struct snd_usb_substream *subs,
goto clear_pa;
}

- mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid);
- mem_info->size = PAGE_SIZE;
+ /* add intra-page offset so DSP IOVA resolves to the correct 4K slot */
+ mem_info->iova = PREPEND_SID_TO_IOVA(iova + (mem_info->dma & ~PAGE_MASK),
+ uaudio_qdev->data->sid);
+ mem_info->size = TRB_SEGMENT_SIZE;

return 0;

@@ -1311,8 +1319,10 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs,
goto clear_pa;
}

- mem_info->iova = PREPEND_SID_TO_IOVA(iova, uaudio_qdev->data->sid);
- mem_info->size = PAGE_SIZE;
+ /* add intra-page offset so DSP IOVA resolves to the correct 4K slot */
+ mem_info->iova = PREPEND_SID_TO_IOVA(iova + (mem_info->dma & ~PAGE_MASK),
+ uaudio_qdev->data->sid);
+ mem_info->size = TRB_SEGMENT_SIZE;

return 0;

@@ -1551,10 +1561,10 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,

/* cache intf specific info to use it for unmap and free xfer buf */
uadev[card_num].info[info_idx].data_xfer_ring_va =
- IOVA_MASK(resp->xhci_mem_info.tr_data.iova);
+ ring_iova_base(resp->xhci_mem_info.tr_data);
uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE;
uadev[card_num].info[info_idx].sync_xfer_ring_va =
- IOVA_MASK(resp->xhci_mem_info.tr_sync.iova);
+ ring_iova_base(resp->xhci_mem_info.tr_sync);
uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE;
uadev[card_num].info[info_idx].xfer_buf_iova =
IOVA_MASK(resp->xhci_mem_info.xfer_buff.iova);
@@ -1589,13 +1599,14 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
drop_sync_ep:
if (subs->sync_endpoint) {
uaudio_iommu_unmap(MEM_XFER_RING,
- IOVA_MASK(resp->xhci_mem_info.tr_sync.iova),
+ ring_iova_base(resp->xhci_mem_info.tr_sync),
PAGE_SIZE, PAGE_SIZE);
xhci_sideband_remove_endpoint(uadev[card_num].sb,
usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe));
}
drop_data_ep:
- uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.iova),
+ uaudio_iommu_unmap(MEM_XFER_RING,
+ ring_iova_base(resp->xhci_mem_info.tr_data),
PAGE_SIZE, PAGE_SIZE);
xhci_sideband_remove_endpoint(uadev[card_num].sb,
usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe));

--
2.34.1