summaryrefslogtreecommitdiffstats
path: root/drivers/dma
diff options
context:
space:
mode:
authorSerge Semin <Sergey.Semin@baikalelectronics.ru>2023-01-13 18:13:49 +0100
committerLorenzo Pieralisi <lpieralisi@kernel.org>2023-01-27 17:15:33 +0100
commit993d57bbaacf7ad7a742e51cb717e21e0eb4ef72 (patch)
tree8286f6d2cf73310ea1d640b6ae8ce6e19d60b633 /drivers/dma
parentdmaengine: dw-edma: Fix invalid interleaved xfers semantics (diff)
downloadlinux-993d57bbaacf7ad7a742e51cb717e21e0eb4ef72.tar.xz
linux-993d57bbaacf7ad7a742e51cb717e21e0eb4ef72.zip
dmaengine: dw-edma: Add CPU to PCI bus address translation
Since 9575632052ba ("dmaengine: make slave address physical"), the source and destination addresses of the DMA slave device have been converted to physical addresses in the CPU address space. It's the DMA device driver's responsibility to convert them to the DMA bus address space. In case of the DW eDMA device, the source or destination peripheral (slave) devices reside in PCI bus space. Thus we need to perform the PCI Host/Endpoint windows- based (i.e. DT "ranges" property) address translation; otherwise the eDMA transactions won't work as expected (or can be even harmful) if the CPU and PCI address spaces don't match. Note 1: Even though the DMA interleaved template has both source and destination addresses declared as dma_addr_t, only the CPU memory range should be mapped to be seen by the DMA device since it's a subject of the DMA getting towards the system side. The device part must not be mapped since the slave device resides in the PCI bus space, which isn't affected by IOMMUs or iATU translations. DW PCIe eDMA generates corresponding MWr/MRd TLPs on its own. Note 2: This functionality is mainly required for the remote eDMA setup since the CPU address must be manually translated into the PCI bus space before being written to LLI.{SAR,DAR}. If eDMA is embedded in the locally accessible DW PCIe Root Port/Endpoint, software-based translation isn't required since hardware will translate it via the Outbound iATU as long as the DMA_BYPASS flag is cleared. If DMA_BYPASS is set or there is no Outbound iATU entry that contains the SAR or DAR (for Read and Write channel respectively), there won't be any translation performed but DMA will proceed with the corresponding source/destination address as-is. Link: https://lore.kernel.org/r/20230113171409.30470-8-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Acked-by: Vinod Koul <vkoul@kernel.org>
Diffstat (limited to 'drivers/dma')
-rw-r--r--drivers/dma/dw-edma/dw-edma-core.c18
1 files changed, 17 insertions, 1 deletions
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index d5c4192141ef..6c9f95a8e397 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -39,6 +39,17 @@ struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
return container_of(vd, struct dw_edma_desc, vd);
}
+static inline
+u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr)
+{
+ struct dw_edma_chip *chip = chan->dw->chip;
+
+ if (chip->ops->pci_address)
+ return chip->ops->pci_address(chip->dev, cpu_addr);
+
+ return cpu_addr;
+}
+
static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk)
{
struct dw_edma_burst *burst;
@@ -327,11 +338,11 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan);
enum dma_transfer_direction dir = xfer->direction;
- phys_addr_t src_addr, dst_addr;
struct scatterlist *sg = NULL;
struct dw_edma_chunk *chunk;
struct dw_edma_burst *burst;
struct dw_edma_desc *desc;
+ u64 src_addr, dst_addr;
size_t fsz = 0;
u32 cnt = 0;
int i;
@@ -406,6 +417,11 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer)
dst_addr = chan->config.dst_addr;
}
+ if (dir == DMA_DEV_TO_MEM)
+ src_addr = dw_edma_get_pci_address(chan, (phys_addr_t)src_addr);
+ else
+ dst_addr = dw_edma_get_pci_address(chan, (phys_addr_t)dst_addr);
+
if (xfer->type == EDMA_XFER_CYCLIC) {
cnt = xfer->xfer.cyclic.cnt;
} else if (xfer->type == EDMA_XFER_SCATTER_GATHER) {