[PATCH v3 09/13] device core: Introduce multiple dma pfn offsets
Jim Quinlan
james.quinlan at broadcom.com
Wed Jun 3 19:20:41 UTC 2020
The new field in struct device 'dma_pfn_offset_map' is used to facilitate
the use of multiple pfn offsets between cpu addrs and dma addrs. It
subsumes the role of dev->dma_pfn_offset -- a uniform offset -- and
designates the single offset a special case.
of_dma_configure() is the typical manner to set pfn offsets but there
are a number of ad hoc assignments to dev->dma_pfn_offset in the
kernel code. These cases now invoke the function
attach_uniform_dma_pfn_offset(dev, pfn_offset).
Signed-off-by: Jim Quinlan <james.quinlan at broadcom.com>
---
arch/arm/include/asm/dma-mapping.h | 9 +-
arch/arm/mach-keystone/keystone.c | 9 +-
arch/sh/drivers/pci/pcie-sh7786.c | 3 +-
arch/sh/kernel/dma-coherent.c | 17 ++--
arch/x86/pci/sta2x11-fixup.c | 7 +-
drivers/acpi/arm64/iort.c | 5 +-
drivers/gpu/drm/sun4i/sun4i_backend.c | 7 +-
drivers/iommu/io-pgtable-arm.c | 2 +-
.../platform/sunxi/sun4i-csi/sun4i_csi.c | 5 +-
.../platform/sunxi/sun6i-csi/sun6i_csi.c | 5 +-
drivers/of/address.c | 93 +++++++++++++++++--
drivers/of/device.c | 8 +-
drivers/remoteproc/remoteproc_core.c | 2 +-
.../staging/media/sunxi/cedrus/cedrus_hw.c | 7 +-
drivers/usb/core/message.c | 4 +-
drivers/usb/core/usb.c | 2 +-
include/linux/device.h | 4 +-
include/linux/dma-direct.h | 16 +++-
include/linux/dma-mapping.h | 45 +++++++++
kernel/dma/coherent.c | 11 ++-
20 files changed, 210 insertions(+), 51 deletions(-)
diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h
index bdd80ddbca34..f1e72f99468b 100644
--- a/arch/arm/include/asm/dma-mapping.h
+++ b/arch/arm/include/asm/dma-mapping.h
@@ -35,8 +35,9 @@ static inline const struct dma_map_ops *get_arch_dma_ops(struct bus_type *bus)
#ifndef __arch_pfn_to_dma
static inline dma_addr_t pfn_to_dma(struct device *dev, unsigned long pfn)
{
- if (dev)
- pfn -= dev->dma_pfn_offset;
+ if (dev && dev->dma_pfn_offset_map)
+ pfn -= dma_pfn_offset_from_phys_addr(dev, PFN_PHYS(pfn));
+
return (dma_addr_t)__pfn_to_bus(pfn);
}
@@ -44,8 +45,8 @@ static inline unsigned long dma_to_pfn(struct device *dev, dma_addr_t addr)
{
unsigned long pfn = __bus_to_pfn(addr);
- if (dev)
- pfn += dev->dma_pfn_offset;
+ if (dev && dev->dma_pfn_offset_map)
+ pfn += dma_pfn_offset_from_dma_addr(dev, addr);
return pfn;
}
diff --git a/arch/arm/mach-keystone/keystone.c b/arch/arm/mach-keystone/keystone.c
index 638808c4e122..e7d3ee6e9cb5 100644
--- a/arch/arm/mach-keystone/keystone.c
+++ b/arch/arm/mach-keystone/keystone.c
@@ -8,6 +8,7 @@
*/
#include <linux/io.h>
#include <linux/of.h>
+#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
@@ -38,9 +39,11 @@ static int keystone_platform_notifier(struct notifier_block *nb,
return NOTIFY_BAD;
if (!dev->of_node) {
- dev->dma_pfn_offset = keystone_dma_pfn_offset;
- dev_err(dev, "set dma_pfn_offset%08lx\n",
- dev->dma_pfn_offset);
+ int ret = attach_uniform_dma_pfn_offset
+ (dev, keystone_dma_pfn_offset);
+
+ dev_err(dev, "set dma_pfn_offset%08lx%s\n",
+ dev->dma_pfn_offset, ret ? " failed" : "");
}
return NOTIFY_OK;
}
diff --git a/arch/sh/drivers/pci/pcie-sh7786.c b/arch/sh/drivers/pci/pcie-sh7786.c
index e0b568aaa701..2e832a5c58c1 100644
--- a/arch/sh/drivers/pci/pcie-sh7786.c
+++ b/arch/sh/drivers/pci/pcie-sh7786.c
@@ -12,6 +12,7 @@
#include <linux/io.h>
#include <linux/async.h>
#include <linux/delay.h>
+#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/sh_clk.h>
@@ -487,7 +488,7 @@ int pcibios_map_platform_irq(const struct pci_dev *pdev, u8 slot, u8 pin)
void pcibios_bus_add_device(struct pci_dev *pdev)
{
- pdev->dev.dma_pfn_offset = dma_pfn_offset;
+ attach_uniform_dma_pfn_offset(&pdev->dev, dma_pfn_offset);
}
static int __init sh7786_pcie_core_init(void)
diff --git a/arch/sh/kernel/dma-coherent.c b/arch/sh/kernel/dma-coherent.c
index d4811691b93c..5fc9e358b6c7 100644
--- a/arch/sh/kernel/dma-coherent.c
+++ b/arch/sh/kernel/dma-coherent.c
@@ -14,6 +14,8 @@ void *arch_dma_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle,
{
void *ret, *ret_nocache;
int order = get_order(size);
+ unsigned long pfn;
+ phys_addr_t phys;
gfp |= __GFP_ZERO;
@@ -34,11 +36,14 @@ void *arch_dma_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle,
return NULL;
}
- split_page(pfn_to_page(virt_to_phys(ret) >> PAGE_SHIFT), order);
+ phys = virt_to_phys(ret);
+ pfn = phys >> PAGE_SHIFT;
+ split_page(pfn_to_page(pfn), order);
- *dma_handle = virt_to_phys(ret);
- if (!WARN_ON(!dev))
- *dma_handle -= PFN_PHYS(dev->dma_pfn_offset);
+ *dma_handle = (dma_addr_t)phys;
+ if (!WARN_ON(!dev) && dev->dma_pfn_offset_map)
+ *dma_handle -= PFN_PHYS(
+ dma_pfn_offset_from_phys_addr(dev, phys));
return ret_nocache;
}
@@ -50,8 +55,8 @@ void arch_dma_free(struct device *dev, size_t size, void *vaddr,
unsigned long pfn = (dma_handle >> PAGE_SHIFT);
int k;
- if (!WARN_ON(!dev))
- pfn += dev->dma_pfn_offset;
+ if (!WARN_ON(!dev) && dev->dma_pfn_offset_map)
+ pfn += dma_pfn_offset_from_dma_addr(dev, dma_handle);
for (k = 0; k < (1 << order); k++)
__free_pages(pfn_to_page(pfn + k), 0);
diff --git a/arch/x86/pci/sta2x11-fixup.c b/arch/x86/pci/sta2x11-fixup.c
index c313d784efab..4cdeca9f69b6 100644
--- a/arch/x86/pci/sta2x11-fixup.c
+++ b/arch/x86/pci/sta2x11-fixup.c
@@ -12,6 +12,7 @@
#include <linux/export.h>
#include <linux/list.h>
#include <linux/dma-direct.h>
+#include <linux/dma-mapping.h>
#include <asm/iommu.h>
#define STA2X11_SWIOTLB_SIZE (4*1024*1024)
@@ -133,7 +134,7 @@ static void sta2x11_map_ep(struct pci_dev *pdev)
struct sta2x11_instance *instance = sta2x11_pdev_to_instance(pdev);
struct device *dev = &pdev->dev;
u32 amba_base, max_amba_addr;
- int i;
+ int i, ret;
if (!instance)
return;
@@ -141,7 +142,9 @@ static void sta2x11_map_ep(struct pci_dev *pdev)
pci_read_config_dword(pdev, AHB_BASE(0), &amba_base);
max_amba_addr = amba_base + STA2X11_AMBA_SIZE - 1;
- dev->dma_pfn_offset = PFN_DOWN(-amba_base);
+ ret = attach_uniform_dma_pfn_offset(dev, PFN_DOWN(-amba_base));
+ if (ret)
+ dev_err(dev, "sta2x11: could not set PFN offset\n");
dev->bus_dma_limit = max_amba_addr;
pci_set_consistent_dma_mask(pdev, max_amba_addr);
diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c
index 28a6b387e80e..153661ddc74b 100644
--- a/drivers/acpi/arm64/iort.c
+++ b/drivers/acpi/arm64/iort.c
@@ -1142,8 +1142,9 @@ void iort_dma_setup(struct device *dev, u64 *dma_addr, u64 *dma_size)
*dma_addr = dmaaddr;
*dma_size = size;
- dev->dma_pfn_offset = PFN_DOWN(offset);
- dev_dbg(dev, "dma_pfn_offset(%#08llx)\n", offset);
+ ret = attach_uniform_dma_pfn_offset(dev, PFN_DOWN(offset));
+ dev_dbg(dev, "dma_pfn_offset(%#08llx)%s\n",
+ offset, ret ? " failed!" : "");
}
static void __init acpi_iort_register_irq(int hwirq, const char *name,
diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c
index 072ea113e6be..3d41dfc7d178 100644
--- a/drivers/gpu/drm/sun4i/sun4i_backend.c
+++ b/drivers/gpu/drm/sun4i/sun4i_backend.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
+#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
@@ -786,7 +787,7 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
const struct sun4i_backend_quirks *quirks;
struct resource *res;
void __iomem *regs;
- int i, ret;
+ int i, ret = 0;
backend = devm_kzalloc(dev, sizeof(*backend), GFP_KERNEL);
if (!backend)
@@ -812,7 +813,9 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
* on our device since the RAM mapping is at 0 for the DMA bus,
* unlike the CPU.
*/
- drm->dev->dma_pfn_offset = PHYS_PFN_OFFSET;
+ ret = attach_uniform_dma_pfn_offset(dev, PHYS_PFN_OFFSET);
+ if (ret)
+ return ret;
}
backend->engine.node = dev->of_node;
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 04fbd4bf0ff9..e9cc1c2d47cd 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -754,7 +754,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg)
if (cfg->oas > ARM_LPAE_MAX_ADDR_BITS)
return NULL;
- if (!selftest_running && cfg->iommu_dev->dma_pfn_offset) {
+ if (!selftest_running && cfg->iommu_dev->dma_pfn_offset_map) {
dev_err(cfg->iommu_dev, "Cannot accommodate DMA offset for IOMMU page tables\n");
return NULL;
}
diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
index eff34ded6305..7212da5e1076 100644
--- a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
+++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
@@ -7,6 +7,7 @@
*/
#include <linux/clk.h>
+#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -183,7 +184,9 @@ static int sun4i_csi_probe(struct platform_device *pdev)
return ret;
} else {
#ifdef PHYS_PFN_OFFSET
- csi->dev->dma_pfn_offset = PHYS_PFN_OFFSET;
+ ret = attach_uniform_dma_pfn_offset(dev, PHYS_PFN_OFFSET);
+ if (ret)
+ return ret;
#endif
}
diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
index 055eb0b8e396..2d66d415b6c3 100644
--- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
+++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
@@ -898,7 +898,10 @@ static int sun6i_csi_probe(struct platform_device *pdev)
sdev->dev = &pdev->dev;
/* The DMA bus has the memory mapped at 0 */
- sdev->dev->dma_pfn_offset = PHYS_OFFSET >> PAGE_SHIFT;
+ ret = attach_uniform_dma_pfn_offset(sdev->dev,
+ PHYS_OFFSET >> PAGE_SHIFT);
+ if (ret)
+ return ret;
ret = sun6i_csi_resource_request(sdev, pdev);
if (ret)
diff --git a/drivers/of/address.c b/drivers/of/address.c
index 96d8cfb14a60..c89333b0a5fb 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -918,6 +918,70 @@ void __iomem *of_io_request_and_map(struct device_node *np, int index,
}
EXPORT_SYMBOL(of_io_request_and_map);
+static int attach_dma_pfn_offset_map(struct device *dev,
+ struct device_node *node, int num_ranges)
+{
+ struct of_range_parser parser;
+ struct of_range range;
+ struct dma_pfn_offset_region *r;
+
+ r = devm_kcalloc(dev, num_ranges + 1,
+ sizeof(struct dma_pfn_offset_region), GFP_KERNEL);
+ if (!r)
+ return -ENOMEM;
+ dev->dma_pfn_offset_map = r;
+ of_dma_range_parser_init(&parser, node);
+
+ /*
+ * Record all info for DMA ranges array. We could
+ * just use the of_range struct, but if we did that it
+ * would require more calculations for phys_to_dma and
+ * dma_to_phys conversions.
+ */
+ for_each_of_range(&parser, &range) {
+ r->cpu_start = range.cpu_addr;
+ r->cpu_end = r->cpu_start + range.size - 1;
+ r->dma_start = range.bus_addr;
+ r->dma_end = r->dma_start + range.size - 1;
+ r->pfn_offset = PFN_DOWN(range.cpu_addr)
+ - PFN_DOWN(range.bus_addr);
+ r++;
+ }
+ return 0;
+}
+
+
+
+/**
+ * attach_dma_pfn_offset - Assign scalar offset for all addresses.
+ * @dev: device pointer; only needed for a corner case.
+ * @dma_pfn_offset: offset to apply when converting from phys addr
+ * to dma addr and vice versa.
+ *
+ * It returns -ENOMEM if out of memory, otherwise 0.
+ */
+int attach_uniform_dma_pfn_offset(struct device *dev, unsigned long pfn_offset)
+{
+ struct dma_pfn_offset_region *r;
+
+ if (!dev)
+ return -ENODEV;
+
+ if (!pfn_offset)
+ return 0;
+
+ r = devm_kcalloc(dev, 1, sizeof(struct dma_pfn_offset_region),
+ GFP_KERNEL);
+ if (!r)
+ return -ENOMEM;
+
+ r->uniform_offset = true;
+ r->pfn_offset = pfn_offset;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(attach_uniform_dma_pfn_offset);
+
/**
* of_dma_get_range - Get DMA range info
* @dev: device pointer; only needed for a corner case.
@@ -933,7 +997,7 @@ EXPORT_SYMBOL(of_io_request_and_map);
* CPU addr (phys_addr_t) : pna cells
* size : nsize cells
*
- * It returns -ENODEV if "dma-ranges" property was not found
+ * It returns -ENODEV if !dev or "dma-ranges" property was not found
* for this device in DT.
*/
int of_dma_get_range(struct device *dev, struct device_node *np, u64 *dma_addr,
@@ -946,7 +1010,13 @@ int of_dma_get_range(struct device *dev, struct device_node *np, u64 *dma_addr,
bool found_dma_ranges = false;
struct of_range_parser parser;
struct of_range range;
+ phys_addr_t cpu_start = ~(phys_addr_t)0;
u64 dma_start = U64_MAX, dma_end = 0, dma_offset = 0;
+ bool dma_multi_pfn_offset = false;
+ int num_ranges = 0;
+
+ if (!dev)
+ return -ENODEV;
while (node) {
ranges = of_get_property(node, "dma-ranges", &len);
@@ -977,11 +1047,10 @@ int of_dma_get_range(struct device *dev, struct device_node *np, u64 *dma_addr,
pr_debug("dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
range.bus_addr, range.cpu_addr, range.size);
- if (dma_offset && range.cpu_addr - range.bus_addr != dma_offset) {
- pr_warn("Can't handle multiple dma-ranges with different offsets on node(%pOF)\n", node);
- /* Don't error out as we'd break some existing DTs */
- continue;
- }
+ num_ranges++;
+ if (dma_offset && range.cpu_addr - range.bus_addr != dma_offset)
+ dma_multi_pfn_offset = true;
+
dma_offset = range.cpu_addr - range.bus_addr;
/* Take lower and upper limits */
@@ -989,6 +1058,8 @@ int of_dma_get_range(struct device *dev, struct device_node *np, u64 *dma_addr,
dma_start = range.bus_addr;
if (range.bus_addr + range.size > dma_end)
dma_end = range.bus_addr + range.size;
+ if (range.cpu_addr < cpu_start)
+ cpu_start = range.cpu_addr;
}
if (dma_start >= dma_end) {
@@ -998,9 +1069,17 @@ int of_dma_get_range(struct device *dev, struct device_node *np, u64 *dma_addr,
goto out;
}
+ if (dma_multi_pfn_offset)
+ ret = attach_dma_pfn_offset_map(dev, node, num_ranges);
+ else if (dma_offset)
+ ret = attach_uniform_dma_pfn_offset(dev, PFN_DOWN(dma_offset));
+
+ if (ret)
+ goto out;
+
*dma_addr = dma_start;
*size = dma_end - dma_start;
- *paddr = dma_start + dma_offset;
+ *paddr = cpu_start;
pr_debug("final: dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
*dma_addr, *paddr, *size);
diff --git a/drivers/of/device.c b/drivers/of/device.c
index ef6a741f9f0b..91c50f40a82e 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -91,7 +91,6 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
u64 dma_addr, paddr, size = 0;
int ret;
bool coherent;
- unsigned long offset;
const struct iommu_ops *iommu;
u64 mask, end;
@@ -105,10 +104,8 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
if (!force_dma)
return ret == -ENODEV ? 0 : ret;
- dma_addr = offset = 0;
+ dma_addr = 0;
} else {
- offset = PFN_DOWN(paddr - dma_addr);
-
/*
* Add a work around to treat the size as mask + 1 in case
* it is defined in DT as a mask.
@@ -123,7 +120,6 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
dev_err(dev, "Adjusted size 0x%llx invalid\n", size);
return -EINVAL;
}
- dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", offset);
}
/*
@@ -142,8 +138,6 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
else if (!size)
size = 1ULL << 32;
- dev->dma_pfn_offset = offset;
-
/*
* Limit coherent and dma mask based on size and default mask
* set by the driver.
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index e12a54e67588..e49648f77261 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -518,7 +518,7 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
/* Initialise vdev subdevice */
snprintf(name, sizeof(name), "vdev%dbuffer", rvdev->index);
rvdev->dev.parent = rproc->dev.parent;
- rvdev->dev.dma_pfn_offset = rproc->dev.parent->dma_pfn_offset;
+ rvdev->dev.dma_pfn_offset_map = rproc->dev.parent->dma_pfn_offset_map;
rvdev->dev.release = rproc_rvdev_release;
dev_set_name(&rvdev->dev, "%s#%s", dev_name(rvdev->dev.parent), name);
dev_set_drvdata(&rvdev->dev, rvdev);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
index daf5f244f93b..29217e6e4153 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
@@ -171,8 +171,11 @@ int cedrus_hw_probe(struct cedrus_dev *dev)
*/
#ifdef PHYS_PFN_OFFSET
- if (!(variant->quirks & CEDRUS_QUIRK_NO_DMA_OFFSET))
- dev->dev->dma_pfn_offset = PHYS_PFN_OFFSET;
+ if (!(variant->quirks & CEDRUS_QUIRK_NO_DMA_OFFSET)) {
+ ret = attach_uniform_dma_pfn_offset(dev, PHYS_PFN_OFFSET);
+ if (ret)
+ return ret;
+ }
#endif
ret = of_reserved_mem_device_init(dev->dev);
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 6197938dcc2d..071856000428 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1956,10 +1956,10 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
intf->dev.groups = usb_interface_groups;
/*
* Please refer to usb_alloc_dev() to see why we set
- * dma_mask and dma_pfn_offset.
+ * dma_mask and dma_pfn_offset_map.
*/
intf->dev.dma_mask = dev->dev.dma_mask;
- intf->dev.dma_pfn_offset = dev->dev.dma_pfn_offset;
+ intf->dev.dma_pfn_offset_map = dev->dev.dma_pfn_offset_map;
INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
intf->minor = -1;
device_initialize(&intf->dev);
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index f16c26dc079d..3fbc0c06ce9c 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -611,7 +611,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
* mask for the entire HCD, so don't do that.
*/
dev->dev.dma_mask = bus->sysdev->dma_mask;
- dev->dev.dma_pfn_offset = bus->sysdev->dma_pfn_offset;
+ dev->dev.dma_pfn_offset_map = bus->sysdev->dma_pfn_offset_map;
set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
dev->state = USB_STATE_ATTACHED;
dev->lpm_disable_count = 1;
diff --git a/include/linux/device.h b/include/linux/device.h
index ac8e37cd716a..9c079643ff54 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -492,7 +492,7 @@ struct dev_links_info {
* such descriptors.
* @bus_dma_limit: Limit of an upstream bridge or bus which imposes a smaller
* DMA limit than the device itself supports.
- * @dma_pfn_offset: offset of DMA memory range relatively of RAM
+ * @dma_pfn_offset_map: offset map for DMA memory range relatively of RAM
* @dma_parms: A low level driver may set these to teach IOMMU code about
* segment limitations.
* @dma_pools: Dma pools (if dma'ble device).
@@ -577,7 +577,7 @@ struct device {
64 bit addresses for consistent
allocations such descriptors. */
u64 bus_dma_limit; /* upstream dma constraint */
- unsigned long dma_pfn_offset;
+ struct dma_pfn_offset_region *dma_pfn_offset_map;
struct device_dma_parameters *dma_parms;
diff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h
index 24b8684aa21d..3c3363a3925e 100644
--- a/include/linux/dma-direct.h
+++ b/include/linux/dma-direct.h
@@ -15,14 +15,26 @@ static inline dma_addr_t __phys_to_dma(struct device *dev, phys_addr_t paddr)
{
dma_addr_t dev_addr = (dma_addr_t)paddr;
- return dev_addr - ((dma_addr_t)dev->dma_pfn_offset << PAGE_SHIFT);
+ if (dev->dma_pfn_offset_map) {
+ unsigned long dma_pfn_offset
+ = dma_pfn_offset_from_phys_addr(dev, paddr);
+
+ dev_addr -= ((dma_addr_t)dma_pfn_offset << PAGE_SHIFT);
+ }
+ return dev_addr;
}
static inline phys_addr_t __dma_to_phys(struct device *dev, dma_addr_t dev_addr)
{
phys_addr_t paddr = (phys_addr_t)dev_addr;
- return paddr + ((phys_addr_t)dev->dma_pfn_offset << PAGE_SHIFT);
+ if (dev->dma_pfn_offset_map) {
+ unsigned long dma_pfn_offset
+ = dma_pfn_offset_from_dma_addr(dev, dev_addr);
+
+ paddr += ((phys_addr_t)dma_pfn_offset << PAGE_SHIFT);
+ }
+ return paddr;
}
#endif /* !CONFIG_ARCH_HAS_PHYS_TO_DMA */
diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h
index 330ad58fbf4d..e0c2ec07c00a 100644
--- a/include/linux/dma-mapping.h
+++ b/include/linux/dma-mapping.h
@@ -256,6 +256,46 @@ static inline void dma_direct_sync_sg_for_cpu(struct device *dev,
size_t dma_direct_max_mapping_size(struct device *dev);
#ifdef CONFIG_HAS_DMA
+struct dma_pfn_offset_region {
+ bool uniform_offset;
+ phys_addr_t cpu_start;
+ phys_addr_t cpu_end;
+ dma_addr_t dma_start;
+ dma_addr_t dma_end;
+ unsigned long pfn_offset;
+};
+
+int attach_uniform_dma_pfn_offset(struct device *dev,
+ unsigned long dma_pfn_offset);
+
+static inline unsigned long dma_pfn_offset_from_dma_addr(struct device *dev,
+ dma_addr_t dma_addr)
+{
+ const struct dma_pfn_offset_region *m = dev->dma_pfn_offset_map;
+
+ if (m->uniform_offset)
+ return m->pfn_offset;
+
+ for (; m->cpu_end; m++)
+ if (dma_addr >= m->dma_start && dma_addr <= m->dma_end)
+ return m->pfn_offset;
+ return 0;
+}
+
+static inline unsigned long dma_pfn_offset_from_phys_addr(struct device *dev,
+ phys_addr_t paddr)
+{
+ const struct dma_pfn_offset_region *m = dev->dma_pfn_offset_map;
+
+ if (m->uniform_offset)
+ return m->pfn_offset;
+
+ for (; m->cpu_end; m++)
+ if (paddr >= m->cpu_start && paddr <= m->cpu_end)
+ return m->pfn_offset;
+ return 0;
+}
+
#include <asm/dma-mapping.h>
static inline const struct dma_map_ops *get_dma_ops(struct device *dev)
@@ -463,6 +503,11 @@ u64 dma_get_required_mask(struct device *dev);
size_t dma_max_mapping_size(struct device *dev);
unsigned long dma_get_merge_boundary(struct device *dev);
#else /* CONFIG_HAS_DMA */
+static inline int attach_uniform_dma_pfn_offset(struct device *dev,
+ unsigned long dma_pfn_offset)
+{
+ return -EIO;
+}
static inline dma_addr_t dma_map_page_attrs(struct device *dev,
struct page *page, size_t offset, size_t size,
enum dma_data_direction dir, unsigned long attrs)
diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c
index 2a0c4985f38e..20fc48d0b9d7 100644
--- a/kernel/dma/coherent.c
+++ b/kernel/dma/coherent.c
@@ -31,10 +31,13 @@ static inline struct dma_coherent_mem *dev_get_coherent_memory(struct device *de
static inline dma_addr_t dma_get_device_base(struct device *dev,
struct dma_coherent_mem * mem)
{
- if (mem->use_dev_dma_pfn_offset)
- return (mem->pfn_base - dev->dma_pfn_offset) << PAGE_SHIFT;
- else
- return mem->device_base;
+ if (mem->use_dev_dma_pfn_offset && dev->dma_pfn_offset_map) {
+ unsigned long dma_pfn_offset = dma_pfn_offset_from_phys_addr
+ (dev, PFN_PHYS(mem->pfn_base));
+
+ return (mem->pfn_base - dma_pfn_offset) << PAGE_SHIFT;
+ }
+ return mem->device_base;
}
static int dma_init_coherent_memory(phys_addr_t phys_addr,
--
2.17.1
More information about the dri-devel
mailing list