Skip to content

Commit

Permalink
dma-mapping: introduce DMA range map, supplanting dma_pfn_offset
Browse files Browse the repository at this point in the history
The new field 'dma_range_map' in struct device is used to facilitate the
use of single or multiple offsets between mapping regions of cpu addrs and
dma addrs.  It subsumes the role of "dev->dma_pfn_offset" which was only
capable of holding a single uniform offset and had no region bounds
checking.

The function of_dma_get_range() has been modified so that it takes a single
argument -- the device node -- and returns a map, NULL, or an error code.
The map is an array that holds the information regarding the DMA regions.
Each range entry contains the address offset, the cpu_start address, the
dma_start address, and the size of the region.

of_dma_configure() is the typical manner to set range offsets but there are
a number of ad hoc assignments to "dev->dma_pfn_offset" in the kernel
driver code.  These cases now invoke the function
dma_direct_set_offset(dev, cpu_addr, dma_addr, size).

Signed-off-by: Jim Quinlan <james.quinlan@broadcom.com>
[hch: various interface cleanups]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Mathieu Poirier <mathieu.poirier@linaro.org>
Tested-by: Mathieu Poirier <mathieu.poirier@linaro.org>
Tested-by: Nathan Chancellor <natechancellor@gmail.com>
  • Loading branch information
Jim Quinlan authored and Christoph Hellwig committed Sep 17, 2020
1 parent 6eb0233 commit e0d0727
Show file tree
Hide file tree
Showing 22 changed files with 285 additions and 118 deletions.
9 changes: 4 additions & 5 deletions arch/arm/include/asm/dma-direct.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@
#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_range_map)
pfn = PFN_DOWN(translate_phys_to_dma(dev, PFN_PHYS(pfn)));
return (dma_addr_t)__pfn_to_bus(pfn);
}

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_range_map)
pfn = PFN_DOWN(translate_dma_to_phys(dev, PFN_PHYS(pfn)));
return pfn;
}

Expand Down
17 changes: 8 additions & 9 deletions arch/arm/mach-keystone/keystone.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -25,8 +26,6 @@
#include "keystone.h"

#ifdef CONFIG_ARM_LPAE
static unsigned long keystone_dma_pfn_offset __read_mostly;

static int keystone_platform_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
Expand All @@ -39,9 +38,12 @@ 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 = dma_direct_set_offset(dev, KEYSTONE_HIGH_PHYS_START,
KEYSTONE_LOW_PHYS_START,
KEYSTONE_HIGH_PHYS_SIZE);
dev_err(dev, "set dma_offset%08llx%s\n",
KEYSTONE_HIGH_PHYS_START - KEYSTONE_LOW_PHYS_START,
ret ? " failed" : "");
}
return NOTIFY_OK;
}
Expand All @@ -54,11 +56,8 @@ static struct notifier_block platform_nb = {
static void __init keystone_init(void)
{
#ifdef CONFIG_ARM_LPAE
if (PHYS_OFFSET >= KEYSTONE_HIGH_PHYS_START) {
keystone_dma_pfn_offset = PFN_DOWN(KEYSTONE_HIGH_PHYS_START -
KEYSTONE_LOW_PHYS_START);
if (PHYS_OFFSET >= KEYSTONE_HIGH_PHYS_START)
bus_register_notifier(&platform_bus_type, &platform_nb);
}
#endif
keystone_pm_runtime_init();
}
Expand Down
4 changes: 4 additions & 0 deletions arch/arm/mach-omap1/include/mach/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
__phys_to_pfn(__dma); \
})

#define __arch_dma_to_virt(dev, addr) ({ (void *) (is_lbus_device(dev) ? \
lbus_to_virt(addr) : \
__phys_to_virt(addr)); })

#define __arch_virt_to_dma(dev, addr) ({ unsigned long __addr = (unsigned long)(addr); \
(dma_addr_t) (is_lbus_device(dev) ? \
virt_to_lbus(__addr) : \
Expand Down
9 changes: 5 additions & 4 deletions arch/sh/drivers/pci/pcie-sh7786.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -31,6 +32,8 @@ struct sh7786_pcie_port {
static struct sh7786_pcie_port *sh7786_pcie_ports;
static unsigned int nr_ports;
static unsigned long dma_pfn_offset;
size_t memsize;
u64 memstart;

static struct sh7786_pcie_hwops {
int (*core_init)(void);
Expand Down Expand Up @@ -301,7 +304,6 @@ static int __init pcie_init(struct sh7786_pcie_port *port)
struct pci_channel *chan = port->hose;
unsigned int data;
phys_addr_t memstart, memend;
size_t memsize;
int ret, i, win;

/* Begin initialization */
Expand Down Expand Up @@ -368,8 +370,6 @@ static int __init pcie_init(struct sh7786_pcie_port *port)
memstart = ALIGN_DOWN(memstart, memsize);
memsize = roundup_pow_of_two(memend - memstart);

dma_pfn_offset = memstart >> PAGE_SHIFT;

/*
* If there's more than 512MB of memory, we need to roll over to
* LAR1/LAMR1.
Expand Down Expand Up @@ -487,7 +487,8 @@ 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;
dma_direct_set_offset(&pdev->dev, __pa(memory_start),
__pa(memory_start) - memstart, memsize);
}

static int __init sh7786_pcie_core_init(void)
Expand Down
6 changes: 4 additions & 2 deletions arch/x86/pci/sta2x11-fixup.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,17 @@ 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;

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 = dma_direct_set_offset(dev, 0, amba_base, STA2X11_AMBA_SIZE);
if (ret)
dev_err(dev, "sta2x11: could not set DMA offset\n");

dev->bus_dma_limit = max_amba_addr;
pci_set_consistent_dma_mask(pdev, max_amba_addr);
Expand Down
6 changes: 4 additions & 2 deletions drivers/acpi/arm64/iort.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>

#define IORT_TYPE_MASK(type) (1 << (type))
#define IORT_MSI_TYPE (1 << ACPI_IORT_NODE_ITS_GROUP)
Expand Down Expand Up @@ -1184,8 +1185,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 = dma_direct_set_offset(dev, dmaaddr + offset, dmaaddr, size);

dev_dbg(dev, "dma_offset(%#08llx)%s\n", offset, ret ? " failed!" : "");
}

static void __init acpi_iort_register_irq(int hwirq, const char *name,
Expand Down
2 changes: 2 additions & 0 deletions drivers/base/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,8 @@ static void device_release(struct kobject *kobj)
*/
devres_release_all(dev);

kfree(dev->dma_range_map);

if (dev->release)
dev->release(dev);
else if (dev->type && dev->type->release)
Expand Down
8 changes: 7 additions & 1 deletion drivers/gpu/drm/sun4i/sun4i_backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -811,8 +812,13 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
* because of an old DT, we need to set the DMA offset by hand
* on our device since the RAM mapping is at 0 for the DMA bus,
* unlike the CPU.
*
* XXX(hch): this has no business in a driver and needs to move
* to the device tree.
*/
drm->dev->dma_pfn_offset = PHYS_PFN_OFFSET;
ret = dma_direct_set_offset(drm->dev, PHYS_OFFSET, 0, SZ_4G);
if (ret)
return ret;
}

backend->engine.node = dev->of_node;
Expand Down
2 changes: 1 addition & 1 deletion drivers/iommu/io-pgtable-arm.c
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,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_range_map) {
dev_err(cfg->iommu_dev, "Cannot accommodate DMA offset for IOMMU page tables\n");
return NULL;
}
Expand Down
9 changes: 8 additions & 1 deletion drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -182,8 +183,14 @@ static int sun4i_csi_probe(struct platform_device *pdev)
if (ret)
return ret;
} else {
/*
* XXX(hch): this has no business in a driver and needs to move
* to the device tree.
*/
#ifdef PHYS_PFN_OFFSET
csi->dev->dma_pfn_offset = PHYS_PFN_OFFSET;
ret = dma_direct_set_offset(csi->dev, PHYS_OFFSET, 0, SZ_4G);
if (ret)
return ret;
#endif
}

Expand Down
11 changes: 9 additions & 2 deletions drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
Original file line number Diff line number Diff line change
Expand Up @@ -899,8 +899,15 @@ static int sun6i_csi_probe(struct platform_device *pdev)
return -ENOMEM;

sdev->dev = &pdev->dev;
/* The DMA bus has the memory mapped at 0 */
sdev->dev->dma_pfn_offset = PHYS_OFFSET >> PAGE_SHIFT;
/*
* The DMA bus has the memory mapped at 0.
*
* XXX(hch): this has no business in a driver and needs to move
* to the device tree.
*/
ret = dma_direct_set_offset(sdev->dev, PHYS_OFFSET, 0, SZ_4G);
if (ret)
return ret;

ret = sun6i_csi_resource_request(sdev, pdev);
if (ret)
Expand Down
73 changes: 32 additions & 41 deletions drivers/of/address.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/dma-direct.h> /* for bus_dma_region */

#include "of_private.h"

Expand Down Expand Up @@ -937,33 +938,33 @@ void __iomem *of_io_request_and_map(struct device_node *np, int index,
}
EXPORT_SYMBOL(of_io_request_and_map);

#ifdef CONFIG_HAS_DMA
/**
* of_dma_get_range - Get DMA range info
* of_dma_get_range - Get DMA range info and put it into a map array
* @np: device node to get DMA range info
* @dma_addr: pointer to store initial DMA address of DMA range
* @paddr: pointer to store initial CPU address of DMA range
* @size: pointer to store size of DMA range
* @map: dma range structure to return
*
* Look in bottom up direction for the first "dma-ranges" property
* and parse it.
* dma-ranges format:
* and parse it. Put the information into a DMA offset map array.
*
* dma-ranges format:
* DMA addr (dma_addr) : naddr cells
* CPU addr (phys_addr_t) : pna cells
* size : nsize cells
*
* It returns -ENODEV if "dma-ranges" property was not found
* for this device in DT.
* It returns -ENODEV if "dma-ranges" property was not found for this
* device in the DT.
*/
int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *size)
int of_dma_get_range(struct device_node *np, const struct bus_dma_region **map)
{
struct device_node *node = of_node_get(np);
const __be32 *ranges = NULL;
int len;
int ret = 0;
bool found_dma_ranges = false;
struct of_range_parser parser;
struct of_range range;
u64 dma_start = U64_MAX, dma_end = 0, dma_offset = 0;
struct bus_dma_region *r;
int len, num_ranges = 0;
int ret = 0;

while (node) {
ranges = of_get_property(node, "dma-ranges", &len);
Expand All @@ -989,49 +990,39 @@ int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *siz
}

of_dma_range_parser_init(&parser, node);
for_each_of_range(&parser, &range)
num_ranges++;

r = kcalloc(num_ranges + 1, sizeof(*r), GFP_KERNEL);
if (!r) {
ret = -ENOMEM;
goto out;
}

/*
* Record all info in the generic DMA ranges array for struct device.
*/
*map = r;
of_dma_range_parser_init(&parser, node);
for_each_of_range(&parser, &range) {
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;
}
if (range.cpu_addr == OF_BAD_ADDR) {
pr_err("translation of DMA address(%llx) to CPU address failed node(%pOF)\n",
range.bus_addr, node);
continue;
}
dma_offset = range.cpu_addr - range.bus_addr;

/* Take lower and upper limits */
if (range.bus_addr < dma_start)
dma_start = range.bus_addr;
if (range.bus_addr + range.size > dma_end)
dma_end = range.bus_addr + range.size;
}

if (dma_start >= dma_end) {
ret = -EINVAL;
pr_debug("Invalid DMA ranges configuration on node(%pOF)\n",
node);
goto out;
r->cpu_start = range.cpu_addr;
r->dma_start = range.bus_addr;
r->size = range.size;
r->offset = range.cpu_addr - range.bus_addr;
r++;
}

*dma_addr = dma_start;
*size = dma_end - dma_start;
*paddr = dma_start + dma_offset;

pr_debug("final: dma_addr(%llx) cpu_addr(%llx) size(%llx)\n",
*dma_addr, *paddr, *size);

out:
of_node_put(node);

return ret;
}
#endif /* CONFIG_HAS_DMA */

/**
* of_dma_is_coherent - Check if device is coherent
Expand Down
Loading

0 comments on commit e0d0727

Please sign in to comment.