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

From: Wesley Cheng

Date: Fri Aug 28 2026 - 17:40:33 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 | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c
index bd3f84a3652b..d6e4bcaa7239 100644
--- a/sound/usb/qcom/qc_audio_offload.c
+++ b/sound/usb/qcom/qc_audio_offload.c
@@ -58,6 +58,8 @@
#define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \
(((u64)sid) << 32)))
#define IOVA_MASK(iova) (((u64)(iova)) & 0xFFFFFFFF)
+/* recover the raw xfer ring iova by subtracting the intra-page offset added at setup */
+#define RING_IOVA_BASE(mem) (IOVA_MASK((mem).iova) - ((mem).dma & ~PAGE_MASK))
#define IOVA_BASE 0x1000
#define IOVA_XFER_RING_BASE (IOVA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1))
#define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32)
@@ -1238,8 +1240,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;

@@ -1307,8 +1311,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;

@@ -1547,10 +1553,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);
@@ -1585,13 +1591,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