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 --- include/asm-generic/vmlinux.lds.h | 2 ++ include/linux/iommu.h | 2 ++ include/linux/of_iommu.h | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+) (limited to 'include') diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index aa70cbda327c..bee5d683074d 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 e6a7c9ff72f2..7b83f9f8e11d 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 51a560f34bca..5762cdc8effe 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.2.3 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(-) (limited to 'include') diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h index 85738b200023..dc3420e77758 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 3b64d0bf5bba..ff1f4e9afccb 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 d5d388160f42..8a1560f95d4a 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.2.3 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(+) (limited to 'include') diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 7b83f9f8e11d..415c7613d02c 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.2.3 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(-) (limited to 'include') diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index dd5112265cc9..6d13f962f156 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 89b903406968..73236d3cc955 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 5762cdc8effe..d03abbb11c34 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.2.3 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(-) (limited to 'include') diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h index dc3420e77758..f3c0d953f6a2 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 ff1f4e9afccb..b89caf8c7586 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 8a1560f95d4a..c3007cb4bfa6 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.2.3 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(-) (limited to 'include') diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 73236d3cc955..af1dc6a1c0a1 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 d03abbb11c34..16c75547d725 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.2.3