[PATCH v5 4/4] ALSA: usb-audio: qcom: fix xfer ring IOMMU unmap on 16K+ page kernels
From: Wesley Cheng
Date: Tue Nov 03 2026 - 18:43:02 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 can therefore start at
a non-page-aligned offset within the page that gets mapped, which the
sideband sg table reports as the offset of its entry.
Add that offset 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 (the sg entry length) instead of PAGE_SIZE as the ring
size. The offset is taken from the sg entry rather than from the ring's
DMA address, which is an IOVA when xHCI is behind an IOMMU, and whose
low bits then need not match the offset within the page.
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.
Have uaudio_endpoint_setup() return the iova it mapped instead, 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-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 48ee55116004..dd904d72badd 100644
--- a/sound/usb/qcom/qc_audio_offload.c
+++ b/sound/usb/qcom/qc_audio_offload.c
@@ -1178,6 +1178,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
@@ -1189,7 +1190,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;
@@ -1197,6 +1199,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);
@@ -1221,6 +1225,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);
@@ -1232,8 +1238,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;
@@ -1263,6 +1272,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);
@@ -1291,6 +1302,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);
@@ -1301,8 +1314,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;
@@ -1438,6 +1453,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;
@@ -1470,7 +1486,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;
@@ -1479,7 +1495,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;
@@ -1540,11 +1556,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);
@@ -1578,15 +1592,14 @@ 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);
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),
- PAGE_SIZE, PAGE_SIZE);
+ uaudio_iommu_unmap(MEM_XFER_RING, data_ring_iova, PAGE_SIZE,
+ PAGE_SIZE);
xhci_sideband_remove_endpoint(uadev[card_num].sb,
usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe));
--
2.43.0