From 1cd076bf67793942ed921b766f7d461de2ebc0a2 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 14:40:58 +0100 Subject: iommu: provide early initialisation hook for IOMMU drivers IOMMU drivers must be initialised before any of their upstream devices, otherwise the relevant iommu_ops won't be configured for the bus in question. To solve this, a number of IOMMU drivers use initcalls to initialise the driver before anything has a chance to be probed. Whilst this solves the immediate problem, it leaves the job of probing the IOMMU completely separate from the iommu_ops to configure the IOMMU, which are called on a per-bus basis and require the driver to figure out exactly which instance of the IOMMU is being requested. In particular, the add_device callback simply passes a struct device to the driver, which then has to parse firmware tables or probe buses to identify the relevant IOMMU instance. This patch takes the first step in addressing this problem by adding an early initialisation pass for IOMMU drivers, giving them the ability to store some per-instance data in their iommu_ops structure and store that in their of_node. This can later be used when parsing OF masters to identify the IOMMU instance in question. Acked-by: Arnd Bergmann Acked-by: Joerg Roedel Acked-by: Marek Szyprowski Tested-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/iommu/of_iommu.c | 17 +++++++++++++++++ include/asm-generic/vmlinux.lds.h | 2 ++ include/linux/iommu.h | 2 ++ include/linux/of_iommu.h | 25 +++++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index e550ccb..89b9034 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -22,6 +22,9 @@ #include #include +static const struct of_device_id __iommu_of_table_sentinel + __used __section(__iommu_of_table_end); + /** * of_get_dma_window - Parse *dma-window property and returns 0 if found. * @@ -89,3 +92,17 @@ int of_get_dma_window(struct device_node *dn, const char *prefix, int index, return 0; } EXPORT_SYMBOL_GPL(of_get_dma_window); + +void __init of_iommu_init(void) +{ + struct device_node *np; + const struct of_device_id *match, *matches = &__iommu_of_table; + + for_each_matching_node_and_match(np, matches, &match) { + const of_iommu_init_fn init_fn = match->data; + + if (init_fn(np)) + pr_err("Failed to initialise IOMMU %s\n", + of_node_full_name(np)); + } +} diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index aa70cbd..bee5d68 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -164,6 +164,7 @@ #define CLKSRC_OF_TABLES() OF_TABLE(CONFIG_CLKSRC_OF, clksrc) #define IRQCHIP_OF_MATCH_TABLE() OF_TABLE(CONFIG_IRQCHIP, irqchip) #define CLK_OF_TABLES() OF_TABLE(CONFIG_COMMON_CLK, clk) +#define IOMMU_OF_TABLES() OF_TABLE(CONFIG_OF_IOMMU, iommu) #define RESERVEDMEM_OF_TABLES() OF_TABLE(CONFIG_OF_RESERVED_MEM, reservedmem) #define CPU_METHOD_OF_TABLES() OF_TABLE(CONFIG_SMP, cpu_method) #define EARLYCON_OF_TABLES() OF_TABLE(CONFIG_SERIAL_EARLYCON, earlycon) @@ -497,6 +498,7 @@ CLK_OF_TABLES() \ RESERVEDMEM_OF_TABLES() \ CLKSRC_OF_TABLES() \ + IOMMU_OF_TABLES() \ CPU_METHOD_OF_TABLES() \ KERNEL_DTB() \ IRQCHIP_OF_MATCH_TABLE() \ diff --git a/include/linux/iommu.h b/include/linux/iommu.h index e6a7c9f..7b83f9f 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -103,6 +103,7 @@ enum iommu_attr { * @domain_get_attr: Query domain attributes * @domain_set_attr: Change domain attributes * @pgsize_bitmap: bitmap of supported page sizes + * @priv: per-instance data private to the iommu driver */ struct iommu_ops { bool (*capable)(enum iommu_cap); @@ -133,6 +134,7 @@ struct iommu_ops { u32 (*domain_get_windows)(struct iommu_domain *domain); unsigned long pgsize_bitmap; + void *priv; }; #define IOMMU_GROUP_NOTIFY_ADD_DEVICE 1 /* Device added */ diff --git a/include/linux/of_iommu.h b/include/linux/of_iommu.h index 51a560f..5762cdc 100644 --- a/include/linux/of_iommu.h +++ b/include/linux/of_iommu.h @@ -1,12 +1,17 @@ #ifndef __OF_IOMMU_H #define __OF_IOMMU_H +#include +#include + #ifdef CONFIG_OF_IOMMU extern int of_get_dma_window(struct device_node *dn, const char *prefix, int index, unsigned long *busno, dma_addr_t *addr, size_t *size); +extern void of_iommu_init(void); + #else static inline int of_get_dma_window(struct device_node *dn, const char *prefix, @@ -16,6 +21,26 @@ static inline int of_get_dma_window(struct device_node *dn, const char *prefix, return -EINVAL; } +static inline void of_iommu_init(void) { } + #endif /* CONFIG_OF_IOMMU */ +static inline void of_iommu_set_ops(struct device_node *np, + const struct iommu_ops *ops) +{ + np->data = (struct iommu_ops *)ops; +} + +static inline struct iommu_ops *of_iommu_get_ops(struct device_node *np) +{ + return np->data; +} + +extern struct of_device_id __iommu_of_table; + +typedef int (*of_iommu_init_fn)(struct device_node *); + +#define IOMMU_OF_DECLARE(name, compat, fn) \ + _OF_DECLARE(iommu, name, compat, fn, of_iommu_init_fn) + #endif /* __OF_IOMMU_H */ -- cgit v1.1 From a3a60f81ee6f8fa65a57fa186b395bcd1f1bb097 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 15:49:10 +0100 Subject: dma-mapping: replace set_arch_dma_coherent_ops with arch_setup_dma_ops set_arch_dma_coherent_ops is called from of_dma_configure in order to swizzle the architectural dma-mapping functions over to a cache-coherent implementation. This is currently implemented only for ARM. In anticipation of re-using this mechanism for IOMMU-backed dma-mapping ops too, this patch replaces the function with a broader arch_setup_dma_ops callback which will be extended in future. Acked-by: Arnd Bergmann Acked-by: Marek Szyprowski Tested-by: Robin Murphy Signed-off-by: Will Deacon --- arch/arm/include/asm/dma-mapping.h | 8 ++++---- drivers/of/platform.c | 31 +++++++++++++------------------ include/linux/dma-mapping.h | 7 ++----- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h index 85738b2..dc3420e 100644 --- a/arch/arm/include/asm/dma-mapping.h +++ b/arch/arm/include/asm/dma-mapping.h @@ -121,12 +121,12 @@ static inline unsigned long dma_max_pfn(struct device *dev) } #define dma_max_pfn(dev) dma_max_pfn(dev) -static inline int set_arch_dma_coherent_ops(struct device *dev) +static inline void arch_setup_dma_ops(struct device *dev, bool coherent) { - set_dma_ops(dev, &arm_coherent_dma_ops); - return 0; + if (coherent) + set_dma_ops(dev, &arm_coherent_dma_ops); } -#define set_arch_dma_coherent_ops(dev) set_arch_dma_coherent_ops(dev) +#define arch_setup_dma_ops arch_setup_dma_ops static inline dma_addr_t phys_to_dma(struct device *dev, phys_addr_t paddr) { diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 3b64d0b..ff1f4e9 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -164,6 +164,8 @@ static void of_dma_configure(struct device *dev) { u64 dma_addr, paddr, size; int ret; + bool coherent; + unsigned long offset; /* * Set default dma-mask to 32 bit. Drivers are expected to setup @@ -178,28 +180,21 @@ static void of_dma_configure(struct device *dev) if (!dev->dma_mask) dev->dma_mask = &dev->coherent_dma_mask; - /* - * if dma-coherent property exist, call arch hook to setup - * dma coherent operations. - */ - if (of_dma_is_coherent(dev->of_node)) { - set_arch_dma_coherent_ops(dev); - dev_dbg(dev, "device is dma coherent\n"); - } - - /* - * if dma-ranges property doesn't exist - just return else - * setup the dma offset - */ ret = of_dma_get_range(dev->of_node, &dma_addr, &paddr, &size); if (ret < 0) { - dev_dbg(dev, "no dma range information to setup\n"); - return; + dma_addr = offset = 0; + size = dev->coherent_dma_mask; + } else { + offset = PFN_DOWN(paddr - dma_addr); + dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", dev->dma_pfn_offset); } + dev->dma_pfn_offset = offset; + + coherent = of_dma_is_coherent(dev->of_node); + dev_dbg(dev, "device is%sdma coherent\n", + coherent ? " " : " not "); - /* DMA ranges found. Calculate and set dma_pfn_offset */ - dev->dma_pfn_offset = PFN_DOWN(paddr - dma_addr); - dev_dbg(dev, "dma_pfn_offset(%#08lx)\n", dev->dma_pfn_offset); + arch_setup_dma_ops(dev, coherent); } /** diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index d5d3881..8a1560f 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -129,11 +129,8 @@ static inline int dma_coerce_mask_and_coherent(struct device *dev, u64 mask) extern u64 dma_get_required_mask(struct device *dev); -#ifndef set_arch_dma_coherent_ops -static inline int set_arch_dma_coherent_ops(struct device *dev) -{ - return 0; -} +#ifndef arch_setup_dma_ops +static inline void arch_setup_dma_ops(struct device *dev, bool coherent) { } #endif static inline unsigned int dma_get_max_seg_size(struct device *dev) -- cgit v1.1 From d0f60a44f5120a8e1c48995285c7d8d1e4915b35 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 16:15:59 +0100 Subject: iommu: add new iommu_ops callback for adding an OF device This patch adds a new function to the iommu_ops structure to allow an OF device to be added to a specific IOMMU instance using the recently merged generic devicetree binding for IOMMUs. The callback (of_xlate) takes a struct device representing the master and an of_phandle_args representing the IOMMU and the correspondong IDs for the new master. Acked-by: Arnd Bergmann Acked-by: Joerg Roedel Acked-by: Marek Szyprowski Tested-by: Robin Murphy Signed-off-by: Will Deacon --- include/linux/iommu.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 7b83f9f..415c761 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -102,6 +103,7 @@ enum iommu_attr { * @remove_device: remove device from iommu grouping * @domain_get_attr: Query domain attributes * @domain_set_attr: Change domain attributes + * @of_xlate: add OF master IDs to iommu grouping * @pgsize_bitmap: bitmap of supported page sizes * @priv: per-instance data private to the iommu driver */ @@ -133,6 +135,10 @@ struct iommu_ops { /* Get the numer of window per domain */ u32 (*domain_get_windows)(struct iommu_domain *domain); +#ifdef CONFIG_OF_IOMMU + int (*of_xlate)(struct device *dev, struct of_phandle_args *args); +#endif + unsigned long pgsize_bitmap; void *priv; }; -- cgit v1.1 From 7eba1d51485197fa77c43c42eae3ce04b4b1c1c0 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 16:20:32 +0100 Subject: iommu: provide helper function to configure an IOMMU for an of master The generic IOMMU device-tree bindings can be used to add arbitrary OF masters to an IOMMU with a compliant binding. This patch introduces of_iommu_configure, which does exactly that. Acked-by: Arnd Bergmann Acked-by: Joerg Roedel Acked-by: Marek Szyprowski Tested-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/iommu/Kconfig | 2 +- drivers/iommu/of_iommu.c | 33 +++++++++++++++++++++++++++++++++ include/linux/of_iommu.h | 6 ++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index dd51122..6d13f96 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -15,7 +15,7 @@ if IOMMU_SUPPORT config OF_IOMMU def_bool y - depends on OF + depends on OF && IOMMU_API config FSL_PAMU bool "Freescale IOMMU support" diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 89b9034..73236d3 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -93,6 +94,38 @@ int of_get_dma_window(struct device_node *dn, const char *prefix, int index, } EXPORT_SYMBOL_GPL(of_get_dma_window); +struct iommu_ops *of_iommu_configure(struct device *dev) +{ + struct of_phandle_args iommu_spec; + struct device_node *np; + struct iommu_ops *ops = NULL; + int idx = 0; + + /* + * We don't currently walk up the tree looking for a parent IOMMU. + * See the `Notes:' section of + * Documentation/devicetree/bindings/iommu/iommu.txt + */ + while (!of_parse_phandle_with_args(dev->of_node, "iommus", + "#iommu-cells", idx, + &iommu_spec)) { + np = iommu_spec.np; + ops = of_iommu_get_ops(np); + + if (!ops || !ops->of_xlate || ops->of_xlate(dev, &iommu_spec)) + goto err_put_node; + + of_node_put(np); + idx++; + } + + return ops; + +err_put_node: + of_node_put(np); + return NULL; +} + void __init of_iommu_init(void) { struct device_node *np; diff --git a/include/linux/of_iommu.h b/include/linux/of_iommu.h index 5762cdc..d03abbb 100644 --- a/include/linux/of_iommu.h +++ b/include/linux/of_iommu.h @@ -1,6 +1,7 @@ #ifndef __OF_IOMMU_H #define __OF_IOMMU_H +#include #include #include @@ -11,6 +12,7 @@ extern int of_get_dma_window(struct device_node *dn, const char *prefix, size_t *size); extern void of_iommu_init(void); +extern struct iommu_ops *of_iommu_configure(struct device *dev); #else @@ -22,6 +24,10 @@ static inline int of_get_dma_window(struct device_node *dn, const char *prefix, } static inline void of_iommu_init(void) { } +static inline struct iommu_ops *of_iommu_configure(struct device *dev) +{ + return NULL; +} #endif /* CONFIG_OF_IOMMU */ -- cgit v1.1 From 461bfb3fe7d0cf64d9ee2190ad8507e460fab198 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Wed, 19 Nov 2014 11:15:31 +0000 Subject: iommu: fix initialization without 'add_device' callback IOMMU drivers can be initialized from of_iommu helpers. Such drivers don't need to provide device_add callbacks to operate properly, so there is no need to fail initialization if the callback is missing. Acked-by: Arnd Bergmann Signed-off-by: Marek Szyprowski Signed-off-by: Will Deacon --- drivers/iommu/iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index ed8b048..02f798b 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -737,7 +737,7 @@ static int add_iommu_group(struct device *dev, void *data) const struct iommu_ops *ops = cb->ops; if (!ops->add_device) - return -ENODEV; + return 0; WARN_ON(dev->iommu_group); -- cgit v1.1 From 97890ba9289c66e23f2f2d431937693b6498d6f6 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 16:24:20 +0100 Subject: dma-mapping: detect and configure IOMMU in of_dma_configure This patch extends of_dma_configure so that it sets up the IOMMU for a device, as well as the coherent/non-coherent DMA mapping ops. Acked-by: Arnd Bergmann Acked-by: Marek Szyprowski Tested-by: Robin Murphy Signed-off-by: Will Deacon --- arch/arm/include/asm/dma-mapping.h | 4 +++- drivers/of/platform.c | 21 ++++++++++++++------- include/linux/dma-mapping.h | 8 +++++++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h index dc3420e..f3c0d95 100644 --- a/arch/arm/include/asm/dma-mapping.h +++ b/arch/arm/include/asm/dma-mapping.h @@ -121,7 +121,9 @@ static inline unsigned long dma_max_pfn(struct device *dev) } #define dma_max_pfn(dev) dma_max_pfn(dev) -static inline void arch_setup_dma_ops(struct device *dev, bool coherent) +static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, + u64 size, struct iommu_ops *iommu, + bool coherent) { if (coherent) set_dma_ops(dev, &arm_coherent_dma_ops); diff --git a/drivers/of/platform.c b/drivers/of/platform.c index ff1f4e9..b89caf8 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -166,6 +167,7 @@ static void of_dma_configure(struct device *dev) int ret; bool coherent; unsigned long offset; + struct iommu_ops *iommu; /* * Set default dma-mask to 32 bit. Drivers are expected to setup @@ -194,7 +196,16 @@ static void of_dma_configure(struct device *dev) dev_dbg(dev, "device is%sdma coherent\n", coherent ? " " : " not "); - arch_setup_dma_ops(dev, coherent); + iommu = of_iommu_configure(dev); + dev_dbg(dev, "device is%sbehind an iommu\n", + iommu ? " " : " not "); + + arch_setup_dma_ops(dev, dma_addr, size, iommu, coherent); +} + +static void of_dma_deconfigure(struct device *dev) +{ + arch_teardown_dma_ops(dev); } /** @@ -223,16 +234,12 @@ static struct platform_device *of_platform_device_create_pdata( if (!dev) goto err_clear_flag; - of_dma_configure(&dev->dev); dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; - - /* We do not fill the DMA ops for platform devices by default. - * This is currently the responsibility of the platform code - * to do such, possibly using a device notifier - */ + of_dma_configure(&dev->dev); if (of_device_add(dev) != 0) { + of_dma_deconfigure(&dev->dev); platform_device_put(dev); goto err_clear_flag; } diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index 8a1560f..c3007cb 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -130,7 +130,13 @@ static inline int dma_coerce_mask_and_coherent(struct device *dev, u64 mask) extern u64 dma_get_required_mask(struct device *dev); #ifndef arch_setup_dma_ops -static inline void arch_setup_dma_ops(struct device *dev, bool coherent) { } +static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, + u64 size, struct iommu_ops *iommu, + bool coherent) { } +#endif + +#ifndef arch_teardown_dma_ops +static inline void arch_teardown_dma_ops(struct device *dev) { } #endif static inline unsigned int dma_get_max_seg_size(struct device *dev) -- cgit v1.1 From af4dda732ea044517f8beed4a38c852ea97e7690 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 17:51:16 +0100 Subject: arm: call iommu_init before of_platform_populate We need to ensure that the IOMMUs in the system have a chance to perform some basic initialisation before we start adding masters to them. This patch adds a call to of_iommu_init before of_platform_populate. Acked-by: Russell King Acked-by: Arnd Bergmann Acked-by: Marek Szyprowski Signed-off-by: Will Deacon --- arch/arm/kernel/setup.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index c031063..1cfc94a 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -806,6 +807,7 @@ static int __init customize_machine(void) * machine from the device tree, if no callback is provided, * otherwise we would always need an init_machine callback. */ + of_iommu_init(); if (machine_desc->init_machine) machine_desc->init_machine(); #ifdef CONFIG_OF -- cgit v1.1 From 4bb25789ed28228a52c030bf28edb2fcdb214be8 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Wed, 27 Aug 2014 17:52:44 +0100 Subject: arm: dma-mapping: plumb our iommu mapping ops into arch_setup_dma_ops This patch plumbs the existing ARM IOMMU DMA infrastructure (which isn't actually called outside of a few drivers) into arch_setup_dma_ops, so that we can use IOMMUs for DMA transfers in a more generic fashion. Since this significantly complicates the arch_setup_dma_ops function, it is moved out of line into dma-mapping.c. If CONFIG_ARM_DMA_USE_IOMMU is not set, the iommu parameter is ignored and the normal ops are used instead. Acked-by: Russell King Acked-by: Arnd Bergmann Acked-by: Marek Szyprowski Signed-off-by: Will Deacon --- arch/arm/include/asm/dma-mapping.h | 12 +++--- arch/arm/mm/dma-mapping.c | 83 ++++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h index f3c0d95..9410b7e 100644 --- a/arch/arm/include/asm/dma-mapping.h +++ b/arch/arm/include/asm/dma-mapping.h @@ -121,14 +121,12 @@ static inline unsigned long dma_max_pfn(struct device *dev) } #define dma_max_pfn(dev) dma_max_pfn(dev) -static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, - u64 size, struct iommu_ops *iommu, - bool coherent) -{ - if (coherent) - set_dma_ops(dev, &arm_coherent_dma_ops); -} #define arch_setup_dma_ops arch_setup_dma_ops +extern void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size, + struct iommu_ops *iommu, bool coherent); + +#define arch_teardown_dma_ops arch_teardown_dma_ops +extern void arch_teardown_dma_ops(struct device *dev); static inline dma_addr_t phys_to_dma(struct device *dev, phys_addr_t paddr) { diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index e890711..09645f0 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -1947,9 +1947,8 @@ EXPORT_SYMBOL_GPL(arm_iommu_release_mapping); * arm_iommu_create_mapping) * * Attaches specified io address space mapping to the provided device, - * this replaces the dma operations (dma_map_ops pointer) with the - * IOMMU aware version. More than one client might be attached to - * the same io address space mapping. + * More than one client might be attached to the same io address space + * mapping. */ int arm_iommu_attach_device(struct device *dev, struct dma_iommu_mapping *mapping) @@ -1962,7 +1961,6 @@ int arm_iommu_attach_device(struct device *dev, kref_get(&mapping->kref); dev->archdata.mapping = mapping; - set_dma_ops(dev, &iommu_ops); pr_debug("Attached IOMMU controller to %s device.\n", dev_name(dev)); return 0; @@ -1974,7 +1972,6 @@ EXPORT_SYMBOL_GPL(arm_iommu_attach_device); * @dev: valid struct device pointer * * Detaches the provided device from a previously attached map. - * This voids the dma operations (dma_map_ops pointer) */ void arm_iommu_detach_device(struct device *dev) { @@ -1989,10 +1986,82 @@ void arm_iommu_detach_device(struct device *dev) iommu_detach_device(mapping->domain, dev); kref_put(&mapping->kref, release_iommu_mapping); dev->archdata.mapping = NULL; - set_dma_ops(dev, NULL); pr_debug("Detached IOMMU controller from %s device.\n", dev_name(dev)); } EXPORT_SYMBOL_GPL(arm_iommu_detach_device); -#endif +static struct dma_map_ops *arm_get_iommu_dma_map_ops(bool coherent) +{ + return coherent ? &iommu_coherent_ops : &iommu_ops; +} + +static bool arm_setup_iommu_dma_ops(struct device *dev, u64 dma_base, u64 size, + struct iommu_ops *iommu) +{ + struct dma_iommu_mapping *mapping; + + if (!iommu) + return false; + + mapping = arm_iommu_create_mapping(dev->bus, dma_base, size); + if (IS_ERR(mapping)) { + pr_warn("Failed to create %llu-byte IOMMU mapping for device %s\n", + size, dev_name(dev)); + return false; + } + + if (arm_iommu_attach_device(dev, mapping)) { + pr_warn("Failed to attached device %s to IOMMU_mapping\n", + dev_name(dev)); + arm_iommu_release_mapping(mapping); + return false; + } + + return true; +} + +static void arm_teardown_iommu_dma_ops(struct device *dev) +{ + struct dma_iommu_mapping *mapping = dev->archdata.mapping; + + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(mapping); +} + +#else + +static bool arm_setup_iommu_dma_ops(struct device *dev, u64 dma_base, u64 size, + struct iommu_ops *iommu) +{ + return false; +} + +static void arm_teardown_iommu_dma_ops(struct device *dev) { } + +#define arm_get_iommu_dma_map_ops arm_get_dma_map_ops + +#endif /* CONFIG_ARM_DMA_USE_IOMMU */ + +static struct dma_map_ops *arm_get_dma_map_ops(bool coherent) +{ + return coherent ? &arm_coherent_dma_ops : &arm_dma_ops; +} + +void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size, + struct iommu_ops *iommu, bool coherent) +{ + struct dma_map_ops *dma_ops; + + if (arm_setup_iommu_dma_ops(dev, dma_base, size, iommu)) + dma_ops = arm_get_iommu_dma_map_ops(coherent); + else + dma_ops = arm_get_dma_map_ops(coherent); + + set_dma_ops(dev, dma_ops); +} + +void arch_teardown_dma_ops(struct device *dev) +{ + arm_teardown_iommu_dma_ops(dev); +} -- cgit v1.1 From a42a7a1fb5f1f9004b023594609dc22da02fc08b Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Fri, 5 Dec 2014 13:41:02 +0000 Subject: iommu: store DT-probed IOMMU data privately Since the data pointer in the DT node is public and may be overwritten by conflicting code, move the DT-probed IOMMU ops to a private list where they will be safe. Acked-by: Grant Likely Signed-off-by: Robin Murphy [will: added missing #include and missing ')'] Signed-off-by: Will Deacon --- drivers/iommu/of_iommu.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/of_iommu.h | 12 ++---------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 73236d3..af1dc6a 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -22,6 +22,7 @@ #include #include #include +#include static const struct of_device_id __iommu_of_table_sentinel __used __section(__iommu_of_table_end); @@ -94,6 +95,44 @@ int of_get_dma_window(struct device_node *dn, const char *prefix, int index, } EXPORT_SYMBOL_GPL(of_get_dma_window); +struct of_iommu_node { + struct list_head list; + struct device_node *np; + struct iommu_ops *ops; +}; +static LIST_HEAD(of_iommu_list); +static DEFINE_SPINLOCK(of_iommu_lock); + +void of_iommu_set_ops(struct device_node *np, struct iommu_ops *ops) +{ + struct of_iommu_node *iommu = kzalloc(sizeof(*iommu), GFP_KERNEL); + + if (WARN_ON(!iommu)) + return; + + INIT_LIST_HEAD(&iommu->list); + iommu->np = np; + iommu->ops = ops; + spin_lock(&of_iommu_lock); + list_add_tail(&iommu->list, &of_iommu_list); + spin_unlock(&of_iommu_lock); +} + +struct iommu_ops *of_iommu_get_ops(struct device_node *np) +{ + struct of_iommu_node *node; + struct iommu_ops *ops = NULL; + + spin_lock(&of_iommu_lock); + list_for_each_entry(node, &of_iommu_list, list) + if (node->np == np) { + ops = node->ops; + break; + } + spin_unlock(&of_iommu_lock); + return ops; +} + struct iommu_ops *of_iommu_configure(struct device *dev) { struct of_phandle_args iommu_spec; diff --git a/include/linux/of_iommu.h b/include/linux/of_iommu.h index d03abbb..16c7554 100644 --- a/include/linux/of_iommu.h +++ b/include/linux/of_iommu.h @@ -31,16 +31,8 @@ static inline struct iommu_ops *of_iommu_configure(struct device *dev) #endif /* CONFIG_OF_IOMMU */ -static inline void of_iommu_set_ops(struct device_node *np, - const struct iommu_ops *ops) -{ - np->data = (struct iommu_ops *)ops; -} - -static inline struct iommu_ops *of_iommu_get_ops(struct device_node *np) -{ - return np->data; -} +void of_iommu_set_ops(struct device_node *np, struct iommu_ops *ops); +struct iommu_ops *of_iommu_get_ops(struct device_node *np); extern struct of_device_id __iommu_of_table; -- cgit v1.1