Re: [PATCH v12 05/12] dmaengine: qcom: bam_dma: add support for BAM locking
From: Manivannan Sadhasivam
Date: Wed Mar 11 2026 - 09:26:34 EST
On Tue, Mar 10, 2026 at 04:44:19PM +0100, Bartosz Golaszewski wrote:
> Add support for BAM pipe locking. To that end: when starting DMA on an RX
> channel - prepend the existing queue of issued descriptors with an
> additional "dummy" command descriptor with the LOCK bit set. Once the
> transaction is done (no more issued descriptors), issue one more dummy
> descriptor with the UNLOCK bit.
>
> We *must* wait until the transaction is signalled as done because we
> must not perform any writes into config registers while the engine is
> busy.
>
> The dummy writes must be issued into a scratchpad register of the client
> so provide a mechanism to communicate the right address via descriptor
> metadata.
>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
> ---
> drivers/dma/qcom/bam_dma.c | 175 ++++++++++++++++++++++++++++++++++++++-
> include/linux/dma/qcom_bam_dma.h | 4 +
> 2 files changed, 176 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
> index 83491e7c2f17d8c9d12a1a055baea7e3a0a75a53..627c85a2df4dcdbac247d831a4aef047c2189456 100644
> --- a/drivers/dma/qcom/bam_dma.c
> +++ b/drivers/dma/qcom/bam_dma.c
> @@ -28,11 +28,13 @@
> #include <linux/clk.h>
> #include <linux/device.h>
> #include <linux/dma-mapping.h>
> +#include <linux/dma/qcom_bam_dma.h>
> #include <linux/dmaengine.h>
> #include <linux/init.h>
> #include <linux/interrupt.h>
> #include <linux/io.h>
> #include <linux/kernel.h>
> +#include <linux/lockdep.h>
> #include <linux/module.h>
> #include <linux/of_address.h>
> #include <linux/of_dma.h>
> @@ -60,6 +62,8 @@ struct bam_desc_hw {
> #define DESC_FLAG_EOB BIT(13)
> #define DESC_FLAG_NWD BIT(12)
> #define DESC_FLAG_CMD BIT(11)
> +#define DESC_FLAG_LOCK BIT(10)
> +#define DESC_FLAG_UNLOCK BIT(9)
>
> struct bam_async_desc {
> struct virt_dma_desc vd;
> @@ -391,6 +395,14 @@ struct bam_chan {
> struct list_head desc_list;
>
> struct list_head node;
> +
> + /* BAM locking infrastructure */
> + bool locked;
Nit: Move this boolean at the end to avoid hole in-between.
> + phys_addr_t scratchpad_addr;
> + struct scatterlist lock_sg;
> + struct scatterlist unlock_sg;
> + struct bam_cmd_element lock_ce;
> + struct bam_cmd_element unlock_ce;
> };
>
> static inline struct bam_chan *to_bam_chan(struct dma_chan *common)
> @@ -652,6 +664,27 @@ static int bam_slave_config(struct dma_chan *chan,
> return 0;
> }
>
> +static int bam_metadata_attach(struct dma_async_tx_descriptor *desc, void *data, size_t len)
> +{
> + struct bam_chan *bchan = to_bam_chan(desc->chan);
> + const struct bam_device_data *bdata = bchan->bdev->dev_data;
> + struct bam_desc_metadata *metadata = data;
> +
> + if (!data)
> + return -EINVAL;
> +
> + if (!bdata->pipe_lock_supported)
> + return -EOPNOTSUPP;
I don't think you should error out if pipe lock is not supported. You can safely
return 0 so that the client can continue to do DMA. Otherwise, if the client
tries to do DMA on a non-pipe lock supported platform (a valid case), DMA will
fail.
There is also no incentive for the clients to know whether pipe lock is
supported or not as they can proceed anyway.
> +
> + bchan->scratchpad_addr = metadata->scratchpad_addr;
> +
> + return 0;
> +}
> +
> +static const struct dma_descriptor_metadata_ops bam_metadata_ops = {
> + .attach = bam_metadata_attach,
> +};
> +
> /**
> * bam_prep_slave_sg - Prep slave sg transaction
> *
> @@ -668,6 +701,7 @@ static struct dma_async_tx_descriptor *bam_prep_slave_sg(struct dma_chan *chan,
> void *context)
> {
> struct bam_chan *bchan = to_bam_chan(chan);
> + struct dma_async_tx_descriptor *tx_desc;
> struct bam_device *bdev = bchan->bdev;
> struct bam_async_desc *async_desc;
> struct scatterlist *sg;
> @@ -723,7 +757,12 @@ static struct dma_async_tx_descriptor *bam_prep_slave_sg(struct dma_chan *chan,
> } while (remainder > 0);
> }
>
> - return vchan_tx_prep(&bchan->vc, &async_desc->vd, flags);
> + tx_desc = vchan_tx_prep(&bchan->vc, &async_desc->vd, flags);
> + if (!tx_desc)
> + return NULL;
> +
> + tx_desc->metadata_ops = &bam_metadata_ops;
> + return tx_desc;
> }
>
> /**
> @@ -1012,6 +1051,112 @@ static void bam_apply_new_config(struct bam_chan *bchan,
> bchan->reconfigure = 0;
> }
>
> +static struct bam_async_desc *
> +bam_make_lock_desc(struct bam_chan *bchan, struct scatterlist *sg,
> + struct bam_cmd_element *ce, unsigned long flag)
> +{
> + struct dma_chan *chan = &bchan->vc.chan;
> + struct bam_async_desc *async_desc;
> + struct bam_desc_hw *desc;
> + struct virt_dma_desc *vd;
> + struct virt_dma_chan *vc;
> + unsigned int mapped;
> + dma_cookie_t cookie;
> + int ret;
> +
> + sg_init_table(sg, 1);
> +
> + async_desc = kzalloc_flex(*async_desc, desc, 1, GFP_NOWAIT);
> + if (!async_desc) {
> + dev_err(bchan->bdev->dev, "failed to allocate the BAM lock descriptor\n");
> + return NULL;
> + }
> +
> + async_desc->num_desc = 1;
> + async_desc->curr_desc = async_desc->desc;
> + async_desc->dir = DMA_MEM_TO_DEV;
> +
> + desc = async_desc->desc;
> +
> + bam_prep_ce_le32(ce, bchan->scratchpad_addr, BAM_WRITE_COMMAND, 0);
> + sg_set_buf(sg, ce, sizeof(*ce));
> +
> + mapped = dma_map_sg_attrs(chan->slave, sg, 1, DMA_TO_DEVICE, DMA_PREP_CMD);
> + if (!mapped) {
> + kfree(async_desc);
> + return NULL;
> + }
> +
> + desc->flags |= cpu_to_le16(DESC_FLAG_CMD | flag);
> + desc->addr = sg_dma_address(sg);
> + desc->size = sizeof(struct bam_cmd_element);
> +
> + vc = &bchan->vc;
> + vd = &async_desc->vd;
> +
> + dma_async_tx_descriptor_init(&vd->tx, &vc->chan);
> + vd->tx.flags = DMA_PREP_CMD;
> + vd->tx.desc_free = vchan_tx_desc_free;
> + vd->tx_result.result = DMA_TRANS_NOERROR;
> + vd->tx_result.residue = 0;
> +
> + cookie = dma_cookie_assign(&vd->tx);
> + ret = dma_submit_error(cookie);
> + if (ret)
> + return NULL;
Why can't you pass the actual error pointer to the caller. Right now, caller
just treats all failures as -ENOMEM.
> +
> + return async_desc;
> +}
> +
> +static int bam_do_setup_pipe_lock(struct bam_chan *bchan, bool lock)
> +{
> + struct bam_device *bdev = bchan->bdev;
> + const struct bam_device_data *bdata = bdev->dev_data;
> + struct bam_async_desc *lock_desc;
> + struct bam_cmd_element *ce;
> + struct scatterlist *sgl;
> + unsigned long flag;
> +
> + lockdep_assert_held(&bchan->vc.lock);
> +
> + if (!bdata->pipe_lock_supported || !bchan->scratchpad_addr ||
> + bchan->slave.direction != DMA_MEM_TO_DEV)
> + return 0;
> +
> + if (lock) {
> + sgl = &bchan->lock_sg;
> + ce = &bchan->lock_ce;
> + flag = DESC_FLAG_LOCK;
> + } else {
> + sgl = &bchan->unlock_sg;
> + ce = &bchan->unlock_ce;
> + flag = DESC_FLAG_UNLOCK;
> + }
> +
> + lock_desc = bam_make_lock_desc(bchan, sgl, ce, flag);
> + if (!lock_desc)
> + return -ENOMEM;
> +
> + if (lock)
> + list_add(&lock_desc->vd.node, &bchan->vc.desc_issued);
> + else
> + list_add_tail(&lock_desc->vd.node, &bchan->vc.desc_issued);
> +
> + bchan->locked = lock;
> +
> + return 0;
> +}
> +
> +static int bam_setup_pipe_lock(struct bam_chan *bchan)
> +{
> + return bam_do_setup_pipe_lock(bchan, true);
> +}
> +
> +static int bam_setup_pipe_unlock(struct bam_chan *bchan)
> +{
> + return bam_do_setup_pipe_lock(bchan, false);
> +}
> +
> /**
> * bam_start_dma - start next transaction
> * @bchan: bam dma channel
> @@ -1121,6 +1266,7 @@ static void bam_dma_work(struct work_struct *work)
> struct bam_device *bdev = from_work(bdev, work, work);
> struct bam_chan *bchan;
> unsigned int i;
> + int ret;
>
> /* go through the channels and kick off transactions */
> for (i = 0; i < bdev->num_channels; i++) {
> @@ -1128,6 +1274,13 @@ static void bam_dma_work(struct work_struct *work)
>
> guard(spinlock_irqsave)(&bchan->vc.lock);
>
> + if (list_empty(&bchan->vc.desc_issued) && bchan->locked) {
I fully agree with Stephan that we cannot rely on this to ensure completion of
previous commands. But I can't help with the NWD behavior :/
> + ret = bam_setup_pipe_unlock(bchan);
if (bam_setup_pipe_unlock())?
> + if (ret)
> + dev_err(bchan->vc.chan.slave,
> + "Failed to set up the pipe unlock descriptor\n");
> + }
> +
> if (!list_empty(&bchan->vc.desc_issued) && !IS_BUSY(bchan))
> bam_start_dma(bchan);
> }
> @@ -1142,9 +1295,17 @@ static void bam_dma_work(struct work_struct *work)
> static void bam_issue_pending(struct dma_chan *chan)
> {
> struct bam_chan *bchan = to_bam_chan(chan);
> + int ret;
>
> guard(spinlock_irqsave)(&bchan->vc.lock);
>
> + if (!bchan->locked) {
> + ret = bam_setup_pipe_lock(bchan);
if (bam_setup_pipe_lock())?
- Mani
--
மணிவண்ணன் சதாசிவம்