From fd127b56a410cd912dae382252c8c5b88a4a66b5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 6 Nov 2025 16:44:50 +0100 Subject: [PATCH 01/12] UPSTREAM: dmaengine: qcom: bam_dma: order includes alphabetically For easier maintenance and better readability order all includes alphabetically. Link: https://lore.kernel.org/r/20251106-qcom-bam-dma-refactor-v1-1-0e2baaf3d81a@linaro.org Signed-off-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov --- drivers/dma/qcom/bam_dma.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index cf1b2e512921b..c03c5c5fddd7d 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -23,24 +23,24 @@ * indication of where the hardware is currently working. */ -#include -#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include -#include #include -#include -#include -#include +#include +#include +#include #include +#include +#include #include "../dmaengine.h" #include "../virt-dma.h" From b5d648b12f55b13f3166e96b06818a6e452217d7 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 6 Nov 2025 16:44:51 +0100 Subject: [PATCH 02/12] UPSTREAM: dmaengine: qcom: bam_dma: use lock guards Simplify locking across the driver with lock guards from cleanup.h. Link: https://lore.kernel.org/r/20251106-qcom-bam-dma-refactor-v1-2-0e2baaf3d81a@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/dma/qcom/bam_dma.c | 126 +++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 70 deletions(-) diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index c03c5c5fddd7d..e848774a1ab15 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -594,7 +595,6 @@ static void bam_free_chan(struct dma_chan *chan) struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; u32 val; - unsigned long flags; int ret; ret = pm_runtime_get_sync(bdev->dev); @@ -608,9 +608,8 @@ static void bam_free_chan(struct dma_chan *chan) goto err; } - spin_lock_irqsave(&bchan->vc.lock, flags); - bam_reset_channel(bchan); - spin_unlock_irqrestore(&bchan->vc.lock, flags); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) + bam_reset_channel(bchan); dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, bchan->fifo_virt, bchan->fifo_phys); @@ -648,12 +647,11 @@ static int bam_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg) { struct bam_chan *bchan = to_bam_chan(chan); - unsigned long flag; - spin_lock_irqsave(&bchan->vc.lock, flag); + guard(spinlock_irqsave)(&bchan->vc.lock); + memcpy(&bchan->slave, cfg, sizeof(*cfg)); bchan->reconfigure = 1; - spin_unlock_irqrestore(&bchan->vc.lock, flag); return 0; } @@ -776,38 +774,37 @@ static int bam_dma_terminate_all(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); struct bam_async_desc *async_desc, *tmp; - unsigned long flag; LIST_HEAD(head); /* remove all transactions, including active transaction */ - spin_lock_irqsave(&bchan->vc.lock, flag); - /* - * If we have transactions queued, then some might be committed to the - * hardware in the desc fifo. The only way to reset the desc fifo is - * to do a hardware reset (either by pipe or the entire block). - * bam_chan_init_hw() will trigger a pipe reset, and also reinit the - * pipe. If the pipe is left disabled (default state after pipe reset) - * and is accessed by a connected hardware engine, a fatal error in - * the BAM will occur. There is a small window where this could happen - * with bam_chan_init_hw(), but it is assumed that the caller has - * stopped activity on any attached hardware engine. Make sure to do - * this first so that the BAM hardware doesn't cause memory corruption - * by accessing freed resources. - */ - if (!list_empty(&bchan->desc_list)) { - async_desc = list_first_entry(&bchan->desc_list, - struct bam_async_desc, desc_node); - bam_chan_init_hw(bchan, async_desc->dir); - } + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + /* + * If we have transactions queued, then some might be committed to the + * hardware in the desc fifo. The only way to reset the desc fifo is + * to do a hardware reset (either by pipe or the entire block). + * bam_chan_init_hw() will trigger a pipe reset, and also reinit the + * pipe. If the pipe is left disabled (default state after pipe reset) + * and is accessed by a connected hardware engine, a fatal error in + * the BAM will occur. There is a small window where this could happen + * with bam_chan_init_hw(), but it is assumed that the caller has + * stopped activity on any attached hardware engine. Make sure to do + * this first so that the BAM hardware doesn't cause memory corruption + * by accessing freed resources. + */ + if (!list_empty(&bchan->desc_list)) { + async_desc = list_first_entry(&bchan->desc_list, + struct bam_async_desc, desc_node); + bam_chan_init_hw(bchan, async_desc->dir); + } - list_for_each_entry_safe(async_desc, tmp, - &bchan->desc_list, desc_node) { - list_add(&async_desc->vd.node, &bchan->vc.desc_issued); - list_del(&async_desc->desc_node); - } + list_for_each_entry_safe(async_desc, tmp, + &bchan->desc_list, desc_node) { + list_add(&async_desc->vd.node, &bchan->vc.desc_issued); + list_del(&async_desc->desc_node); + } - vchan_get_all_descriptors(&bchan->vc, &head); - spin_unlock_irqrestore(&bchan->vc.lock, flag); + vchan_get_all_descriptors(&bchan->vc, &head); + } vchan_dma_desc_free_list(&bchan->vc, &head); @@ -823,17 +820,16 @@ static int bam_pause(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - unsigned long flag; int ret; ret = pm_runtime_get_sync(bdev->dev); if (ret < 0) return ret; - spin_lock_irqsave(&bchan->vc.lock, flag); - writel_relaxed(1, bam_addr(bdev, bchan->id, BAM_P_HALT)); - bchan->paused = 1; - spin_unlock_irqrestore(&bchan->vc.lock, flag); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + writel_relaxed(1, bam_addr(bdev, bchan->id, BAM_P_HALT)); + bchan->paused = 1; + } pm_runtime_mark_last_busy(bdev->dev); pm_runtime_put_autosuspend(bdev->dev); @@ -849,17 +845,16 @@ static int bam_resume(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - unsigned long flag; int ret; ret = pm_runtime_get_sync(bdev->dev); if (ret < 0) return ret; - spin_lock_irqsave(&bchan->vc.lock, flag); - writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_HALT)); - bchan->paused = 0; - spin_unlock_irqrestore(&bchan->vc.lock, flag); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_HALT)); + bchan->paused = 0; + } pm_runtime_mark_last_busy(bdev->dev); pm_runtime_put_autosuspend(bdev->dev); @@ -876,7 +871,6 @@ static int bam_resume(struct dma_chan *chan) static u32 process_channel_irqs(struct bam_device *bdev) { u32 i, srcs, pipe_stts, offset, avail; - unsigned long flags; struct bam_async_desc *async_desc, *tmp; srcs = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_EE)); @@ -896,7 +890,7 @@ static u32 process_channel_irqs(struct bam_device *bdev) writel_relaxed(pipe_stts, bam_addr(bdev, i, BAM_P_IRQ_CLR)); - spin_lock_irqsave(&bchan->vc.lock, flags); + guard(spinlock_irqsave)(&bchan->vc.lock); offset = readl_relaxed(bam_addr(bdev, i, BAM_P_SW_OFSTS)) & P_SW_OFSTS_MASK; @@ -935,8 +929,6 @@ static u32 process_channel_irqs(struct bam_device *bdev) } list_del(&async_desc->desc_node); } - - spin_unlock_irqrestore(&bchan->vc.lock, flags); } return srcs; @@ -1000,7 +992,6 @@ static enum dma_status bam_tx_status(struct dma_chan *chan, dma_cookie_t cookie, int ret; size_t residue = 0; unsigned int i; - unsigned long flags; ret = dma_cookie_status(chan, cookie, txstate); if (ret == DMA_COMPLETE) @@ -1009,23 +1000,22 @@ static enum dma_status bam_tx_status(struct dma_chan *chan, dma_cookie_t cookie, if (!txstate) return bchan->paused ? DMA_PAUSED : ret; - spin_lock_irqsave(&bchan->vc.lock, flags); - vd = vchan_find_desc(&bchan->vc, cookie); - if (vd) { - residue = container_of(vd, struct bam_async_desc, vd)->length; - } else { - list_for_each_entry(async_desc, &bchan->desc_list, desc_node) { - if (async_desc->vd.tx.cookie != cookie) - continue; - - for (i = 0; i < async_desc->num_desc; i++) - residue += le16_to_cpu( - async_desc->curr_desc[i].size); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + vd = vchan_find_desc(&bchan->vc, cookie); + if (vd) { + residue = container_of(vd, struct bam_async_desc, vd)->length; + } else { + list_for_each_entry(async_desc, &bchan->desc_list, desc_node) { + if (async_desc->vd.tx.cookie != cookie) + continue; + + for (i = 0; i < async_desc->num_desc; i++) + residue += le16_to_cpu( + async_desc->curr_desc[i].size); + } } } - spin_unlock_irqrestore(&bchan->vc.lock, flags); - dma_set_residue(txstate, residue); if (ret == DMA_IN_PROGRESS && bchan->paused) @@ -1166,17 +1156,16 @@ static void dma_tasklet(struct tasklet_struct *t) { struct bam_device *bdev = from_tasklet(bdev, t, task); struct bam_chan *bchan; - unsigned long flags; unsigned int i; /* go through the channels and kick off transactions */ for (i = 0; i < bdev->num_channels; i++) { bchan = &bdev->channels[i]; - spin_lock_irqsave(&bchan->vc.lock, flags); + + guard(spinlock_irqsave)(&bchan->vc.lock); if (!list_empty(&bchan->vc.desc_issued) && !IS_BUSY(bchan)) bam_start_dma(bchan); - spin_unlock_irqrestore(&bchan->vc.lock, flags); } } @@ -1190,15 +1179,12 @@ static void dma_tasklet(struct tasklet_struct *t) static void bam_issue_pending(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); - unsigned long flags; - spin_lock_irqsave(&bchan->vc.lock, flags); + guard(spinlock_irqsave)(&bchan->vc.lock); /* if work pending and idle, start a transaction */ if (vchan_issue_pending(&bchan->vc) && !IS_BUSY(bchan)) bam_start_dma(bchan); - - spin_unlock_irqrestore(&bchan->vc.lock, flags); } /** From 236b73abbb10593c914ed522ecf73182010efc7d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 8 Jan 2026 11:50:12 +0100 Subject: [PATCH 03/12] UPSTREAM: scatterlist: introduce sg_nents_for_dma() helper Sometimes the user needs to split each entry on the mapped scatter list due to DMA length constrains. This helper returns a number of entities assuming that each of them is not bigger than supplied maximum length. Link: https://lore.kernel.org/r/20260108105619.3513561-2-andriy.shevchenko@linux.intel.com Reviewed-by: Bjorn Andersson Signed-off-by: Andy Shevchenko --- include/linux/scatterlist.h | 2 ++ lib/scatterlist.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h index 29f6ceb98d74b..6de1a24342990 100644 --- a/include/linux/scatterlist.h +++ b/include/linux/scatterlist.h @@ -441,6 +441,8 @@ static inline void sg_init_marker(struct scatterlist *sgl, int sg_nents(struct scatterlist *sg); int sg_nents_for_len(struct scatterlist *sg, u64 len); +int sg_nents_for_dma(struct scatterlist *sgl, unsigned int sglen, size_t len); + struct scatterlist *sg_last(struct scatterlist *s, unsigned int); void sg_init_table(struct scatterlist *, unsigned int); void sg_init_one(struct scatterlist *, const void *, unsigned int); diff --git a/lib/scatterlist.c b/lib/scatterlist.c index 91fe08b7e8869..a04cfa7a4533f 100644 --- a/lib/scatterlist.c +++ b/lib/scatterlist.c @@ -64,6 +64,32 @@ int sg_nents_for_len(struct scatterlist *sg, u64 len) } EXPORT_SYMBOL(sg_nents_for_len); +/** + * sg_nents_for_dma - return the count of DMA-capable entries in scatterlist + * @sgl: The scatterlist + * @sglen: The current number of entries + * @len: The maximum length of DMA-capable block + * + * Description: + * Determines the number of entries in @sgl which would be permitted in + * DMA-capable transfer if list had been split accordingly, taking into + * account chaining as well. + * + * Returns: + * the number of sgl entries needed + * + **/ +int sg_nents_for_dma(struct scatterlist *sgl, unsigned int sglen, size_t len) +{ + struct scatterlist *sg; + int i, nents = 0; + + for_each_sg(sgl, sg, sglen, i) + nents += DIV_ROUND_UP(sg_dma_len(sg), len); + return nents; +} +EXPORT_SYMBOL(sg_nents_for_dma); + /** * sg_last - return the last scatterlist entry in a list * @sgl: First entry in the scatterlist From a83c3b260012fa3de2c13a0163edaf47bd09a3f7 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 8 Jan 2026 11:50:21 +0100 Subject: [PATCH 04/12] UPSTREAM: dmaengine: qcom: bam_dma: use sg_nents_for_dma() helper Instead of open coded variant let's use recently introduced helper. Link: https://lore.kernel.org/r/20260108105619.3513561-11-andriy.shevchenko@linux.intel.com Reviewed-by: Bjorn Andersson Signed-off-by: Andy Shevchenko --- drivers/dma/qcom/bam_dma.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index e848774a1ab15..6b9b4cc449a2e 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -678,22 +678,17 @@ static struct dma_async_tx_descriptor *bam_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sg; u32 i; struct bam_desc_hw *desc; - unsigned int num_alloc = 0; - + unsigned int num_alloc; if (!is_slave_direction(direction)) { dev_err(bdev->dev, "invalid dma direction\n"); return NULL; } - /* calculate number of required entries */ - for_each_sg(sgl, sg, sg_len, i) - num_alloc += DIV_ROUND_UP(sg_dma_len(sg), BAM_FIFO_SIZE); - /* allocate enough room to accommodate the number of entries */ + num_alloc = sg_nents_for_dma(sgl, sg_len, BAM_FIFO_SIZE); async_desc = kzalloc(struct_size(async_desc, desc, num_alloc), GFP_NOWAIT); - if (!async_desc) return NULL; From 1f7521ab125af3b058c02f0b5447d02943949f77 Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 14 Jul 2026 11:02:31 +0530 Subject: [PATCH 05/12] FROMLIST: dt-bindings: net: qcom,bam-dmux: Add qcom,shikra-bam-dmux compatible On platforms where the modem DMAs into the BAM-DMUX RX data buffers and the XPU enforces per-region access control, each individually DMA-mapped RX buffer consumes an XPU resource group. With only ~16 groups available, the per-buffer mappings exhaust the table and inbound transfers fault. Add qcom,shikra-bam-dmux as an additional compatible for the Shikra SoC, paired with the generic qcom,bam-dmux fallback, so the driver can match on it via its of_device_id table. Link: https://lore.kernel.org/r/20260714-qcom-bam-dmux-vmid-ext-v1-1-3f29da7cca76@oss.qualcomm.com Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Signed-off-by: Vishnu Santhosh --- Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml b/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml index b30544410d092..12f1b2bc46d6e 100644 --- a/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml +++ b/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml @@ -22,7 +22,13 @@ description: | properties: compatible: - const: qcom,bam-dmux + oneOf: + - const: qcom,bam-dmux + - items: + - enum: + # Shikra + - qcom,shikra-bam-dmux + - const: qcom,bam-dmux interrupts: description: From 9c54d8aae839562538e60d441099c337dab9020b Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 14 Jul 2026 11:02:32 +0530 Subject: [PATCH 06/12] FROMLIST: net: wwan: qcom_bam_dmux: Alloc RX buffers as a single coherent block On Qualcomm SoCs where the modem (e.g. the mDSP on Shikra, VMID 43 / NAV) is the AXI master for BAM-DMUX RX transfers and the XPU enforces per-region access control, each individually DMA-mapped RX buffer requires its own XPU resource group (RG). With ~16 RGs available, the 32 per-buffer dma_map_single() calls exhaust the table and the first inbound transfer faults with an XPU violation. BAM-DMUX is a singleton (exactly one instance per SoC), so the destination VMID does not need to be a DT property; it is looked up from the compatible string's match data instead. Add struct bam_dmux_data with a single vmid field, and a shikra_data instance hardcoding QCOM_SCM_VMID_NAV for qcom,shikra-bam-dmux. When match data is present, allocate all BAM_DMUX_NUM_SKB RX buffers as a single contiguous dma_alloc_coherent() block and SCM-assign that block to HLOS plus the VMID once at probe. This reduces RG consumption from 32 to 1. The block is never reclaimed across a modem power cycle (bam_dmux_power_off() does not touch it), so the probe-time assignment covers every subsequent restart without re-assigning or reclaiming. It is reclaimed to HLOS only once, at remove or on a probe error, and if that reclaim fails it is leaked rather than returned to the page allocator. Each rx_skbs[] slot is pre-assigned its virtual and DMA address from the block, so no per-buffer mapping is needed at power-on. Because the coherent block is not page-backed, received payload is copied into a regular netdev skb before handoff to the network stack; this is an unavoidable extra copy on the XPU-enforced RX path. Platforms without match data are unaffected: rx_virt stays NULL, no coherent memory is allocated, and the per-buffer dma_map_single() path is unchanged. Link: https://lore.kernel.org/r/20260714-qcom-bam-dmux-vmid-ext-v1-2-3f29da7cca76@oss.qualcomm.com Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Signed-off-by: Vishnu Santhosh --- drivers/net/wwan/Kconfig | 1 + drivers/net/wwan/qcom_bam_dmux.c | 134 ++++++++++++++++++++++++++++--- 2 files changed, 125 insertions(+), 10 deletions(-) diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig index 410b0245114e8..18f761165cdc0 100644 --- a/drivers/net/wwan/Kconfig +++ b/drivers/net/wwan/Kconfig @@ -64,6 +64,7 @@ config MHI_WWAN_MBIM config QCOM_BAM_DMUX tristate "Qualcomm BAM-DMUX WWAN network driver" depends on (DMA_ENGINE && PM && QCOM_SMEM_STATE) || COMPILE_TEST + select QCOM_SCM help The BAM Data Multiplexer provides access to the network data channels of modems integrated into many older Qualcomm SoCs, e.g. Qualcomm diff --git a/drivers/net/wwan/qcom_bam_dmux.c b/drivers/net/wwan/qcom_bam_dmux.c index 64dab8b57611c..8993346989527 100644 --- a/drivers/net/wwan/qcom_bam_dmux.c +++ b/drivers/net/wwan/qcom_bam_dmux.c @@ -9,11 +9,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -63,6 +65,7 @@ struct bam_dmux_skb_dma { struct bam_dmux *dmux; struct sk_buff *skb; dma_addr_t addr; + void *rx_virt; /* non-NULL: slot in the coherent RX block */ }; struct bam_dmux { @@ -76,6 +79,10 @@ struct bam_dmux { struct completion pc_ack_completion; struct dma_chan *rx, *tx; + /* Single coherent block backing all RX buffers, NULL if unused */ + void *rx_buf; + dma_addr_t rx_buf_dma; + u64 rx_buf_perms; /* SCM source-VMID bitmask of rx_buf */ struct bam_dmux_skb_dma rx_skbs[BAM_DMUX_NUM_SKB]; struct bam_dmux_skb_dma tx_skbs[BAM_DMUX_NUM_SKB]; spinlock_t tx_lock; /* Protect tx_skbs, tx_next_skb */ @@ -93,6 +100,10 @@ struct bam_dmux_netdev { u8 ch; }; +struct bam_dmux_data { + u32 vmid; +}; + static void bam_dmux_pc_vote(struct bam_dmux *dmux, bool enable) { reinit_completion(&dmux->pc_ack_completion); @@ -112,6 +123,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma, { struct device *dev = skb_dma->dmux->dev; + if (skb_dma->rx_virt) /* coherent RX slot: addr pre-assigned */ + return true; + skb_dma->addr = dma_map_single(dev, skb_dma->skb->data, skb_dma->skb->len, dir); if (dma_mapping_error(dev, skb_dma->addr)) { dev_err(dev, "Failed to DMA map buffer\n"); @@ -125,6 +139,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma, static void bam_dmux_skb_dma_unmap(struct bam_dmux_skb_dma *skb_dma, enum dma_data_direction dir) { + if (skb_dma->rx_virt) /* coherent RX slot: nothing to unmap */ + return; + dma_unmap_single(skb_dma->dmux->dev, skb_dma->addr, skb_dma->skb->len, dir); skb_dma->addr = 0; } @@ -471,9 +488,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma) { struct bam_dmux *dmux = skb_dma->dmux; struct dma_async_tx_descriptor *desc; + size_t len = skb_dma->rx_virt ? BAM_DMUX_BUFFER_SIZE : skb_dma->skb->len; desc = dmaengine_prep_slave_single(dmux->rx, skb_dma->addr, - skb_dma->skb->len, DMA_DEV_TO_MEM, + len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { dev_err(dmux->dev, "Failed to prepare RX DMA buffer\n"); @@ -488,6 +506,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma) static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gfp) { + /* Coherent RX slots have rx_virt and addr pre-assigned at probe. */ + if (skb_dma->rx_virt) + return bam_dmux_skb_dma_submit_rx(skb_dma); + if (!skb_dma->skb) { skb_dma->skb = __netdev_alloc_skb(NULL, BAM_DMUX_BUFFER_SIZE, gfp); if (!skb_dma->skb) @@ -502,9 +524,10 @@ static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gf static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma) { struct bam_dmux *dmux = skb_dma->dmux; - struct sk_buff *skb = skb_dma->skb; - struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data; + struct bam_dmux_hdr *hdr = skb_dma->rx_virt ? skb_dma->rx_virt : + (struct bam_dmux_hdr *)skb_dma->skb->data; struct net_device *netdev = dmux->netdevs[hdr->ch]; + struct sk_buff *skb; if (!netdev || !netif_running(netdev)) { dev_warn(dmux->dev, "Data for inactive channel %u\n", hdr->ch); @@ -517,10 +540,18 @@ static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma) return; } - skb_dma->skb = NULL; /* Hand over to network stack */ - - skb_pull(skb, sizeof(*hdr)); - skb_trim(skb, hdr->len); + if (skb_dma->rx_virt) { + /* Coherent block is not page-backed: copy out to a real skb */ + skb = netdev_alloc_skb(netdev, hdr->len); + if (!skb) + return; + skb_put_data(skb, (u8 *)skb_dma->rx_virt + sizeof(*hdr), hdr->len); + } else { + skb = skb_dma->skb; + skb_dma->skb = NULL; /* Hand over to network stack */ + skb_pull(skb, sizeof(*hdr)); + skb_trim(skb, hdr->len); + } skb->dev = netdev; /* Only Raw-IP/QMAP is supported by this driver */ @@ -577,10 +608,14 @@ static void bam_dmux_rx_callback(void *data) { struct bam_dmux_skb_dma *skb_dma = data; struct bam_dmux *dmux = skb_dma->dmux; - struct sk_buff *skb = skb_dma->skb; - struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data; + struct bam_dmux_hdr *hdr; - bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE); + if (skb_dma->rx_virt) { + hdr = skb_dma->rx_virt; /* coherent RX: no skb to unmap */ + } else { + bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE); + hdr = (struct bam_dmux_hdr *)skb_dma->skb->data; + } if (hdr->magic != BAM_DMUX_HDR_MAGIC) { dev_err(dmux->dev, "Invalid magic in header: %#x\n", hdr->magic); @@ -647,6 +682,9 @@ static void bam_dmux_free_skbs(struct bam_dmux_skb_dma skbs[], for (i = 0; i < BAM_DMUX_NUM_SKB; i++) { struct bam_dmux_skb_dma *skb_dma = &skbs[i]; + if (skb_dma->rx_virt) /* coherent block freed at remove */ + continue; + if (skb_dma->addr) bam_dmux_skb_dma_unmap(skb_dma, dir); if (skb_dma->skb) { @@ -765,6 +803,71 @@ static int __maybe_unused bam_dmux_runtime_resume(struct device *dev) return 0; } +static int bam_dmux_alloc_coherent_rx(struct bam_dmux *dmux) +{ + struct device *dev = dmux->dev; + const struct bam_dmux_data *data = of_device_get_match_data(dev); + size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE; + u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS); + struct qcom_scm_vmperm dst[2]; + int i, ret; + + if (!data) + return 0; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + dst[0].vmid = QCOM_SCM_VMID_HLOS; + dst[0].perm = QCOM_SCM_PERM_RW; + dst[1].vmid = data->vmid; + dst[1].perm = QCOM_SCM_PERM_RW; + + dmux->rx_buf = dma_alloc_coherent(dev, size, &dmux->rx_buf_dma, GFP_KERNEL); + if (!dmux->rx_buf) + return -ENOMEM; + + for (i = 0; i < BAM_DMUX_NUM_SKB; i++) { + dmux->rx_skbs[i].rx_virt = dmux->rx_buf + i * BAM_DMUX_BUFFER_SIZE; + dmux->rx_skbs[i].addr = dmux->rx_buf_dma + i * BAM_DMUX_BUFFER_SIZE; + } + + ret = qcom_scm_assign_mem(dmux->rx_buf_dma, size, &src, dst, ARRAY_SIZE(dst)); + if (ret) { + dev_err(dev, "SCM assign RX block failed: %d\n", ret); + dma_free_coherent(dev, size, dmux->rx_buf, dmux->rx_buf_dma); + dmux->rx_buf = NULL; + return ret; + } + dmux->rx_buf_perms = src; + + return 0; +} + +static void bam_dmux_free_coherent_rx(struct bam_dmux *dmux) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE; + + if (!dmux->rx_buf) + return; + + if (dmux->rx_buf_perms) { + if (qcom_scm_assign_mem(dmux->rx_buf_dma, size, &dmux->rx_buf_perms, + &hlos, 1)) { + dev_err(dmux->dev, "SCM reclaim RX block failed; leaking\n"); + return; + } + dmux->rx_buf_perms = 0; + } + + dma_free_coherent(dmux->dev, size, dmux->rx_buf, dmux->rx_buf_dma); + dmux->rx_buf = NULL; +} + static int bam_dmux_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -812,6 +915,10 @@ static int bam_dmux_probe(struct platform_device *pdev) dmux->tx_skbs[i].dmux = dmux; } + ret = bam_dmux_alloc_coherent_rx(dmux); + if (ret) + return ret; + /* Runtime PM manages our own power vote. * Note that the RX path may be active even if we are runtime suspended, * since it is controlled by the remote side. @@ -848,6 +955,7 @@ static int bam_dmux_probe(struct platform_device *pdev) err_disable_pm: pm_runtime_disable(dev); pm_runtime_dont_use_autosuspend(dev); + bam_dmux_free_coherent_rx(dmux); return ret; } @@ -882,13 +990,19 @@ static void bam_dmux_remove(struct platform_device *pdev) disable_irq(dmux->pc_irq); bam_dmux_power_off(dmux); bam_dmux_free_skbs(dmux->tx_skbs, DMA_TO_DEVICE); + bam_dmux_free_coherent_rx(dmux); } static const struct dev_pm_ops bam_dmux_pm_ops = { SET_RUNTIME_PM_OPS(bam_dmux_runtime_suspend, bam_dmux_runtime_resume, NULL) }; +static const struct bam_dmux_data shikra_data = { + .vmid = QCOM_SCM_VMID_NAV, +}; + static const struct of_device_id bam_dmux_of_match[] = { + { .compatible = "qcom,shikra-bam-dmux", .data = &shikra_data }, { .compatible = "qcom,bam-dmux" }, { /* sentinel */ } }; From 14fe1b8f1db3ee586a5bff5ec73a3cb407108546 Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 14 Jul 2026 10:30:22 +0530 Subject: [PATCH 07/12] FROMLIST: dt-bindings: dma: qcom,bam-dma: Add optional qcom,vmid property A SoC can have multiple BAM DMA instances. Some of these BAMs are powered by a remote processor that enforces XPU (eXternal Protection Unit) access control and reads the per-channel descriptor FIFOs as an AXI master under that remote processor's execution environment, so their FIFOs must be accessible to the remote processor's VMID; other BAM instances on the same SoC are not behind such a remote processor and must not have this property set. Add an optional qcom,vmid property listing the destination VMID(s) that the affected BAM instance's descriptor FIFOs must be accessible to. HLOS is always the source owner and must not be listed. Link: https://lore.kernel.org/r/20260714-qcom-bam-dma-vmid-ext-v1-1-cef87c57b7dc@oss.qualcomm.com Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Signed-off-by: Vishnu Santhosh --- .../devicetree/bindings/dma/qcom,bam-dma.yaml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml index ffdb308352c3f..5fafbc9f3de17 100644 --- a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml @@ -12,6 +12,15 @@ maintainers: allOf: - $ref: dma-controller.yaml# + - if: + not: + properties: + compatible: + contains: + const: qcom,shikra-bam-dma + then: + properties: + qcom,vmid: false properties: compatible: @@ -27,6 +36,8 @@ properties: - enum: # SDM845, SM6115, SM8150, SM8250 and QCM2290 - qcom,bam-v1.7.4 + # Shikra + - qcom,shikra-bam-dma - const: qcom,bam-v1.7.0 clocks: @@ -79,6 +90,21 @@ properties: Indicates that the bam is powered up by a remote processor but must be initialized by the local processor. + qcom,vmid: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + items: + minimum: 1 + maximum: 63 + description: + Destination VMIDs of the remote processor(s) that read the per-channel + descriptor FIFOs as an AXI master. When present, the driver SCM-assigns + each FIFO to these VMIDs so the remote access does not trigger an XPU + violation. HLOS is always retained as the source owner and must not be + listed. Optional even when the qcom,shikra-bam-dma compatible is + present; not valid on any other compatible in this schema. + reg: maxItems: 1 From 843ab08d4bef5a63fe525b8f96fe46fa8130b44b Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 14 Jul 2026 10:30:23 +0530 Subject: [PATCH 08/12] FROMLIST: dmaengine: qcom: bam_dma: SCM-assign descriptor FIFOs to a remote VMID On Qualcomm SoCs where the BAM is powered by a remote processor that enforces XPU access control (e.g. the mDSP on Shikra, VMID 43 / NAV), the BAM reads the descriptor FIFO as an AXI master under the remote execution environment. Without an SCM grant for the remote VMID, the first descriptor enqueue faults with an XPU violation. Parse the optional qcom,vmid DT property as a list of destination VMIDs. When present, SCM-assign each channel's descriptor FIFO to HLOS plus the listed VMIDs; num_vmids being non-zero is derived purely from qcom,vmid, a board-integration property, not from the per-SoC IP data, and is distinct from qcom,powered-remotely. A BAM with configured VMIDs has two properties that shape the channel lifecycle: 1. The remote firmware owns the BAM's power and reset. It may remove power during error recovery before the driver releases its channels, so any pipe/block register access at teardown can raise a synchronous external abort, and a local reset is redundant as the remote re-initialises the hardware on the next power-on. 2. TZ does not revoke the SCM grant when the remote powers down. A FIFO assigned once stays assigned across every power cycle, and re-assigning or reclaiming it while the remote is mid-teardown is rejected by TZ with -EINVAL. Handle both by keeping the descriptor FIFO as a persistent resource on such BAMs: allocate and SCM-assign it once on the first bam_alloc_chan(), keep it (and its grant) across power cycles, and reclaim it to HLOS and free it only once in bam_dma_remove(). If the final reclaim fails the remote still has access, so the buffer is leaked rather than returned to the page allocator; the source-VMID bitmask stored by qcom_scm_assign_mem() drives that reclaim. bam_free_chan() on such a BAM therefore only drops local channel state (clear ->initialized, decrement active_channels) with no MMIO and no SCM call, so the block and pipe are re-initialised on the next power-on while power is present. The bam_chan_init_hw() pipe reset in bam_dma_terminate_all() is likewise skipped. reclaiming the FIFO is an SCM call, not a register access, so bam_dma_remove() stays safe. Platforms that do not set qcom,vmid keep num_vmids 0, make no SCM call, and leave the alloc/free and register-access paths unchanged. Link: https://lore.kernel.org/r/20260714-qcom-bam-dma-vmid-ext-v1-2-cef87c57b7dc@oss.qualcomm.com Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Signed-off-by: Vishnu Santhosh --- drivers/dma/qcom/Kconfig | 1 + drivers/dma/qcom/bam_dma.c | 197 ++++++++++++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 16 deletions(-) diff --git a/drivers/dma/qcom/Kconfig b/drivers/dma/qcom/Kconfig index ace75d7b835a2..63105b8cc1276 100644 --- a/drivers/dma/qcom/Kconfig +++ b/drivers/dma/qcom/Kconfig @@ -15,6 +15,7 @@ config QCOM_BAM_DMA depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) select DMA_ENGINE select DMA_VIRTUAL_CHANNELS + select QCOM_SCM help Enable support for the QCOM BAM DMA controller. This controller provides DMA capabilities for a variety of on-chip devices. diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index 6b9b4cc449a2e..d1b60a34cc877 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -42,6 +42,7 @@ #include #include #include +#include #include "../dmaengine.h" #include "../virt-dma.h" @@ -381,6 +382,9 @@ struct bam_chan { struct bam_desc_hw *fifo_virt; dma_addr_t fifo_phys; + /* SCM source-VMID bitmask of the FIFO, 0 if not SCM-assigned */ + u64 fifo_src_perms; + /* fifo markers */ unsigned short head; /* start of active descriptor entries */ unsigned short tail; /* end of active descriptor entries */ @@ -418,6 +422,10 @@ struct bam_device { const struct bam_device_data *dev_data; + /* destination VMIDs for SCM assignment of descriptor FIFOs */ + u32 *vmids; + int num_vmids; + struct clk *bamclk; int irq; @@ -554,6 +562,126 @@ static void bam_chan_init_hw(struct bam_chan *bchan, bchan->tail = 0; } +/** + * bam_parse_vmids - Parse the optional qcom,vmid property + * @bdev: bam device + * + * Reads the list of destination VMIDs from qcom,vmid, if present. HLOS is + * always the source owner and must not be listed. Absent property leaves + * num_vmids 0. + */ +static int bam_parse_vmids(struct bam_device *bdev) +{ + struct device *dev = bdev->dev; + int n, i, ret; + + n = of_property_count_u32_elems(dev->of_node, "qcom,vmid"); + if (n == -EINVAL) + return 0; + + if (n < 0) + return n; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + bdev->vmids = devm_kcalloc(dev, n, sizeof(*bdev->vmids), GFP_KERNEL); + if (!bdev->vmids) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, "qcom,vmid", + bdev->vmids, n); + if (ret) + return ret; + + for (i = 0; i < n; i++) { + if (bdev->vmids[i] == QCOM_SCM_VMID_HLOS) { + dev_err(dev, "qcom,vmid must not include HLOS (%u)\n", + QCOM_SCM_VMID_HLOS); + return -EINVAL; + } + } + + bdev->num_vmids = n; + + return 0; +} + +/** + * bam_assign_fifo - SCM-assign a channel's descriptor FIFO to the remote VMIDs + * @bdev: bam device + * @bchan: bam channel + * + * Grants HLOS plus the configured qcom,vmid VMIDs access to the FIFO, so + * the remote EE can read it. The updated source-VMID bitmask is stored in + * bchan->fifo_src_perms for bam_fifo_can_free() to reverse. + */ +static int bam_assign_fifo(struct bam_device *bdev, struct bam_chan *bchan) +{ + struct qcom_scm_vmperm *dst __free(kfree) = NULL; + u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS); + int i, ret; + + if (!bdev->num_vmids) + return 0; + + dst = kcalloc(bdev->num_vmids + 1, sizeof(*dst), GFP_KERNEL); + if (!dst) + return -ENOMEM; + + dst[0].vmid = QCOM_SCM_VMID_HLOS; + dst[0].perm = QCOM_SCM_PERM_RW; + + for (i = 0; i < bdev->num_vmids; i++) { + dst[i + 1].vmid = bdev->vmids[i]; + dst[i + 1].perm = QCOM_SCM_PERM_RW; + } + + ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE, + &src, dst, bdev->num_vmids + 1); + if (ret) { + dev_err(bdev->dev, "SCM assign fifo chan %u failed: %d\n", + bchan->id, ret); + return ret; + } + + bchan->fifo_src_perms = src; + + return 0; +} + +/** + * bam_fifo_can_free - Reclaim a channel's descriptor FIFO to HLOS + * @bdev: bam device + * @bchan: bam channel + * + * Returns true if the FIFO may now be freed. On SCM failure the remote VMID + * still has access, so the caller must leak the buffer instead of freeing it. + */ +static bool bam_fifo_can_free(struct bam_device *bdev, struct bam_chan *bchan) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int ret; + + if (!bchan->fifo_src_perms) + return true; + + ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE, + &bchan->fifo_src_perms, &hlos, 1); + if (ret) { + dev_err(bdev->dev, "SCM reclaim fifo chan %u failed: %d; leaking\n", + bchan->id, ret); + return false; + } + + bchan->fifo_src_perms = 0; + + return true; +} + /** * bam_alloc_chan - Allocate channel resources for DMA channel. * @chan: specified channel @@ -565,16 +693,29 @@ static int bam_alloc_chan(struct dma_chan *chan) struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - if (bchan->fifo_virt) - return 0; - - /* allocate FIFO descriptor space, but only if necessary */ - bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE, - &bchan->fifo_phys, GFP_KERNEL); - + /* + * Remote-owned BAMs keep the FIFO allocated and SCM-assigned to the + * remote VMID across power cycles (see bam_free_chan), so allocate and + * assign it only once; the block and pipe are still re-initialised on + * every power-on below. + */ if (!bchan->fifo_virt) { - dev_err(bdev->dev, "Failed to allocate desc fifo\n"); - return -ENOMEM; + /* allocate FIFO descriptor space, but only if necessary */ + bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + &bchan->fifo_phys, GFP_KERNEL); + if (!bchan->fifo_virt) { + dev_err(bdev->dev, "Failed to allocate desc fifo\n"); + return -ENOMEM; + } + + if (bam_assign_fifo(bdev, bchan)) { + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bchan->fifo_virt, bchan->fifo_phys); + bchan->fifo_virt = NULL; + return -EIO; + } + } else if (!bdev->num_vmids) { + return 0; } if (bdev->active_channels++ == 0 && bdev->powered_remotely) @@ -608,12 +749,29 @@ static void bam_free_chan(struct dma_chan *chan) goto err; } + /* + * Remote-owned BAMs (qcom,vmid) keep the descriptor FIFO allocated and + * SCM-assigned across power cycles: the remote may already have cut + * power, so pipe-register access would fault, and TZ still holds the + * grant for the next restart (the FIFO is reclaimed and freed once in + * bam_dma_remove). Only drop local channel state here so the block and + * pipe are re-initialised on the next power-on; skip all MMIO. + */ + if (bdev->num_vmids) { + scoped_guard(spinlock_irqsave, &bchan->vc.lock) + bchan->initialized = 0; + bdev->active_channels--; + goto err; + } + scoped_guard(spinlock_irqsave, &bchan->vc.lock) bam_reset_channel(bchan); - dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, bchan->fifo_virt, - bchan->fifo_phys); + if (bam_fifo_can_free(bdev, bchan)) + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bchan->fifo_virt, bchan->fifo_phys); bchan->fifo_virt = NULL; + bdev->active_channels--; /* mask irq for pipe/channel */ val = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE)); @@ -623,7 +781,7 @@ static void bam_free_chan(struct dma_chan *chan) /* disable irq */ writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_IRQ_EN)); - if (--bdev->active_channels == 0 && bdev->powered_remotely) { + if (bdev->active_channels == 0 && bdev->powered_remotely) { /* s/w reset bam */ val = readl_relaxed(bam_addr(bdev, 0, BAM_CTRL)); val |= BAM_SW_RST; @@ -789,7 +947,9 @@ static int bam_dma_terminate_all(struct dma_chan *chan) if (!list_empty(&bchan->desc_list)) { async_desc = list_first_entry(&bchan->desc_list, struct bam_async_desc, desc_node); - bam_chan_init_hw(bchan, async_desc->dir); + /* Remote-owned BAM: pipe reset may fault, skip it. */ + if (!bchan->bdev->num_vmids) + bam_chan_init_hw(bchan, async_desc->dir); } list_for_each_entry_safe(async_desc, tmp, @@ -1303,6 +1463,10 @@ static int bam_dma_probe(struct platform_device *pdev) bdev->powered_remotely = of_property_read_bool(pdev->dev.of_node, "qcom,powered-remotely"); + ret = bam_parse_vmids(bdev); + if (ret) + return ret; + if (bdev->controlled_remotely || bdev->powered_remotely) bdev->bamclk = devm_clk_get_optional(bdev->dev, "bam_clk"); else @@ -1439,9 +1603,10 @@ static void bam_dma_remove(struct platform_device *pdev) if (!bdev->channels[i].fifo_virt) continue; - dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, - bdev->channels[i].fifo_virt, - bdev->channels[i].fifo_phys); + if (bam_fifo_can_free(bdev, &bdev->channels[i])) + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bdev->channels[i].fifo_virt, + bdev->channels[i].fifo_phys); } tasklet_kill(&bdev->task); From 30f2f3f793f9db8b47edfe5813d7554b50cb8641 Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Sat, 11 Jul 2026 09:34:18 +0530 Subject: [PATCH 09/12] FROMLIST: remoteproc: qcom_q6v5_pas: Create platform device for BAM-DMUX Some Qualcomm SoCs using the generic PAS remoteproc driver (e.g. Shikra) implement the BAM-DMUX protocol on the modem remoteproc to expose network data channels. The hardware/firmware resources required by the BAM-DMUX driver are described in an extra device tree node below the modem remoteproc, with the compatible "qcom,bam-dmux". qcom_q6v5_mss.c already creates a platform device for this node (commit 59983c74fc42 ("remoteproc: qcom_q6v5_mss: Create platform device for BAM-DMUX")), but qcom_q6v5_pas.c has no equivalent logic, so the bam-dmux node never probes on SoCs handled by this driver. Mirror the qcom_q6v5_mss.c approach: create a platform device specifically for the "qcom,bam-dmux" child node on probe, and destroy it on remove. of_get_compatible_child() returns NULL when the node is absent, and of_platform_device_create()/of_node_put() are NULL-safe, so this is a no-op for the many PAS-based SoCs that have no bam-dmux child. Link: https://lore.kernel.org/r/20260711-qcom-q6v5-pas-bam-dmux-v1-1-1e9231143b79@oss.qualcomm.com Signed-off-by: Vishnu Santhosh --- drivers/remoteproc/qcom_q6v5_pas.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index d0c3d041bf4d6..ec56e6638a442 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,7 @@ struct qcom_pas { struct qcom_rproc_pdm pdm_subdev; struct qcom_rproc_ssr ssr_subdev; struct qcom_sysmon *sysmon; + struct platform_device *bam_dmux; struct qcom_scm_pas_context *pas_ctx; struct qcom_scm_pas_context *dtb_pas_ctx; @@ -799,6 +801,7 @@ static int qcom_pas_probe(struct platform_device *pdev) const struct qcom_pas_data *desc; struct qcom_pas *pas; struct rproc *rproc; + struct device_node *node; const char *fw_name, *dtb_fw_name = NULL; const struct rproc_ops *ops = &qcom_pas_ops; int ret; @@ -903,6 +906,10 @@ static int qcom_pas_probe(struct platform_device *pdev) if (ret) goto remove_ssr_sysmon; + node = of_get_compatible_child(pdev->dev.of_node, "qcom,bam-dmux"); + pas->bam_dmux = of_platform_device_create(node, NULL, &pdev->dev); + of_node_put(node); + return 0; remove_ssr_sysmon: @@ -927,6 +934,9 @@ static void qcom_pas_remove(struct platform_device *pdev) { struct qcom_pas *pas = platform_get_drvdata(pdev); + if (pas->bam_dmux) + of_platform_device_destroy(&pas->bam_dmux->dev, NULL); + rproc_del(pas->rproc); qcom_q6v5_deinit(&pas->q6v5); From 6ba17b8e660ab66015a5b2690294e5e7cf8d39cf Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 21 Jul 2026 12:04:19 +0530 Subject: [PATCH 10/12] Revert "FROMLIST: arm64: dts: qcom: shikra: Add BAM-DMUX support" v2 of this series moves the bam-dmux node below the modem remoteproc (remoteproc_mpss) instead of at the root, so that userspace can associate the resulting network interfaces with the owning remoteproc via udev/sysfs. Reverting the v1 placement here to pick v2 on top. This reverts commit a4a9273264cf62c2b40713776ca3b228daf8c669. Signed-off-by: Vishnu Santhosh --- arch/arm64/boot/dts/qcom/shikra.dtsi | 50 ---------------------------- 1 file changed, 50 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 812ad89a649e2..0029d740db35e 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -27,22 +27,6 @@ #address-cells = <2>; #size-cells = <2>; - bam_dmux: bam-dmux { - compatible = "qcom,bam-dmux"; - - interrupts-extended = <&modem_smsm 1 IRQ_TYPE_EDGE_BOTH>, - <&modem_smsm 11 IRQ_TYPE_EDGE_BOTH>; - interrupt-names = "pc", - "pc-ack"; - - qcom,smem-states = <&apps_smsm 1>, <&apps_smsm 11>; - qcom,smem-state-names = "pc", - "pc-ack"; - - dmas = <&bam_dmux_dma 4>, <&bam_dmux_dma 5>; - dma-names = "tx", "rx"; - }; - clocks { xo_board: xo-board { compatible = "fixed-clock"; @@ -527,28 +511,6 @@ }; }; - smsm { - compatible = "qcom,smsm"; - - #address-cells = <1>; - #size-cells = <0>; - - mboxes = <0>, <&apcs_glb 13>; - - apps_smsm: apps@0 { - reg = <0>; - #qcom,smem-state-cells = <1>; - }; - - modem_smsm: modem@1 { - reg = <1>; - interrupts = ; - - interrupt-controller; - #interrupt-cells = <2>; - }; - }; - soc: soc@0 { compatible = "simple-bus"; @@ -2587,18 +2549,6 @@ }; }; - bam_dmux_dma: dma-controller@6044000 { - compatible = "qcom,bam-v1.7.0"; - reg = <0x0 0x06044000 0x0 0x19000>; - interrupts = ; - #dma-cells = <1>; - qcom,ee = <0>; - - num-channels = <6>; - qcom,num-ees = <1>; - qcom,powered-remotely; - }; - remoteproc_mpss: remoteproc@6080000 { compatible = "qcom,shikra-mpss-pas"; reg = <0x0 0x06080000 0x0 0x100>; From 3da3d1db43f01d23d55a2478b325cc68eabb8f54 Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 21 Jul 2026 14:27:36 +0530 Subject: [PATCH 11/12] FROMLIST: arm64: dts: qcom: shikra: Add BAM-DMUX support Add required nodes to enable the upstream BAM-DMUX WWAN driver on Qualcomm Shikra SoC. The SMSM (Shared Memory State Machine) node provides the power control signaling between the AP and modem for BAM-DMUX. The BAM DMA controller node describes the A2 modem BAM hardware as a standard DMA controller. The BAM-DMUX node references the DMA channels and the pc/pc-ack interrupt lines from the modem SMSM entry for power control signaling. The BAM-DMUX node is placed below the modem remoteproc so that userspace can associate the resulting network interfaces with the owning remoteproc via udev/sysfs, matching how qcom_q6v5_mss.c already handles the equivalent node on older SoCs. Link: https://lore.kernel.org/r/20260711-qcom-shikra-dts-bam-dmux-v2-1-d5b33ee32138@oss.qualcomm.com Signed-off-by: Vishnu Santhosh --- arch/arm64/boot/dts/qcom/shikra.dtsi | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 0029d740db35e..30cbe65bad805 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -511,6 +511,28 @@ }; }; + smsm { + compatible = "qcom,smsm"; + + #address-cells = <1>; + #size-cells = <0>; + + mboxes = <0>, <&apcs_glb 13>; + + apps_smsm: apps@0 { + reg = <0>; + #qcom,smem-state-cells = <1>; + }; + + modem_smsm: modem@1 { + reg = <1>; + interrupts = ; + + interrupt-controller; + #interrupt-cells = <2>; + }; + }; + soc: soc@0 { compatible = "simple-bus"; @@ -2587,6 +2609,23 @@ qcom,remote-pid = <1>; label = "mpss"; + bam_dmux: bam-dmux { + compatible = "qcom,bam-dmux"; + + interrupts-extended = <&modem_smsm 1 IRQ_TYPE_EDGE_BOTH>, + <&modem_smsm 11 IRQ_TYPE_EDGE_BOTH>; + interrupt-names = "pc", + "pc-ack"; + + qcom,smem-states = <&apps_smsm 1>, + <&apps_smsm 11>; + qcom,smem-state-names = "pc", + "pc-ack"; + + dmas = <&bam_dmux_dma 4>, <&bam_dmux_dma 5>; + dma-names = "tx", "rx"; + }; + gpr: gpr { compatible = "qcom,gpr"; qcom,glink-channels = "modem_apps"; @@ -3420,6 +3459,18 @@ }; }; + bam_dmux_dma: dma-controller@6044000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x0 0x06044000 0x0 0x19000>; + interrupts = ; + #dma-cells = <1>; + qcom,ee = <0>; + + num-channels = <6>; + qcom,num-ees = <1>; + qcom,powered-remotely; + }; + sram@c11e000 { compatible = "qcom,shikra-imem", "mmio-sram"; reg = <0x0 0x0c11e000 0x0 0x1000>; From 2546101dd9cacf093d770b712bc6b65cdfe8366b Mon Sep 17 00:00:00 2001 From: Vishnu Santhosh Date: Tue, 21 Jul 2026 16:21:58 +0530 Subject: [PATCH 12/12] FROMLIST: arm64: dts: qcom: shikra: Add qcom,vmid to BAM-DMA node On the Qualcomm Shikra SoC the mDSP (VMID 43 / QCOM_SCM_VMID_NAV) is the AXI master for BAM descriptor FIFO accesses. The XPU enforces per-region access control; without an SCM assignment granting NAV access, the first DMA transfer triggers an XPU violation. Add qcom,vmid = to the bam_dmux_dma controller node so bam_dma SCM-assigns each channel descriptor FIFO at allocation. BAM-DMUX itself is a singleton and no longer needs a DT property for its destination VMID: the driver now selects QCOM_SCM_VMID_NAV internally via the qcom,shikra-bam-dmux compatible's match data. Link: https://lore.kernel.org/r/20260714-b4-qcom-shikra-dts-bam-dmux-vmid-ext-v1-1-5b19da8d7735@oss.qualcomm.com Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Signed-off-by: Vishnu Santhosh --- arch/arm64/boot/dts/qcom/shikra.dtsi | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 30cbe65bad805..706b720b03d6b 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -2610,7 +2610,7 @@ label = "mpss"; bam_dmux: bam-dmux { - compatible = "qcom,bam-dmux"; + compatible = "qcom,shikra-bam-dmux", "qcom,bam-dmux"; interrupts-extended = <&modem_smsm 1 IRQ_TYPE_EDGE_BOTH>, <&modem_smsm 11 IRQ_TYPE_EDGE_BOTH>; @@ -3460,7 +3460,7 @@ }; bam_dmux_dma: dma-controller@6044000 { - compatible = "qcom,bam-v1.7.0"; + compatible = "qcom,shikra-bam-dma", "qcom,bam-v1.7.0"; reg = <0x0 0x06044000 0x0 0x19000>; interrupts = ; #dma-cells = <1>; @@ -3469,6 +3469,7 @@ num-channels = <6>; qcom,num-ees = <1>; qcom,powered-remotely; + qcom,vmid = ; }; sram@c11e000 {