[PATCH v6 4/4] ALSA: usb-audio: qcom: fix xfer ring IOMMU unmap on 16K+ page kernels
From: Wesley Cheng
Date: Mon Nov 09 2026 - 21:38:35 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. The driver maps the page containing
the ring segment, but reports the start of that page as the ring
address and PAGE_SIZE as its size. This is only correct if the segment
starts at offset 0 in its page.
For both the transfer rings and the event ring, add that offset to the
mapped iova before sending it to the ADSP over QMI, so the reported
address points at the start of the segment rather than the start of
its page. Also report the sg entry length (TRB_SEGMENT_SIZE) instead of
PAGE_SIZE as the ring size. The offset is taken from the sg entry
rather than from the ring's DMA address, because when xHCI is behind an
IOMMU that address is an IOVA, and its low bits need not match the
offset within the page.
With the offset added, IOVA_MASK() on the reported iova no longer gives
the page-aligned iova that was mapped. Have uaudio_endpoint_setup() pass
back the mapped iova, and use it for the cached data/sync_xfer_ring_va
and the drop_sync_ep/drop_data_ep unmaps.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Wesley Cheng <wesley.cheng@xxxxxxxxxxxxxxxx>
---
sound/usb/qcom/qc_audio_offload.c | 45 +++++++++++++++++++++++++--------------
1 file changed, 29 insertions(+), 16 deletions(-)
diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c
index c8edc48cb80e..65bf64c08b6e 100644
--- a/sound/usb/qcom/qc_audio_offload.c
+++ b/sound/usb/qcom/qc_audio_offload.c
@@ -1177,6 +1177,7 @@ static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs,
* @card_num: uadev index
* @mem_info: QMI response info
* @ep_desc: QMI ep desc response field
+ * @ring_iova: returns the iova the transfer ring page is mapped at
*
* Initialize the USB endpoint being used for a particular USB
* stream. Will request XHCI sec intr to reserve the EP for
@@ -1188,7 +1189,8 @@ static phys_addr_t
uaudio_endpoint_setup(struct snd_usb_substream *subs,
struct snd_usb_endpoint *endpoint, int card_num,
struct mem_info_v01 *mem_info,
- struct usb_endpoint_descriptor_v01 *ep_desc)
+ struct usb_endpoint_descriptor_v01 *ep_desc,
+ unsigned long *ring_iova)
{
struct usb_host_endpoint *ep;
phys_addr_t tr_pa = 0;
@@ -1196,6 +1198,8 @@ uaudio_endpoint_setup(struct snd_usb_substream *subs,
bool dma_coherent;
unsigned long iova;
struct page *pg;
+ unsigned int ring_size;
+ unsigned int ring_offset;
int ret = -ENODEV;
dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
@@ -1220,6 +1224,8 @@ uaudio_endpoint_setup(struct snd_usb_substream *subs,
pg = sg_page(sgt->sgl);
tr_pa = page_to_phys(pg);
mem_info->dma = sg_dma_address(sgt->sgl);
+ ring_size = sgt->sgl->length;
+ ring_offset = sgt->sgl->offset;
sg_free_table(sgt);
kfree(sgt);
@@ -1231,8 +1237,11 @@ 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 + ring_offset,
+ uaudio_qdev->data->sid);
+ mem_info->size = ring_size;
+ *ring_iova = iova;
return 0;
@@ -1260,6 +1269,8 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs,
bool dma_coherent;
unsigned long iova;
struct page *pg;
+ unsigned int ring_size;
+ unsigned int ring_offset;
int ret;
dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
@@ -1288,6 +1299,8 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs,
pg = sg_page(sgt->sgl);
er_pa = page_to_phys(pg);
mem_info->dma = sg_dma_address(sgt->sgl);
+ ring_size = sgt->sgl->length;
+ ring_offset = sgt->sgl->offset;
sg_free_table(sgt);
kfree(sgt);
@@ -1298,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 + ring_offset,
+ uaudio_qdev->data->sid);
+ mem_info->size = ring_size;
return 0;
@@ -1435,6 +1450,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
struct qmi_uaudio_stream_resp_msg_v01 *resp,
int info_idx)
{
+ unsigned long data_ring_iova = 0, sync_ring_iova = 0;
struct q6usb_offload *data;
int pcm_dev_num;
int card_num;
@@ -1467,7 +1483,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
resp->std_as_opr_intf_desc_valid = 1;
ret = uaudio_endpoint_setup(subs, subs->data_endpoint, card_num,
&resp->xhci_mem_info.tr_data,
- &resp->std_as_data_ep_desc);
+ &resp->std_as_data_ep_desc, &data_ring_iova);
if (ret < 0)
return ret;
@@ -1476,7 +1492,7 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
if (subs->sync_endpoint) {
ret = uaudio_endpoint_setup(subs, subs->sync_endpoint, card_num,
&resp->xhci_mem_info.tr_sync,
- &resp->std_as_sync_ep_desc);
+ &resp->std_as_sync_ep_desc, &sync_ring_iova);
if (ret < 0)
goto drop_data_ep;
@@ -1537,11 +1553,9 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
uadev[card_num].usb_core_id = resp->controller_num;
/* 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);
+ uadev[card_num].info[info_idx].data_xfer_ring_va = data_ring_iova;
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);
+ uadev[card_num].info[info_idx].sync_xfer_ring_va = sync_ring_iova;
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);
@@ -1575,13 +1589,12 @@ static int prepare_qmi_response(struct snd_usb_substream *subs,
usb_offload_put(subs->dev);
drop_sync_ep:
if (subs->sync_endpoint) {
- uaudio_iommu_unmap(MEM_XFER_RING,
- IOVA_MASK(resp->xhci_mem_info.tr_sync.iova),
- PAGE_SIZE, PAGE_SIZE);
+ uaudio_iommu_unmap(MEM_XFER_RING, sync_ring_iova, PAGE_SIZE,
+ PAGE_SIZE);
}
drop_data_ep:
- uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.iova),
- PAGE_SIZE, PAGE_SIZE);
+ uaudio_iommu_unmap(MEM_XFER_RING, data_ring_iova, PAGE_SIZE,
+ PAGE_SIZE);
return ret;
}
--
2.43.0