* [PATCH 1/3] dmaengine: dw-edma: Implement device_synchronize() callback
2026-06-29 8:45 [PATCH 0/3] bus: mhi: ep: Implement flush_async() callback to flush async read/write Manivannan Sadhasivam via B4 Relay
@ 2026-06-29 8:45 ` Manivannan Sadhasivam via B4 Relay
2026-06-29 14:20 ` Frank Li
2026-06-29 8:45 ` [PATCH 2/3] bus: mhi: ep: Add mhi_cntrl->flush_async() callback to flush the async read/write Manivannan Sadhasivam via B4 Relay
2026-06-29 8:45 ` [PATCH 3/3] PCI: epf-mhi: Implement mhi_cntrl->flush_async() to flush DMA read/write Manivannan Sadhasivam via B4 Relay
2 siblings, 1 reply; 5+ messages in thread
From: Manivannan Sadhasivam via B4 Relay @ 2026-06-29 8:45 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Frank Li,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas
Cc: dmaengine, linux-kernel, mhi, linux-arm-msm, linux-pci,
Manivannan Sadhasivam
From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
device_synchronize() callback is required by the client drivers to ensure
all the DMA operations are completed so that they can free the memory
associated with the complete callbacks.
So implement this callback by first making sure that all the in-flight DMA
operations are completed and then call vchan_synchronize() to drain the
DMA tasklet.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
---
drivers/dma/dw-edma/dw-edma-core.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index c2feb3adc79f..7b12dfe8cfd3 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -331,6 +331,21 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
return err;
}
+static void dw_edma_device_synchronize(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+
+ /*
+ * Make sure all the in-flight DMA operations are completed before
+ * draining the tasklet using vchan_synchronize().
+ */
+ while (chan->status == EDMA_ST_BUSY && time_before(jiffies, timeout))
+ cpu_relax();
+
+ vchan_synchronize(&chan->vc);
+}
+
static void dw_edma_device_issue_pending(struct dma_chan *dchan)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
@@ -968,6 +983,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
dma->device_pause = dw_edma_device_pause;
dma->device_resume = dw_edma_device_resume;
dma->device_terminate_all = dw_edma_device_terminate_all;
+ dma->device_synchronize = dw_edma_device_synchronize;
dma->device_issue_pending = dw_edma_device_issue_pending;
dma->device_tx_status = dw_edma_device_tx_status;
dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH 1/3] dmaengine: dw-edma: Implement device_synchronize() callback
2026-06-29 8:45 ` [PATCH 1/3] dmaengine: dw-edma: Implement device_synchronize() callback Manivannan Sadhasivam via B4 Relay
@ 2026-06-29 14:20 ` Frank Li
0 siblings, 0 replies; 5+ messages in thread
From: Frank Li @ 2026-06-29 14:20 UTC (permalink / raw)
To: manivannan.sadhasivam
Cc: Manivannan Sadhasivam, Vinod Koul, Frank Li,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas,
dmaengine, linux-kernel, mhi, linux-arm-msm, linux-pci
On Mon, Jun 29, 2026 at 10:45:15AM +0200, Manivannan Sadhasivam via B4 Relay wrote:
>
> From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
>
> device_synchronize() callback is required by the client drivers to ensure
> all the DMA operations are completed so that they can free the memory
> associated with the complete callbacks.
>
> So implement this callback by first making sure that all the in-flight DMA
> operations are completed and then call vchan_synchronize() to drain the
> DMA tasklet.
>
> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
> ---
> drivers/dma/dw-edma/dw-edma-core.c | 16 ++++++++++++++++
> 1 file changed, 16 insertions(+)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index c2feb3adc79f..7b12dfe8cfd3 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -331,6 +331,21 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
> return err;
> }
>
> +static void dw_edma_device_synchronize(struct dma_chan *dchan)
> +{
> + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
> + unsigned long timeout = jiffies + msecs_to_jiffies(5000);
> +
> + /*
> + * Make sure all the in-flight DMA operations are completed before
> + * draining the tasklet using vchan_synchronize().
> + */
> + while (chan->status == EDMA_ST_BUSY && time_before(jiffies, timeout))
> + cpu_relax();
read_poll_timeout(...), Does need READ_ONCE(chan->status)?
Frank
> +
> + vchan_synchronize(&chan->vc);
> +}
> +
> static void dw_edma_device_issue_pending(struct dma_chan *dchan)
> {
> struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
> @@ -968,6 +983,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
> dma->device_pause = dw_edma_device_pause;
> dma->device_resume = dw_edma_device_resume;
> dma->device_terminate_all = dw_edma_device_terminate_all;
> + dma->device_synchronize = dw_edma_device_synchronize;
> dma->device_issue_pending = dw_edma_device_issue_pending;
> dma->device_tx_status = dw_edma_device_tx_status;
> dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
>
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/3] bus: mhi: ep: Add mhi_cntrl->flush_async() callback to flush the async read/write
2026-06-29 8:45 [PATCH 0/3] bus: mhi: ep: Implement flush_async() callback to flush async read/write Manivannan Sadhasivam via B4 Relay
2026-06-29 8:45 ` [PATCH 1/3] dmaengine: dw-edma: Implement device_synchronize() callback Manivannan Sadhasivam via B4 Relay
@ 2026-06-29 8:45 ` Manivannan Sadhasivam via B4 Relay
2026-06-29 8:45 ` [PATCH 3/3] PCI: epf-mhi: Implement mhi_cntrl->flush_async() to flush DMA read/write Manivannan Sadhasivam via B4 Relay
2 siblings, 0 replies; 5+ messages in thread
From: Manivannan Sadhasivam via B4 Relay @ 2026-06-29 8:45 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Frank Li,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas
Cc: dmaengine, linux-kernel, mhi, linux-arm-msm, linux-pci,
Manivannan Sadhasivam, stable+noautosel
From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
MHI EP stack makes use of the MHI controller drivers like MHI EPF to do
read/write to the host memory. And that driver is free to use mechanisms
like DMA to offload the read/write operations.
So if DMA is used for offload, then there is no guarantee that those DMA
operations would be completed by the time mhi_ep_remove() gets called. This
can lead to UAF (Use-After-Free) issues as the DMA callback can trigger
xfer_cb() even after mhi_ep_remove() has returned.
So to fix this issue, introduce the mhi_cntrl->flush_async() callback and
call it in mhi_ep_remove() before setting xfer_cb to NULL.
Note that flush_async() blocks until all the in-flight async transfers are
completed, so calling it with the chan->lock held would needlessly stall
the transfer paths on that channel for the whole duration of the drain. So
drop chan->lock around the flush and clear xfer_cb() only afterwards, once
all the pending completions are drained.
Cc: <stable+noautosel@kernel.org> # Needs dmaengine driver fix as well
Fixes: 2547beb00ddb ("bus: mhi: ep: Add support for async DMA read operation")
Fixes: ee08acb58fe4 ("bus: mhi: ep: Add support for async DMA write operation")
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
---
drivers/bus/mhi/ep/main.c | 7 +++++++
include/linux/mhi_ep.h | 2 ++
2 files changed, 9 insertions(+)
diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c
index 0277e1ab1198..329a4855d397 100644
--- a/drivers/bus/mhi/ep/main.c
+++ b/drivers/bus/mhi/ep/main.c
@@ -1612,6 +1612,7 @@ static void mhi_ep_remove(struct device *dev)
{
struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev);
struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver);
+ struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl;
struct mhi_result result = {};
struct mhi_ep_chan *mhi_chan;
int dir;
@@ -1636,6 +1637,12 @@ static void mhi_ep_remove(struct device *dev)
}
mhi_chan->state = MHI_CH_STATE_DISABLED;
+ mutex_unlock(&mhi_chan->lock);
+
+ if (mhi_cntrl->flush_async)
+ mhi_cntrl->flush_async(mhi_cntrl);
+
+ mutex_lock(&mhi_chan->lock);
mhi_chan->xfer_cb = NULL;
mutex_unlock(&mhi_chan->lock);
}
diff --git a/include/linux/mhi_ep.h b/include/linux/mhi_ep.h
index 7b40fc8cbe77..f6383a57a872 100644
--- a/include/linux/mhi_ep.h
+++ b/include/linux/mhi_ep.h
@@ -107,6 +107,7 @@ struct mhi_ep_buf_info {
* @write_sync: CB function for writing to host memory synchronously
* @read_async: CB function for reading from host memory asynchronously
* @write_async: CB function for writing to host memory asynchronously
+ * @flush_async: CB function for flushing asynchronous read/writes
* @mhi_state: MHI Endpoint state
* @max_chan: Maximum channels supported by the endpoint controller
* @mru: MRU (Maximum Receive Unit) value of the endpoint controller
@@ -164,6 +165,7 @@ struct mhi_ep_cntrl {
int (*write_sync)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info);
int (*read_async)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info);
int (*write_async)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info);
+ void (*flush_async)(struct mhi_ep_cntrl *mhi_cntrl);
enum mhi_state mhi_state;
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/3] PCI: epf-mhi: Implement mhi_cntrl->flush_async() to flush DMA read/write
2026-06-29 8:45 [PATCH 0/3] bus: mhi: ep: Implement flush_async() callback to flush async read/write Manivannan Sadhasivam via B4 Relay
2026-06-29 8:45 ` [PATCH 1/3] dmaengine: dw-edma: Implement device_synchronize() callback Manivannan Sadhasivam via B4 Relay
2026-06-29 8:45 ` [PATCH 2/3] bus: mhi: ep: Add mhi_cntrl->flush_async() callback to flush the async read/write Manivannan Sadhasivam via B4 Relay
@ 2026-06-29 8:45 ` Manivannan Sadhasivam via B4 Relay
2 siblings, 0 replies; 5+ messages in thread
From: Manivannan Sadhasivam via B4 Relay @ 2026-06-29 8:45 UTC (permalink / raw)
To: Manivannan Sadhasivam, Vinod Koul, Frank Li,
Krzysztof Wilczyński, Kishon Vijay Abraham I, Bjorn Helgaas
Cc: dmaengine, linux-kernel, mhi, linux-arm-msm, linux-pci,
Manivannan Sadhasivam
From: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
The MHI core needs to make sure that all the current DMA transactions are
completed before removing the channels. So implement the
mhi_cntrl->flush_async() callback by first making sure all the in-flight
DMA operations are completed and then flushing the DMA workqueue.
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
---
drivers/pci/endpoint/functions/pci-epf-mhi.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/pci/endpoint/functions/pci-epf-mhi.c b/drivers/pci/endpoint/functions/pci-epf-mhi.c
index 7f5326925ed5..4af3689921a3 100644
--- a/drivers/pci/endpoint/functions/pci-epf-mhi.c
+++ b/drivers/pci/endpoint/functions/pci-epf-mhi.c
@@ -644,6 +644,15 @@ static int pci_epf_mhi_edma_write_async(struct mhi_ep_cntrl *mhi_cntrl,
return ret;
}
+static void pci_epf_mhi_edma_flush_async(struct mhi_ep_cntrl *mhi_cntrl)
+{
+ struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
+
+ dmaengine_terminate_sync(epf_mhi->dma_chan_rx);
+ dmaengine_terminate_sync(epf_mhi->dma_chan_tx);
+ flush_workqueue(epf_mhi->dma_wq);
+}
+
struct epf_dma_filter {
struct device *dev;
u32 dma_mask;
@@ -812,6 +821,7 @@ static int pci_epf_mhi_link_up(struct pci_epf *epf)
mhi_cntrl->write_sync = pci_epf_mhi_edma_write;
mhi_cntrl->read_async = pci_epf_mhi_edma_read_async;
mhi_cntrl->write_async = pci_epf_mhi_edma_write_async;
+ mhi_cntrl->flush_async = pci_epf_mhi_edma_flush_async;
}
/* Register the MHI EP controller */
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread