Generic DT bindings for PCI and ARM SMMU
Subsystems (7)
Robin Murphy
reviewercover Generic DT bindings for PCI and ARM SMMU
Hi all,
Here's an initial dump of what I've been doing for full generic binding support on SMMUv2. I'm posting it as one big series because it all fits together, but the OF and arm64 patches should be free to go through their respective trees without horribly breaking anything (I think...)
Currently this just brings platform and PCI generic binding support to the level of the old binding, and adds proper group creation - I have more ongoing work on top of this to remove the stream ID limits and start filling in SMR support, which will probably show up added/folded in to subsequent revisions.
I've put a branch together (based on the SMMU changes in -next) here:
git://linux-arm.org/linux-rm iommu/generic
Tested on Juno (MMU-401) and LS2085 (MMU-500) for both PCI and platform devices, plus various combinations of stream ID aliasing between the two. Since we don't yet have a robust solution for dealing with MSI doorbells downstream of translation, there are some temporary hacks that I've been running with on the iommu/msi branch.
Robin.
---
Mark Rutland (1):
Docs: dt: add PCI IOMMU map bindings
Robin Murphy (11):
of/irq: Break out msi-map lookup (again) iommu/of: Handle iommu-map property for PCI arm64/dma-mapping: Extend DMA ops workaround to PCI devices arm64/dma-mapping: Remove default domain workaround iommu/arm-smmu: Streamline SMMU data lookup iommu/arm-smmu: Factor out mmu-masters handling iommu/arm-smmu: Refactor master/group config handling iommu/arm-smmu: Simplify mmu-masters handling
Docs: dt: document ARM SMMU generic binding usage iommu/arm-smmu: Generic IOMMU DT bindings support iommu/arm-smmu: Group platform devices appropriately
.../devicetree/bindings/iommu/arm,smmu.txt | 40 +- .../devicetree/bindings/pci/pci-iommu.txt | 171 +++++ arch/arm64/mm/dma-mapping.c | 56 +- drivers/iommu/arm-smmu.c | 733 ++++++++++++--------- drivers/iommu/of_iommu.c | 43 +- drivers/of/irq.c | 70 +- drivers/of/of_pci.c | 102 +++ include/linux/of_pci.h | 8 + 8 files changed, 765 insertions(+), 458 deletions(-) create mode 100644 Documentation/devicetree/bindings/pci/pci-iommu.txt
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 1/12 Docs: dt: add PCI IOMMU map bindings
From: Mark Rutland <[email protected]>
The existing IOMMU bindings are able to specify the relationship between masters and IOMMUs, but they are insufficient for describing the general case of hotpluggable busses such as PCI where the set of masters is not known until runtime, and the relationship between masters and IOMMUs is a property of the integration of the system.
This patch adds a generic binding for mapping PCI devices to IOMMUs, using a new iommu-map property (specific to PCI*) which may be used to map devices (identified by their Requester ID) to sideband data for the IOMMU which they master through.
--- .../devicetree/bindings/pci/pci-iommu.txt | 171 +++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/pci-iommu.txt
Documentation/devicetree/bindings/pci/pci-iommu.txt new file +171 -0
@@ -0,0 +1,171 @@ +This document describes the generic device tree binding for describing the +relationship between PCI(e) devices and IOMMU(s). + +Each PCI(e) device under a root complex is uniquely identified by its Requester +ID (AKA RID). A Requester ID is a triplet of a Bus number, Device number, and +Function number. + +For the purpose of this document, when treated as a numeric value, a RID is +formatted such that: + +* Bits [15:8] are the Bus number. +* Bits [7:3] are the Device number. +* Bits [2:0] are the Function number. +* Any other bits required for padding must be zero. + +IOMMUs may distinguish PCI devices through sideband data derived from the +Requester ID. While a given PCI device can only master through one IOMMU, a +root complex may split masters across a set of IOMMUs (e.g. with one IOMMU per +bus). + +The generic 'iommus' property is insufficient to describe this relationship, +and a mechanism is required to map from a PCI device to its IOMMU and sideband +data. + +For generic IOMMU bindings, see +Documentation/devicetree/bindings/iommu/iommu.txt. + + +PCI root complex +================ + +Optional properties +------------------- + +- iommu-map: Maps a Requester ID to an IOMMU and associated iommu-specifier + data. + + The property is an arbitrary number of tuples of + (rid-base,iommu,iommu-base,length). + + Any RID r in the interval [rid-base, rid-base + length) is associated with + the listed IOMMU, with the iommu-specifier (r - rid-base + iommu-base). + +- iommu-map-mask: A mask to be applied to each Requester ID prior to being + mapped to an iommu-specifier per the iommu-map property. + + +Example (1) +=========== + +/ { + #address-cells = <1>; + #size-cells = <1>; + + iommu: iommu at a { + reg = <0xa 0x1>; + compatible = "vendor,some-iommu"; + #iommu-cells = <1>; + }; + + pci: pci at f { + reg = <0xf 0x1>; + compatible = "vendor,pcie-root-complex"; + device_type = "pci"; + + /* + * The sideband data provided to the IOMMU is the RID, + * identity-mapped. + */ + iommu-map = <0x0 &iommu 0x0 0x10000>; + }; +}; + + +Example (2) +=========== + +/ { + #address-cells = <1>; + #size-cells = <1>; + + iommu: iommu at a { + reg = <0xa 0x1>; + compatible = "vendor,some-iommu"; + #iommu-cells = <1>; + }; + + pci: pci at f { + reg = <0xf 0x1>; + compatible = "vendor,pcie-root-complex"; + device_type = "pci"; + + /* + * The sideband data provided to the IOMMU is the RID with the + * function bits masked out. + */ + iommu-map = <0x0 &iommu 0x0 0x10000>; + iommu-map-mask = <0xfff8>; + }; +}; + + +Example (3) +=========== + +/ { + #address-cells = <1>; + #size-cells = <1>; + + iommu: iommu at a { + reg = <0xa 0x1>; + compatible = "vendor,some-iommu"; + #iommu-cells = <1>; + }; + + pci: pci at f { + reg = <0xf 0x1>; + compatible = "vendor,pcie-root-complex"; + device_type = "pci"; + + /* + * The sideband data provided to the IOMMU is the RID, + * but the high bits of the bus number are flipped. + */ + iommu-map = <0x0000 &iommu 0x8000 0x8000>, + <0x8000 &iommu 0x0000 0x8000>; + }; +}; + + +Example (4) +=========== + +/ { + #address-cells = <1>; + #size-cells = <1>; + + iommu_a: iommu at a { + reg = <0xa 0x1>; + compatible = "vendor,some-iommu"; + #iommu-cells = <1>; + }; + + iommu_b: iommu at b { + reg = <0xb 0x1>; + compatible = "vendor,some-iommu"; + #iommu-cells = <1>; + }; + + iommu_c: iommu at c { + reg = <0xc 0x1>; + compatible = "vendor,some-iommu"; + #iommu-cells = <1>; + }; + + pci: pci at f { + reg = <0xf 0x1>; + compatible = "vendor,pcie-root-complex"; + device_type = "pci"; + + /* + * Devices with bus number 0-127 are mastered via IOMMU + * a, with sideband data being RID[14:0]. + * Devices with bus number 128-255 are mastered via + * IOMMU b, with sideband data being RID[14:0]. + * No devices master via IOMMU c. + */ + iommu-map = <0x0000 &iommu_a 0x0000 0x8000>, + <0x8000 &iommu_b 0x0000 0x8000>; + }; +};
Signature · 1 line
2.7.2.333.g70bd996.dirty
Rob Herring
maintainerPATCH 1/12 Docs: dt: add PCI IOMMU map bindings
Quoted 13 lines · Robin Murphy
From: Mark Rutland <[email protected]>
The existing IOMMU bindings are able to specify the relationship between masters and IOMMUs, but they are insufficient for describing the general case of hotpluggable busses such as PCI where the set of masters is not known until runtime, and the relationship between masters and IOMMUs is a property of the integration of the system.
This patch adds a generic binding for mapping PCI devices to IOMMUs, using a new iommu-map property (specific to PCI*) which may be used to map devices (identified by their Requester ID) to sideband data for the IOMMU which they master through.
Quoted 5 lines
Signed-off-by Mark Rutland <[email protected]>--- .../devicetree/bindings/pci/pci-iommu.txt | 171 +++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/pci-iommu.txt
Robin Murphy
reviewerPATCH 2/12 of/irq: Break out msi-map lookup (again)
In reply to Robin MurphyThe PCI msi-map code is already doing double-duty translating IDs and retrieving MSI parents, which unsurprisingly is the same functionality we need for the identically-formatted PCI iommu-map property. Drag the core parsing routine up yet another layer into the general OF-PCI code, and further generalise it for either kind of lookup in either flavour of map property.
--- drivers/of/irq.c | 70 ++------------------------------- drivers/of/of_pci.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/of_pci.h | 8 ++++ 3 files changed, 114 insertions(+), 66 deletions(-)
drivers/of/irq.c +4 -66
@@ -24,6 +24,7 @@#include <linux/module.h> #include <linux/of.h> #include <linux/of_irq.h> +#include <linux/of_pci.h> #include <linux/string.h> #include <linux/slab.h>
@@ -586,13 +587,7 @@ static u32 __of_msi_map_rid(struct device *dev, struct device_node **np, u32 rid_in) {struct device *parent_dev; -struct device_node *msi_controller_node; -struct device_node *msi_np = *np; -u32 map_mask, masked_rid, rid_base, msi_base, rid_len, phandle; -int msi_map_len; -bool matched;u32 rid_out = rid_in; -const __be32 *msi_map =NULL ;/* * Walk up the device parent links looking for one with a
@@ -602,71 +597,14 @@ static u32 __of_msi_map_rid(struct device *dev, struct device_node **np,if (!parent_dev->of_node)continue ; - msi_map =of_get_property (parent_dev->of_node, -"msi-map" , &msi_map_len); -if (!msi_map) +if (!of_property_read_bool (parent_dev->of_node,"msi-map" ))continue ; -if (msi_map_len % (4 *sizeof (__be32))) { -dev_err (parent_dev,"Error: Bad msi-map length: %d\n" , - msi_map_len); -return rid_out; - }/* We have a good parent_dev and msi_map, let's use them. */ +of_pci_map_rid (parent_dev->of_node,"msi-map" , rid_in, np, + &rid_out);break ; } -if (!msi_map) -return rid_out; - -/* The default is to select all bits. */ - map_mask =0xffffffff ; - -/* -* Can be overridden by "msi-map-mask" property. If -* of_property_read_u32() fails, the default is used. -*/ -of_property_read_u32 (parent_dev->of_node,"msi-map-mask" , &map_mask); - - masked_rid = map_mask & rid_in; - matched = false; -while (!matched && msi_map_len >=4 *sizeof (__be32)) { - rid_base =be32_to_cpup (msi_map +0 ); - phandle =be32_to_cpup (msi_map +1 ); - msi_base =be32_to_cpup (msi_map +2 ); - rid_len =be32_to_cpup (msi_map +3 ); - -if (rid_base & ~map_mask) { -dev_err (parent_dev, -"Invalid msi-map translation - msi-map-mask (0x%x) ignores rid-base (0x%x)\n" , - map_mask, rid_base); -return rid_out; - } - - msi_controller_node =of_find_node_by_phandle (phandle); - - matched = (masked_rid >= rid_base && - masked_rid < rid_base + rid_len); -if (msi_np) - matched &= msi_np == msi_controller_node; - -if (matched && !msi_np) { - *np = msi_np = msi_controller_node; -break ; - } - -of_node_put (msi_controller_node); - msi_map_len -=4 *sizeof (__be32); - msi_map +=4 ; - } -if (!matched) -return rid_out; - - rid_out = masked_rid - rid_base + msi_base; -dev_dbg (dev, -"msi-map at: %s, using mask %08x, rid-base: %08x, msi-base: %08x, length: %08x, rid: %08x -> %08x\n" , -dev_name (parent_dev), map_mask, rid_base, msi_base, - rid_len, rid_in, rid_out); -return rid_out; }
drivers/of/of_pci.c +102 -0
105 lines not on this page · raw message
include/linux/of_pci.h +8 -0
@@ -17,6 +17,8 @@ int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin);int of_pci_parse_bus_range (struct device_node *node,struct resource *res);int of_get_pci_domain_nr (struct device_node *node);void of_pci_check_probe_only (void ); +int of_pci_map_rid (struct device_node *np,const char *map_name,u32 rid_in, +struct device_node **target,u32 *rid_out);#else static inline int of_irq_parse_pci (const struct pci_dev *pdev,struct of_phandle_args *out_irq) {
@@ -52,6 +54,12 @@ of_get_pci_domain_nr(struct device_node *node) return -1; } +static inline int of_pci_map_rid (struct device_node *np,const char *map_name, +u32 rid_in,struct device_node **target,u32 *rid_out) +{ +return -EINVAL ; +} +static inline void of_pci_check_probe_only (void ) { }#endif
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 3/12 iommu/of: Handle iommu-map property for PCI
In reply to Robin MurphyNow that we have a way to pick up the RID translation and target IOMMU, hook up of_iommu_configure() to bring PCI devices into the of_xlate mechanism and allow them IOMMU-backed DMA ops without the need for driver-specific handling.
--- drivers/iommu/of_iommu.c | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-)
drivers/iommu/of_iommu.c +36 -7
@@ -22,6 +22,7 @@#include <linux/limits.h> #include <linux/of.h> #include <linux/of_iommu.h> +#include <linux/of_pci.h> #include <linux/slab.h> static const struct of_device_id __iommu_of_table_sentinel
@@ -134,20 +135,48 @@ struct iommu_ops *of_iommu_get_ops(struct device_node *np)return ops; } +static int __get_pci_rid (struct pci_dev *pdev,u16 alias,void *data) +{ +struct of_phandle_args *iommu_spec = data; +struct device_node *np = pdev->bus->dev.of_node; + + iommu_spec->args[0 ] = alias; +return np == iommu_spec->np; +} +struct iommu_ops *of_iommu_configure (struct device *dev,struct device_node *master_np) {struct of_phandle_args iommu_spec; -struct device_node *np; +struct device_node *np =NULL ;struct iommu_ops *ops =NULL ;int idx =0 ; -/* -* We can't do much for PCI devices without knowing how -* device IDs are wired up from the PCI bus to the IOMMU. -*/ -if (dev_is_pci (dev)) -return NULL ; +if (dev_is_pci (dev)) { +/* +* Start by tracing the RID alias down the PCI topology as +* far as the host bridge whose OF node we have... +*/ + iommu_spec.np = master_np; +pci_for_each_dma_alias (to_pci_dev (dev), __get_pci_rid, + &iommu_spec); +/* +* ...then find out what that becomes once it escapes the PCI +* bus into the system beyond, and which IOMMU it ends up at. +*/ +if (of_pci_map_rid (master_np,"iommu-map" , iommu_spec.args[0 ], + &np, iommu_spec.args)) +return NULL ; + + iommu_spec.np = np; + iommu_spec.args_count =1 ; + 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); +return ops; + }/* * We don't currently walk up the tree looking for a parent IOMMU.
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 4/12 arm64/dma-mapping: Extend DMA ops workaround to PCI devices
In reply to Robin MurphyPCI devices now suffer the same hiccup as platform devices, in that they get their DMA ops configured before they have been added to their bus, and thus before we know whether they have successfully registered with an IOMMU or not. Until the necessary driver core changes to reorder calls during device creation have been worked out, extend our delayed notifier trick onto the PCI bus so as to avoid broken DMA ops for IOMMUs there.
--- arch/arm64/mm/dma-mapping.c | 4 ++++ 1 file changed, 4 insertions(+)
arch/arm64/mm/dma-mapping.c +4 -0
@@ -933,6 +933,10 @@ static int __init __iommu_dma_init(void) ret =register_iommu_dma_ops_notifier (&platform_bus_type);if (!ret) ret =register_iommu_dma_ops_notifier (&amba_bustype); +#ifdef CONFIG_PCI +if (!ret) + ret =register_iommu_dma_ops_notifier (&pci_bus_type); +#endif /* handle devices queued before this arch_initcall */ if (!ret)
Signature · 1 line
2.7.2.333.g70bd996.dirty
Catalin Marinas
maintainerPATCH 4/12 arm64/dma-mapping: Extend DMA ops workaround to PCI devices
Quoted 11 lines · Robin Murphy
PCI devices now suffer the same hiccup as platform devices, in that they get their DMA ops configured before they have been added to their bus, and thus before we know whether they have successfully registered with an IOMMU or not. Until the necessary driver core changes to reorder calls during device creation have been worked out, extend our delayed notifier trick onto the PCI bus so as to avoid broken DMA ops for IOMMUs there.
CC Catalin Marinas <[email protected]>CC Will Deacon <[email protected]>Signed-off-by Robin Murphy <[email protected]>
Signature · 1 line
Catalin
Robin Murphy
reviewerPATCH 5/12 arm64/dma-mapping: Remove default domain workaround
In reply to Robin MurphyWith the IOMMU core now taking care of default domains for groups regardless of bus type, we can gleefully rip out this stop-gap, as slight recompense for having to expand the other one.
--- arch/arm64/mm/dma-mapping.c | 52 +++++++-------------------------------------- 1 file changed, 8 insertions(+), 44 deletions(-)
arch/arm64/mm/dma-mapping.c +8 -44
@@ -804,57 +804,24 @@ struct iommu_dma_notifier_data {static LIST_HEAD (iommu_dma_masters);static DEFINE_MUTEX (iommu_dma_notifier_lock); -/* -* Temporarily "borrow" a domain feature flag to to tell if we had to resort -* to creating our own domain here, in case we need to clean it up again. -*/ -#define __IOMMU_DOMAIN_FAKE_DEFAULT (1U << 31) -static bool do_iommu_attach (struct device *dev,const struct iommu_ops *ops,u64 dma_base,u64 size) {struct iommu_domain *domain =iommu_get_domain_for_dev (dev);/* -* Best case: The device is either part of a group which was -* already attached to a domain in a previous call, or it's -* been put in a default DMA domain by the IOMMU core. +* If the IOMMU driver has the DMA domain support that we require, +* then the IOMMU core will have already configured a group for this +* device, and allocated the default domain for that group. */ -if (!domain) { -/* -* Urgh. The IOMMU core isn't going to do default domains -* for non-PCI devices anyway, until it has some means of -* abstracting the entirely implementation-specific -* sideband data/SoC topology/unicorn dust that may or -* may not differentiate upstream masters. -* So until then, HORRIBLE HACKS! -*/ - domain = ops->domain_alloc (IOMMU_DOMAIN_DMA ); -if (!domain) - goto out_no_domain; - - domain->ops = ops; - domain->type =IOMMU_DOMAIN_DMA | __IOMMU_DOMAIN_FAKE_DEFAULT; - -if (iommu_attach_device (domain, dev)) - goto out_put_domain; +if (!domain ||iommu_dma_init_domain (domain, dma_base, size)) { +pr_warn ("Failed to set up IOMMU for device %s; retaining platform DMA ops\n" , +dev_name (dev)); +return false; } -if (iommu_dma_init_domain (domain, dma_base, size)) - goto out_detach; - dev->archdata.dma_ops = &iommu_dma_ops;return true; - -out_detach: -iommu_detach_device (domain, dev); -out_put_domain: -if (domain->type & __IOMMU_DOMAIN_FAKE_DEFAULT) -iommu_domain_free (domain); -out_no_domain: -pr_warn ("Failed to set up IOMMU for device %s; retaining platform DMA ops\n" , -dev_name (dev)); -return false; } static void queue_iommu_attach(struct device *dev, const struct iommu_ops *ops,
@@ -971,11 +938,8 @@ void arch_teardown_dma_ops(struct device *dev) {struct iommu_domain *domain =iommu_get_domain_for_dev (dev); -if (domain) { +if (WARN_ON (domain))iommu_detach_device (domain, dev); -if (domain->type & __IOMMU_DOMAIN_FAKE_DEFAULT) -iommu_domain_free (domain); - } dev->archdata.dma_ops =NULL ; }
Signature · 1 line
2.7.2.333.g70bd996.dirty
Catalin Marinas
maintainerPATCH 5/12 arm64/dma-mapping: Remove default domain workaround
Quoted 7 lines · Robin Murphy
With the IOMMU core now taking care of default domains for groups regardless of bus type, we can gleefully rip out this stop-gap, as slight recompense for having to expand the other one.
CC Catalin Marinas <[email protected]>CC Will Deacon <[email protected]>Signed-off-by Robin Murphy <[email protected]>
Signature · 1 line
Catalin
Robin Murphy
reviewerPATCH 6/12 iommu/arm-smmu: Streamline SMMU data lookup
In reply to Robin MurphySimplify things by stashing our SMMU instance data in drvdata, so that we don't need to go off hunting for it in the driver model callbacks. Watch out, arm_smmu_devices list, your days are numbered!
--- drivers/iommu/arm-smmu.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-)
drivers/iommu/arm-smmu.c +7 -13
@@ -1836,6 +1836,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) } } +platform_set_drvdata (pdev, smmu);INIT_LIST_HEAD (&smmu->list);spin_lock (&arm_smmu_devices_lock);list_add (&smmu->list, &arm_smmu_devices);
@@ -1860,24 +1861,17 @@ out_put_masters:static int arm_smmu_device_remove (struct platform_device *pdev) { +struct arm_smmu_device *smmu =platform_get_drvdata (pdev);int i; -struct device *dev = &pdev->dev; -struct arm_smmu_device *curr, *smmu =NULL ;struct rb_node *node; -spin_lock (&arm_smmu_devices_lock); -list_for_each_entry (curr, &arm_smmu_devices, list) { -if (curr->dev == dev) { - smmu = curr; -list_del (&smmu->list); -break ; - } - } -spin_unlock (&arm_smmu_devices_lock); -if (!smmu)return -ENODEV ; +spin_lock (&arm_smmu_devices_lock); +list_del (&smmu->list); +spin_unlock (&arm_smmu_devices_lock); +for (node =rb_first (&smmu->masters); node; node =rb_next (node)) {struct arm_smmu_master *master = container_of(node, struct arm_smmu_master, node);
@@ -1885,7 +1879,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev) }if (!bitmap_empty (smmu->context_map,ARM_SMMU_MAX_CBS )) -dev_err (dev,"removing device with active domains!\n" ); +dev_err (&pdev->dev,"removing device with active domains!\n" );for (i =0 ; i < smmu->num_global_irqs; ++i)free_irq (smmu->irqs[i], smmu);
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 7/12 iommu/arm-smmu: Factor out mmu-masters handling
In reply to Robin MurphyReorder the SMMU probe routine slightly to separate probing and configuring the SMMU itself from processing the mmu-masters property, and break the handling of the latter out into self-contained functions.
In preparation for changes to come, the failure mode is changed slightly such that instead of a malformed mmu-masters property aborting the whole SMMU device probe, we instead roll back any association with those master devices and let the SMMU itself continue without them.
--- drivers/iommu/arm-smmu.c | 88 ++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 40 deletions(-)
drivers/iommu/arm-smmu.c +48 -40
@@ -465,7 +465,6 @@ static int insert_smmu_master(struct arm_smmu_device *smmu, }static int register_smmu_master (struct arm_smmu_device *smmu, -struct device *dev,struct of_phandle_args *masterspec) {int i;
@@ -473,20 +472,20 @@ static int register_smmu_master(struct arm_smmu_device *smmu, master =find_smmu_master (smmu, masterspec->np);if (master) { -dev_err (dev, +dev_err (smmu->dev,"rejecting multiple registrations for master device %s\n" , masterspec->np->name);return -EBUSY ; }if (masterspec->args_count >MAX_MASTER_STREAMIDS ) { -dev_err (dev, +dev_err (smmu->dev,"reached maximum number (%d) of stream IDs for master device %s\n" ,MAX_MASTER_STREAMIDS , masterspec->np->name);return -ENOSPC ; } - master =devm_kzalloc (dev,sizeof (*master),GFP_KERNEL ); + master =devm_kzalloc (smmu->dev,sizeof (*master),GFP_KERNEL );if (!master)return -ENOMEM ;
@@ -498,7 +497,7 @@ static int register_smmu_master(struct arm_smmu_device *smmu,if (!(smmu->features &ARM_SMMU_FEAT_STREAM_MATCH ) && (streamid >= smmu->num_mapping_groups)) { -dev_err (dev, +dev_err (smmu->dev,"stream ID for master device %s greater than maximum allowed (%d)\n" , masterspec->np->name, smmu->num_mapping_groups);return -ERANGE ;
@@ -1730,14 +1729,53 @@ static const struct of_device_id arm_smmu_of_match[] = { };MODULE_DEVICE_TABLE (of, arm_smmu_of_match); +static void arm_smmu_remove_mmu_masters (struct arm_smmu_device *smmu) +{ +struct arm_smmu_master *master; +struct rb_node *node, *tmp; + + node =rb_first (&smmu->masters); +while (node) { + tmp = node; + node =rb_next (node); + master = container_of(tmp, struct arm_smmu_master, node); + +rb_erase (tmp, &smmu->masters); +of_node_put (master->of_node); +devm_kfree (smmu->dev, master); + } +} + +static int arm_smmu_probe_mmu_masters (struct arm_smmu_device *smmu) +{ +struct of_phandle_args masterspec; +int err, i =0 ; + +while (!of_parse_phandle_with_args (smmu->dev->of_node,"mmu-masters" , +"#stream-id-cells" , i, + &masterspec)) { + err =register_smmu_master (smmu, &masterspec); +if (err) +break ; + i++; + } + +if (err) { +dev_err (smmu->dev,"failed to register mmu-masters\n" ); +arm_smmu_remove_mmu_masters (smmu); + }else if (i) { +dev_info (smmu->dev,"registered %d mmu-masters\n" , i); + } + +return err; +} +static int arm_smmu_device_dt_probe (struct platform_device *pdev) {const struct of_device_id *of_id;struct resource *res;struct arm_smmu_device *smmu;struct device *dev = &pdev->dev; -struct rb_node *node; -struct of_phandle_args masterspec;int num_irqs, i, err; smmu =devm_kzalloc (dev,sizeof (*smmu),GFP_KERNEL );
@@ -1796,22 +1834,6 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)if (err)return err; - i =0 ; - smmu->masters =RB_ROOT ; -while (!of_parse_phandle_with_args (dev->of_node,"mmu-masters" , -"#stream-id-cells" , i, - &masterspec)) { - err =register_smmu_master (smmu, dev, &masterspec); -if (err) { -dev_err (dev,"failed to add master %s\n" , - masterspec.np->name); - goto out_put_masters; - } - - i++; - } -dev_notice (dev,"registered %d master devices\n" , i); -parse_driver_options (smmu); if (smmu->version > ARM_SMMU_V1 &&
@@ -1819,8 +1841,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)dev_err (dev,"found only %d context interrupt(s) but %d required\n" , smmu->num_context_irqs, smmu->num_context_banks); - err = -ENODEV ; - goto out_put_masters; +return -ENODEV ; }for (i =0 ; i < smmu->num_global_irqs; ++i) {
@@ -1837,6 +1858,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) }platform_set_drvdata (pdev, smmu); +arm_smmu_probe_mmu_masters (smmu);INIT_LIST_HEAD (&smmu->list);spin_lock (&arm_smmu_devices_lock);list_add (&smmu->list, &arm_smmu_devices);
@@ -1848,14 +1870,6 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) out_free_irqs:while (i--)free_irq (smmu->irqs[i], smmu); - -out_put_masters: -for (node =rb_first (&smmu->masters); node; node =rb_next (node)) { -struct arm_smmu_master *master - = container_of(node, struct arm_smmu_master, node); -of_node_put (master->of_node); - } -return err; }
@@ -1863,21 +1877,15 @@ static int arm_smmu_device_remove(struct platform_device *pdev) {struct arm_smmu_device *smmu =platform_get_drvdata (pdev);int i; -struct rb_node *node;if (!smmu)return -ENODEV ; +arm_smmu_remove_mmu_masters (smmu);spin_lock (&arm_smmu_devices_lock);list_del (&smmu->list);spin_unlock (&arm_smmu_devices_lock); -for (node =rb_first (&smmu->masters); node; node =rb_next (node)) { -struct arm_smmu_master *master - = container_of(node, struct arm_smmu_master, node); -of_node_put (master->of_node); - } -if (!bitmap_empty (smmu->context_map,ARM_SMMU_MAX_CBS ))dev_err (&pdev->dev,"removing device with active domains!\n" );
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 8/12 iommu/arm-smmu: Refactor master/group config handling
In reply to Robin MurphyWe currently have some very vague and blurred boundaries around devices, groups, and what exactly an arm_smmu_master_cfg represents. Before we can properly deal with multi-master groups, this needs sorting out.
Ratify arm_smmu_master_cfg to strictly represent the set of stream IDs used by an individual device, plus the necessary per-device information for efficient lookup. Complement this by introducing arm_smmu_group_cfg to represent the union of all IDs owned by members of the group and arbitrate the actual allocation of stream mapping table resources.
--- drivers/iommu/arm-smmu.c | 321 +++++++++++++++++++++++++---------------------- 1 file changed, 171 insertions(+), 150 deletions(-)
drivers/iommu/arm-smmu.c +171 -150
@@ -274,16 +274,29 @@ enum arm_smmu_arch_version { ARM_SMMU_V2, }; -struct arm_smmu_smr { -u8 idx; +#define STREAM_UNASSIGNED 0 +#define STREAM_MULTIPLE -1 +struct arm_smmu_streamid { +int s2cr_idx; +u16 mask; +u16 id; +}; + +struct arm_smmu_stream_map_entry {u16 mask;u16 id; };struct arm_smmu_master_cfg { +struct arm_smmu_device *smmu; +struct arm_smmu_domain *smmu_domain;int num_streamids; -u16 streamids[MAX_MASTER_STREAMIDS ]; -struct arm_smmu_smr *smrs; +struct arm_smmu_streamid streamids[MAX_MASTER_STREAMIDS ]; +}; + +struct arm_smmu_group_cfg { +int num_smes; +struct arm_smmu_stream_map_entry smes[MAX_MASTER_STREAMIDS ]; };struct arm_smmu_master {
@@ -425,20 +438,6 @@ static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu,return NULL ; } -static struct arm_smmu_master_cfg * -find_smmu_master_cfg (struct device *dev) -{ -struct arm_smmu_master_cfg *cfg =NULL ; -struct iommu_group *group =iommu_group_get (dev); - -if (group) { - cfg =iommu_group_get_iommudata (group); -iommu_group_put (group); - } - -return cfg; -} -static int insert_smmu_master (struct arm_smmu_device *smmu,struct arm_smmu_master *master) {
@@ -502,7 +501,8 @@ static int register_smmu_master(struct arm_smmu_device *smmu, masterspec->np->name, smmu->num_mapping_groups);return -ERANGE ; } - master->cfg.streamids[i] = streamid; + master->cfg.streamids[i].id = streamid; +/* leave .mask 0; we don't currently share SMRs */ }return insert_smmu_master (smmu, master); }
@@ -1006,92 +1006,101 @@ static void arm_smmu_domain_free(struct iommu_domain *domain)kfree (smmu_domain); } -static int arm_smmu_master_configure_smrs (struct arm_smmu_device *smmu, -struct arm_smmu_master_cfg *cfg) +static int arm_smmu_master_configure_smrs (struct arm_smmu_master_cfg *cfg) { -int i; -struct arm_smmu_smr *smrs; +int i, err; +struct arm_smmu_device *smmu = cfg->smmu;void __iomem *gr0_base =ARM_SMMU_GR0 (smmu); - -if (!(smmu->features &ARM_SMMU_FEAT_STREAM_MATCH )) -return 0 ; - -if (cfg->smrs) -return -EEXIST ; - - smrs =kmalloc_array (cfg->num_streamids,sizeof (*smrs),GFP_KERNEL ); -if (!smrs) { -dev_err (smmu->dev,"failed to allocate %d SMRs\n" , - cfg->num_streamids); -return -ENOMEM ; - } +bool stream_match = smmu->features &ARM_SMMU_FEAT_STREAM_MATCH ;/* Allocate the SMRs on the SMMU */ -for (i =0 ; i < cfg->num_streamids; ++i) { -int idx =__arm_smmu_alloc_bitmap (smmu->smr_map,0 , - smmu->num_mapping_groups); -if (IS_ERR_VALUE (idx)) { -dev_err (smmu->dev,"failed to allocate free SMR\n" ); +for (i =0 ; i < cfg->num_streamids; i++) { +int idx; + +if (cfg->streamids[i].s2cr_idx ==STREAM_MULTIPLE ) +continue ; + +if (cfg->streamids[i].s2cr_idx >STREAM_UNASSIGNED ) { + err = -EEXIST ; goto err_free_smrs; } - smrs[i] = (struct arm_smmu_smr ) { - .idx = idx, - .mask =0 ,/* We don't currently share SMRs */ - .id = cfg->streamids[i], - }; +if (stream_match) + idx =__arm_smmu_alloc_bitmap (smmu->smr_map,0 , + smmu->num_mapping_groups); +else + idx = cfg->streamids[i].id; + +if (IS_ERR_VALUE (idx)) { +dev_err (smmu->dev,"failed to allocate free SMR\n" ); + err = -ENOSPC ; + goto err_free_smrs; + } + + cfg->streamids[i].s2cr_idx = idx +1 ; } +/* For stream indexing, we're only here for the bookkeeping... */ +if (!stream_match) +return 0 ; +/* It worked! Now, poke the actual hardware */ for (i =0 ; i < cfg->num_streamids; ++i) { -u32 reg =SMR_VALID | smrs[i].id <<SMR_ID_SHIFT | - smrs[i].mask <<SMR_MASK_SHIFT ; -writel_relaxed (reg, gr0_base +ARM_SMMU_GR0_SMR (smrs[i].idx)); +u32 idx = cfg->streamids[i].s2cr_idx -1 ; +u32 reg =SMR_VALID | cfg->streamids[i].id <<SMR_ID_SHIFT | + cfg->streamids[i].mask <<SMR_MASK_SHIFT ; + +if (idx !=STREAM_MULTIPLE ) +writel_relaxed (reg, gr0_base +ARM_SMMU_GR0_SMR (idx)); } - cfg->smrs = smrs;return 0 ; err_free_smrs: -while (--i >=0 ) -__arm_smmu_free_bitmap (smmu->smr_map, smrs[i].idx); -kfree (smrs); -return -ENOSPC ; +while (--i >=0 ) { +int idx = cfg->streamids[i].s2cr_idx -1 ; + +if (idx !=STREAM_MULTIPLE ) { +if (stream_match) +__arm_smmu_free_bitmap (smmu->smr_map, idx); + cfg->streamids[i].s2cr_idx =STREAM_UNASSIGNED ; + } + } +return err; } -static void arm_smmu_master_free_smrs (struct arm_smmu_device *smmu, -struct arm_smmu_master_cfg *cfg) +static void arm_smmu_master_free_smrs (struct arm_smmu_master_cfg *cfg) {int i; +struct arm_smmu_device *smmu = cfg->smmu;void __iomem *gr0_base =ARM_SMMU_GR0 (smmu); -struct arm_smmu_smr *smrs = cfg->smrs; - -if (!smrs) -return ; +bool stream_match = smmu->features &ARM_SMMU_FEAT_STREAM_MATCH ;/* Invalidate the SMRs before freeing back to the allocator */ for (i =0 ; i < cfg->num_streamids; ++i) { -u8 idx = smrs[i].idx; +u8 idx = cfg->streamids[i].s2cr_idx -1 ; + +if (cfg->streamids[i].s2cr_idx ==STREAM_MULTIPLE ) +continue ; + + cfg->streamids[i].s2cr_idx =STREAM_UNASSIGNED ; +if (!stream_match) +continue ;writel_relaxed (~SMR_VALID , gr0_base +ARM_SMMU_GR0_SMR (idx));__arm_smmu_free_bitmap (smmu->smr_map, idx); } - - cfg->smrs =NULL ; -kfree (smrs); }static int arm_smmu_domain_add_master (struct arm_smmu_domain *smmu_domain,struct arm_smmu_master_cfg *cfg) {int i, ret; -struct arm_smmu_device *smmu = smmu_domain->smmu; -void __iomem *gr0_base =ARM_SMMU_GR0 (smmu); +void __iomem *gr0_base =ARM_SMMU_GR0 (cfg->smmu); -/* Devices in an IOMMU group may already be configured */ - ret =arm_smmu_master_configure_smrs (smmu, cfg); + ret =arm_smmu_master_configure_smrs (cfg);if (ret) -return ret == -EEXIST ?0 : ret; +return ret;/* * FIXME: This won't be needed once we have IOMMU-backed DMA ops
@@ -1101,50 +1110,34 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,return 0 ;for (i =0 ; i < cfg->num_streamids; ++i) { -u32 idx, s2cr; +u32 idx = cfg->streamids[i].s2cr_idx -1 ; +u32 s2cr =S2CR_TYPE_TRANS |S2CR_PRIVCFG_UNPRIV | + (smmu_domain->cfg.cbndx <<S2CR_CBNDX_SHIFT ); - idx = cfg->smrs ? cfg->smrs[i].idx : cfg->streamids[i]; - s2cr =S2CR_TYPE_TRANS |S2CR_PRIVCFG_UNPRIV | - (smmu_domain->cfg.cbndx <<S2CR_CBNDX_SHIFT );writel_relaxed (s2cr, gr0_base +ARM_SMMU_GR0_S2CR (idx)); }return 0 ; } -static void arm_smmu_domain_remove_master (struct arm_smmu_domain *smmu_domain, -struct arm_smmu_master_cfg *cfg) +static void arm_smmu_domain_remove_master (struct arm_smmu_master_cfg *cfg) {int i; -struct arm_smmu_device *smmu = smmu_domain->smmu; -void __iomem *gr0_base =ARM_SMMU_GR0 (smmu); - -/* An IOMMU group is torn down by the first device to be removed */ -if ((smmu->features &ARM_SMMU_FEAT_STREAM_MATCH ) && !cfg->smrs) -return ; +void __iomem *gr0_base =ARM_SMMU_GR0 (cfg->smmu);/* * We *must* clear the S2CR first, because freeing the SMR means * that it can be re-allocated immediately. */ for (i =0 ; i < cfg->num_streamids; ++i) { -u32 idx = cfg->smrs ? cfg->smrs[i].idx : cfg->streamids[i]; +u32 idx = cfg->streamids[i].s2cr_idx -1 ;u32 reg = disable_bypass ?S2CR_TYPE_FAULT :S2CR_TYPE_BYPASS ;writel_relaxed (reg, gr0_base +ARM_SMMU_GR0_S2CR (idx)); } -arm_smmu_master_free_smrs (smmu, cfg); -} - -static void arm_smmu_detach_dev (struct device *dev, -struct arm_smmu_master_cfg *cfg) -{ -struct iommu_domain *domain = dev->archdata.iommu; -struct arm_smmu_domain *smmu_domain =to_smmu_domain (domain); - - dev->archdata.iommu =NULL ; -arm_smmu_domain_remove_master (smmu_domain, cfg); + cfg->smmu_domain =NULL ; +arm_smmu_master_free_smrs (cfg); }static int arm_smmu_attach_dev (struct iommu_domain *domain,struct device *dev)
@@ -1152,14 +1145,14 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)int ret;struct arm_smmu_domain *smmu_domain =to_smmu_domain (domain);struct arm_smmu_device *smmu; -struct arm_smmu_master_cfg *cfg; +struct arm_smmu_master_cfg *cfg = dev->archdata.iommu; - smmu =find_smmu_for_device (dev); -if (!smmu) { +if (!cfg) {dev_err (dev,"cannot attach to SMMU, is it on the same bus?\n" );return -ENXIO ; } + smmu = cfg->smmu;/* Ensure that the domain is finalised */ ret =arm_smmu_init_domain_context (domain, smmu);if (IS_ERR_VALUE (ret))
@@ -1176,18 +1169,13 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)return -EINVAL ; } -/* Looks ok, so add the device to the domain */ - cfg =find_smmu_master_cfg (dev); -if (!cfg) -return -ENODEV ; -/* Detach the dev from its current domain */ -if (dev->archdata.iommu) -arm_smmu_detach_dev (dev, cfg); +if (cfg->smmu_domain) +arm_smmu_domain_remove_master (cfg); ret =arm_smmu_domain_add_master (smmu_domain, cfg);if (!ret) - dev->archdata.iommu = domain; + cfg->smmu_domain = smmu_domain;return ret; }
@@ -1315,61 +1303,37 @@ static int __arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data)return 0 ;/* Continue walking */ } -static void __arm_smmu_release_pci_iommudata (void *data) -{ -kfree (data); -} - -static int arm_smmu_init_pci_device (struct pci_dev *pdev, -struct iommu_group *group) +static int arm_smmu_init_pci_device (struct arm_smmu_device *smmu, +struct pci_dev *pdev) {struct arm_smmu_master_cfg *cfg;u16 sid; -int i; - - cfg =iommu_group_get_iommudata (group); -if (!cfg) { - cfg =kzalloc (sizeof (*cfg),GFP_KERNEL ); -if (!cfg) -return -ENOMEM ; - -iommu_group_set_iommudata (group, cfg, - __arm_smmu_release_pci_iommudata); - } - -if (cfg->num_streamids >=MAX_MASTER_STREAMIDS ) -return -ENOSPC ; + cfg =kzalloc (sizeof (*cfg),GFP_KERNEL ); +if (!cfg) +return -ENOMEM ;/* * Assume Stream ID == Requester ID for now. * We need a way to describe the ID mappings in FDT. */ pci_for_each_dma_alias (pdev, __arm_smmu_get_pci_sid, &sid); -for (i =0 ; i < cfg->num_streamids; ++i) -if (cfg->streamids[i] == sid) -break ; -/* Avoid duplicate SIDs, as this can lead to SMR conflicts */ -if (i == cfg->num_streamids) - cfg->streamids[cfg->num_streamids++] = sid; + cfg->streamids[0 ].id = sid; + cfg->num_streamids =1 ; + + cfg->smmu = smmu; + pdev->dev.archdata.iommu = cfg;return 0 ; } -static int arm_smmu_init_platform_device (struct device *dev, -struct iommu_group *group) +static int arm_smmu_init_platform_device (struct arm_smmu_device *smmu, +struct device *dev) { -struct arm_smmu_device *smmu =find_smmu_for_device (dev);struct arm_smmu_master *master; -if (!smmu) -return -ENODEV ; - - master =find_smmu_master (smmu, dev->of_node); -if (!master) -return -ENODEV ; - -iommu_group_set_iommudata (group, &master->cfg,NULL ); + master =find_smmu_master (smmu,dev_get_dev_node (dev)); + dev->archdata.iommu = &master->cfg;return 0 ; }
@@ -1377,6 +1341,22 @@ static int arm_smmu_init_platform_device(struct device *dev,static int arm_smmu_add_device (struct device *dev) {struct iommu_group *group; +struct arm_smmu_device *smmu; +int ret; + +if (dev->archdata.iommu) +return -EEXIST ; + + smmu =find_smmu_for_device (dev); +if (!smmu) +return -ENODEV ; + +if (dev_is_pci (dev)) + ret =arm_smmu_init_pci_device (smmu,to_pci_dev (dev)); +else + ret =arm_smmu_init_platform_device (smmu, dev); +if (ret) +return ret; group =iommu_group_get_for_dev (dev);if (IS_ERR (group))
@@ -1389,12 +1369,30 @@ static int arm_smmu_add_device(struct device *dev)static void arm_smmu_remove_device (struct device *dev) {iommu_group_remove_device (dev); +if (dev_is_pci (dev)) +kfree (dev->archdata.iommu); +} + +static void __arm_smmu_release_iommudata (void *data) +{ +kfree (data); +} + +static inline bool __streamid_match_sme (struct arm_smmu_streamid *sid, +struct arm_smmu_stream_map_entry *sme) +{ +/* This will get rather more complicated with masking... */ +return sid->id == sme->id; }static struct iommu_group *arm_smmu_device_group (struct device *dev) { +struct arm_smmu_master_cfg *master_cfg; +struct arm_smmu_group_cfg *group_cfg; +struct arm_smmu_streamid *sids; +struct arm_smmu_stream_map_entry *smes;struct iommu_group *group; -int ret; +int i, j;if (dev_is_pci (dev)) group =pci_device_group (dev);
@@ -1404,14 +1402,37 @@ static struct iommu_group *arm_smmu_device_group(struct device *dev)if (IS_ERR (group))return group; -if (dev_is_pci (dev)) - ret =arm_smmu_init_pci_device (to_pci_dev (dev), group); -else - ret =arm_smmu_init_platform_device (dev, group); + master_cfg = dev->archdata.iommu; + group_cfg =iommu_group_get_iommudata (group); +if (!group_cfg) { + group_cfg =kzalloc (sizeof (*group_cfg),GFP_KERNEL ); +if (!group_cfg) +return ERR_PTR (-ENOMEM ); -if (ret) { -iommu_group_put (group); - group =ERR_PTR (ret); +iommu_group_set_iommudata (group, group_cfg, + __arm_smmu_release_iommudata); + } + +/* Propagate device's IDs to the group, avoiding duplicate entries */ + sids = master_cfg->streamids; + smes = group_cfg->smes; +for (i =0 ; i < master_cfg->num_streamids; i++) { +for (j =0 ; j < group_cfg->num_smes; j++) { +if (__streamid_match_sme (&sids[i], &smes[j])) { + sids[i].s2cr_idx =STREAM_MULTIPLE ; +break ; + } + } + +if (j < group_cfg->num_smes) +continue ; + +if (group_cfg->num_smes ==MAX_MASTER_STREAMIDS ) { +iommu_group_put (group); +return ERR_PTR (-ENOSPC ); + } + + smes[group_cfg->num_smes++].id = sids[i].id; }return group;
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 9/12 iommu/arm-smmu: Simplify mmu-masters handling
In reply to Robin MurphyWith the preceding data reshuffles allowing SMMU instances to be looked up directly wherever needed, we can now wipe out the last vestiges of the global SMMU list. With mmu-masters lookup now a one-time thing outside of any real hotpath, condense our stash of those out of the SMMU instance data down to a single shared list, where they may await their eventual deprecation.
This also seems like a good opportunity to move the parts only relevant to the probe/remove routines down to live in that area of the code.
--- drivers/iommu/arm-smmu.c | 231 +++++++++++++++++++---------------------------- 1 file changed, 91 insertions(+), 140 deletions(-)
drivers/iommu/arm-smmu.c +91 -140
@@ -301,7 +301,7 @@ struct arm_smmu_group_cfg {struct arm_smmu_master {struct device_node *of_node; -struct rb_node node; +struct list_head list;struct arm_smmu_master_cfg cfg; };
@@ -372,8 +372,8 @@ struct arm_smmu_domain {static struct iommu_ops arm_smmu_ops; -static DEFINE_SPINLOCK (arm_smmu_devices_lock); -static LIST_HEAD (arm_smmu_devices); +static DEFINE_RWLOCK (arm_smmu_masters_lock); +static LIST_HEAD (arm_smmu_masters);struct arm_smmu_option_prop {u32 opt;
@@ -417,111 +417,20 @@ static struct device_node *dev_get_dev_node(struct device *dev)return dev->of_node; } -static struct arm_smmu_master *find_smmu_master (struct arm_smmu_device *smmu, -struct device_node *dev_node) +static struct arm_smmu_master *find_smmu_master (struct device_node *dev_node) { -struct rb_node *node = smmu->masters.rb_node; - -while (node) { -struct arm_smmu_master *master; - - master = container_of(node, struct arm_smmu_master, node); - -if (dev_node < master->of_node) - node = node->rb_left; -else if (dev_node > master->of_node) - node = node->rb_right; -else -return master; - } - -return NULL ; -} - -static int insert_smmu_master (struct arm_smmu_device *smmu, -struct arm_smmu_master *master) -{ -struct rb_node **new, *parent; - - new = &smmu->masters.rb_node; - parent =NULL ; -while (*new) { -struct arm_smmu_master *this - = container_of(*new, struct arm_smmu_master, node); - - parent = *new; -if (master->of_node < this->of_node) - new = &((*new)->rb_left); -else if (master->of_node > this->of_node) - new = &((*new)->rb_right); -else -return -EEXIST ; - } - -rb_link_node (&master->node, parent, new); -rb_insert_color (&master->node, &smmu->masters); -return 0 ; -} - -static int register_smmu_master (struct arm_smmu_device *smmu, -struct of_phandle_args *masterspec) -{ -int i;struct arm_smmu_master *master; - master =find_smmu_master (smmu, masterspec->np); -if (master) { -dev_err (smmu->dev, -"rejecting multiple registrations for master device %s\n" , - masterspec->np->name); -return -EBUSY ; - } +read_lock (&arm_smmu_masters_lock); +list_for_each_entry (master, &arm_smmu_masters, list) +if (master->of_node == dev_node) + goto out_unlock; -if (masterspec->args_count >MAX_MASTER_STREAMIDS ) { -dev_err (smmu->dev, -"reached maximum number (%d) of stream IDs for master device %s\n" , -MAX_MASTER_STREAMIDS , masterspec->np->name); -return -ENOSPC ; - } + master =NULL ; - master =devm_kzalloc (smmu->dev,sizeof (*master),GFP_KERNEL ); -if (!master) -return -ENOMEM ; - - master->of_node = masterspec->np; - master->cfg.num_streamids = masterspec->args_count; - -for (i =0 ; i < master->cfg.num_streamids; ++i) { -u16 streamid = masterspec->args[i]; - -if (!(smmu->features &ARM_SMMU_FEAT_STREAM_MATCH ) && - (streamid >= smmu->num_mapping_groups)) { -dev_err (smmu->dev, -"stream ID for master device %s greater than maximum allowed (%d)\n" , - masterspec->np->name, smmu->num_mapping_groups); -return -ERANGE ; - } - master->cfg.streamids[i].id = streamid; -/* leave .mask 0; we don't currently share SMRs */ - } -return insert_smmu_master (smmu, master); -} - -static struct arm_smmu_device *find_smmu_for_device (struct device *dev) -{ -struct arm_smmu_device *smmu; -struct arm_smmu_master *master =NULL ; -struct device_node *dev_node =dev_get_dev_node (dev); - -spin_lock (&arm_smmu_devices_lock); -list_for_each_entry (smmu, &arm_smmu_devices, list) { - master =find_smmu_master (smmu, dev_node); -if (master) -break ; - } -spin_unlock (&arm_smmu_devices_lock); - -return master ? smmu :NULL ; +out_unlock: +read_unlock (&arm_smmu_masters_lock); +return master; }static int __arm_smmu_alloc_bitmap (unsigned long *map,int start,int end)
@@ -1327,36 +1236,26 @@ static int arm_smmu_init_pci_device(struct arm_smmu_device *smmu,return 0 ; } -static int arm_smmu_init_platform_device (struct arm_smmu_device *smmu, -struct device *dev) -{ -struct arm_smmu_master *master; - - master =find_smmu_master (smmu,dev_get_dev_node (dev)); - dev->archdata.iommu = &master->cfg; - -return 0 ; -} -static int arm_smmu_add_device (struct device *dev) {struct iommu_group *group; -struct arm_smmu_device *smmu; -int ret; +struct arm_smmu_master *master;if (dev->archdata.iommu)return -EEXIST ; - smmu =find_smmu_for_device (dev); -if (!smmu) + master =find_smmu_master (dev_get_dev_node (dev)); +if (!master)return -ENODEV ; -if (dev_is_pci (dev)) - ret =arm_smmu_init_pci_device (smmu,to_pci_dev (dev)); -else - ret =arm_smmu_init_platform_device (smmu, dev); -if (ret) -return ret; +if (dev_is_pci (dev)) { +int ret =arm_smmu_init_pci_device (master->cfg.smmu, +to_pci_dev (dev)); +if (ret) +return ret; + }else { + dev->archdata.iommu = &master->cfg; + } group =iommu_group_get_for_dev (dev);if (IS_ERR (group))
@@ -1752,19 +1651,79 @@ MODULE_DEVICE_TABLE(of, arm_smmu_of_match);static void arm_smmu_remove_mmu_masters (struct arm_smmu_device *smmu) { -struct arm_smmu_master *master; -struct rb_node *node, *tmp; +struct arm_smmu_master *master, *tmp; - node =rb_first (&smmu->masters); -while (node) { - tmp = node; - node =rb_next (node); - master = container_of(tmp, struct arm_smmu_master, node); +write_lock (&arm_smmu_masters_lock); +list_for_each_entry_safe (master, tmp, &arm_smmu_masters, list) { +if (master->cfg.smmu != smmu) +continue ; -rb_erase (tmp, &smmu->masters); +list_del (&master->list);of_node_put (master->of_node);devm_kfree (smmu->dev, master); } +write_unlock (&arm_smmu_masters_lock); +} + +static int insert_smmu_master (struct arm_smmu_master *master) +{ +struct arm_smmu_master *tmp; +int ret = -EEXIST ; + +write_lock (&arm_smmu_masters_lock); +list_for_each_entry (tmp, &arm_smmu_masters, list) +if (tmp->of_node == master->of_node) + goto out_unlock; + + ret =0 ; +list_add (&master->list, &arm_smmu_masters); +out_unlock: +write_unlock (&arm_smmu_masters_lock); +return ret; +} + +static int register_smmu_master (struct arm_smmu_device *smmu, +struct of_phandle_args *masterspec) +{ +int i; +struct arm_smmu_master *master; + + master =find_smmu_master (masterspec->np); +if (master) { +dev_err (smmu->dev, +"rejecting multiple registrations for master device %s\n" , + masterspec->np->name); +return -EBUSY ; + } + +if (masterspec->args_count >MAX_MASTER_STREAMIDS ) { +dev_err (smmu->dev, +"reached maximum number (%d) of stream IDs for master device %s\n" , +MAX_MASTER_STREAMIDS , masterspec->np->name); +return -ENOSPC ; + } + + master =devm_kzalloc (smmu->dev,sizeof (*master),GFP_KERNEL ); +if (!master) +return -ENOMEM ; + + master->of_node = masterspec->np; + master->cfg.num_streamids = masterspec->args_count; + +for (i =0 ; i < master->cfg.num_streamids; ++i) { +u16 streamid = masterspec->args[i]; + +if (!(smmu->features &ARM_SMMU_FEAT_STREAM_MATCH ) && + (streamid >= smmu->num_mapping_groups)) { +dev_err (smmu->dev, +"stream ID for master device %s greater than maximum allowed (%d)\n" , + masterspec->np->name, smmu->num_mapping_groups); +return -ERANGE ; + } + master->cfg.streamids[i].id = streamid; +/* leave .mask 0; we don't currently share SMRs */ + } +return insert_smmu_master (master); }static int arm_smmu_probe_mmu_masters (struct arm_smmu_device *smmu)
@@ -1880,11 +1839,6 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)platform_set_drvdata (pdev, smmu);arm_smmu_probe_mmu_masters (smmu); -INIT_LIST_HEAD (&smmu->list); -spin_lock (&arm_smmu_devices_lock); -list_add (&smmu->list, &arm_smmu_devices); -spin_unlock (&arm_smmu_devices_lock); -arm_smmu_device_reset (smmu);return 0 ;
@@ -1903,9 +1857,6 @@ static int arm_smmu_device_remove(struct platform_device *pdev)return -ENODEV ;arm_smmu_remove_mmu_masters (smmu); -spin_lock (&arm_smmu_devices_lock); -list_del (&smmu->list); -spin_unlock (&arm_smmu_devices_lock);if (!bitmap_empty (smmu->context_map,ARM_SMMU_MAX_CBS ))dev_err (&pdev->dev,"removing device with active domains!\n" );
Signature · 1 line
2.7.2.333.g70bd996.dirty
Robin Murphy
reviewerPATCH 10/12 Docs: dt: document ARM SMMU generic binding usage
In reply to Robin MurphyDocument how the generic "iommus" binding should be used to describe ARM SMMU stream IDs instead of the old "mmu-masters" binding.
--- .../devicetree/bindings/iommu/arm,smmu.txt | 40 ++++++++++++++-------- 1 file changed, 25 insertions(+), 15 deletions(-)
Documentation/devicetree/bindings/iommu/arm,smmu.txt +25 -15
@@ -34,12 +34,11 @@ conditions. interrupt per context bank. In the case of a single, combined interrupt, it must be listed multiple times. -- mmu-masters : A list of phandles to device nodes representing bus - masters for which the SMMU can provide a translation - and their corresponding StreamIDs (see example below). - Each device node linked from this list must have a - "#stream-id-cells" property, indicating the number of - StreamIDs associated with it. +- #iommu-cells : See Documentation/devicetree/bindings/iommu/iommu.txt + for details. Should be 1, where each "iommus" entry on + the device represents a distinct stream ID emitted by + that device into the relevant SMMU. Cells beyond 1 are + reserved for future use. ** System MMU optional properties:
@@ -55,9 +54,19 @@ conditions. aliases of secure registers have to be used during SMMU configuration. -Example: +** Deprecated properties: - smmu { +- mmu-masters (deprecated in favour of the generic "iommus" binding) : + A list of phandles to device nodes representing bus + masters for which the SMMU can provide a translation + and their corresponding StreamIDs (see example below). + Each device node linked from this list must have a + "#stream-id-cells" property, indicating the number of + StreamIDs associated with it. + +** Example: + + smmu1: iommu { compatible = "arm,smmu-v1"; reg = <0xba5e0000 0x10000>; #global-interrupts = <2>;
@@ -67,11 +76,12 @@ Example: <0 35 4>, <0 36 4>, <0 37 4>; - - /* - * Two DMA controllers, the first with two StreamIDs (0xd01d - * and 0xd01e) and the second with only one (0xd11c). - */ - mmu-masters = <&dma0 0xd01d 0xd01e>, - <&dma1 0xd11c>; + #iommu-cells = <1>; }; + + /* device with two stream IDs, 0 and 7 */ + master { + iommus = <&smmu1 0>, + <&smmu1 7>; + }; +
Signature · 1 line
2.7.2.333.g70bd996.dirty
Rob Herring
maintainerPATCH 10/12 Docs: dt: document ARM SMMU generic binding usage
Quoted 7 lines · Robin Murphy
Document how the generic "iommus" binding should be used to describe ARM SMMU stream IDs instead of the old "mmu-masters" binding.
Signed-off-by Robin Murphy <[email protected]>--- .../devicetree/bindings/iommu/arm,smmu.txt | 40 ++++++++++++++-------- 1 file changed, 25 insertions(+), 15 deletions(-)
Robin Murphy
reviewerPATCH 11/12 iommu/arm-smmu: Generic IOMMU DT bindings support
In reply to Robin MurphyImplement an of_xlate callback and the appropriate registration so that we can configure masters via generic DT bindings. Initially, we have the equivalent level of functionality with respect to groups, stream ID limits, etc. as for the old bindings, but the door is now open for further improvements.
Since of_iommmu_configure() is not yet clever enough to enforce the necessary probe ordering dependencies, employ the same explicit device creation tactic as the Exynos IOMMU driver to ensure our actual SMMU instance is up and running in time to handle of_xlate calls.
--- drivers/iommu/arm-smmu.c | 144 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 38 deletions(-)
drivers/iommu/arm-smmu.c +106 -38
@@ -39,6 +39,8 @@#include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_iommu.h> +#include <linux/of_platform.h> #include <linux/pci.h> #include <linux/platform_device.h> #include <linux/slab.h>
@@ -421,6 +423,9 @@ static struct arm_smmu_master *find_smmu_master(struct device_node *dev_node) {struct arm_smmu_master *master; +if (!dev_node) +return NULL ; +read_lock (&arm_smmu_masters_lock);list_for_each_entry (master, &arm_smmu_masters, list)if (master->of_node == dev_node)
@@ -1011,13 +1016,6 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,if (ret)return ret; -/* -* FIXME: This won't be needed once we have IOMMU-backed DMA ops -* for all devices behind the SMMU. -*/ -if (smmu_domain->domain.type ==IOMMU_DOMAIN_DMA ) -return 0 ; -for (i =0 ; i < cfg->num_streamids; ++i) {u32 idx = cfg->streamids[i].s2cr_idx -1 ; u32 s2cr = S2CR_TYPE_TRANS | S2CR_PRIVCFG_UNPRIV |
@@ -1206,57 +1204,79 @@ static bool arm_smmu_capable(enum iommu_cap cap) } } +static int arm_smmu_add_dev_streamid (struct arm_smmu_device *smmu, +struct device *dev,u16 sid) +{ +struct arm_smmu_master_cfg *cfg = dev->archdata.iommu; +int i; + +if (!cfg) { + cfg =kzalloc (sizeof (*cfg),GFP_KERNEL ); +if (!cfg) +return -ENOMEM ; + + cfg->smmu = smmu; + dev->archdata.iommu = cfg; + } + +if (cfg->num_streamids >=MAX_MASTER_STREAMIDS ) +return -ENOSPC ; + +/* Avoid duplicate SIDs, as this can lead to SMR conflicts */ +for (i =0 ; i < cfg->num_streamids; ++i) +if (cfg->streamids[i].id == sid) { +dev_warn (dev,"Stream ID 0x%hx repeated; ignoring\n" , + sid); +return 0 ; + } + + cfg->streamids[cfg->num_streamids++].id = sid; + +return 0 ; +} +static int __arm_smmu_get_pci_sid (struct pci_dev *pdev,u16 alias,void *data) { *((u16 *)data) = alias;return 0 ;/* Continue walking */ } -static int arm_smmu_init_pci_device (struct arm_smmu_device *smmu, -struct pci_dev *pdev) +static int arm_smmu_init_legacy_master (struct device *dev) { -struct arm_smmu_master_cfg *cfg; +struct arm_smmu_master *master; +struct device_node *np =dev_get_dev_node (dev);u16 sid; - cfg =kzalloc (sizeof (*cfg),GFP_KERNEL ); -if (!cfg) -return -ENOMEM ; -/* -* Assume Stream ID == Requester ID for now. -* We need a way to describe the ID mappings in FDT. -*/ -pci_for_each_dma_alias (pdev, __arm_smmu_get_pci_sid, &sid); + master =find_smmu_master (np); +if (!master) +return -ENODEV ; - cfg->streamids[0 ].id = sid; - cfg->num_streamids =1 ; +if (!dev_is_pci (dev)) { + dev->archdata.iommu = &master->cfg; +return 0 ; + } - cfg->smmu = smmu; - pdev->dev.archdata.iommu = cfg; - -return 0 ; +/* Legacy bindings assume Stream ID == Requester ID */ +pci_for_each_dma_alias (to_pci_dev (dev), __arm_smmu_get_pci_sid, &sid); +return arm_smmu_add_dev_streamid (master->cfg.smmu, dev, sid); }static int arm_smmu_add_device (struct device *dev) {struct iommu_group *group; -struct arm_smmu_master *master; -if (dev->archdata.iommu) -return -EEXIST ; +if (!dev->archdata.iommu) { +int ret =arm_smmu_init_legacy_master (dev); - master =find_smmu_master (dev_get_dev_node (dev)); -if (!master) -return -ENODEV ; - -if (dev_is_pci (dev)) { -int ret =arm_smmu_init_pci_device (master->cfg.smmu, -to_pci_dev (dev));if (ret)return ret; - }else { - dev->archdata.iommu = &master->cfg; } +/* +* For now, assume that the default group allocators suffice. +* We might have to do some preparatory work here to properly +* handle multiple devices sharing stream IDs. +*/ group =iommu_group_get_for_dev (dev);if (IS_ERR (group))return PTR_ERR (group);
@@ -1268,7 +1288,7 @@ static int arm_smmu_add_device(struct device *dev)static void arm_smmu_remove_device (struct device *dev) {iommu_group_remove_device (dev); -if (dev_is_pci (dev)) +if (!find_smmu_master (dev->of_node))kfree (dev->archdata.iommu); }
@@ -1381,6 +1401,20 @@ out_unlock:return ret; } +static int arm_smmu_of_xlate (struct device *dev,struct of_phandle_args *args) +{ +struct arm_smmu_device *smmu; +struct platform_device *smmu_pdev; + + smmu_pdev =of_find_device_by_node (args->np); +if (!smmu_pdev) +return -ENODEV ; + + smmu =platform_get_drvdata (smmu_pdev); + +return arm_smmu_add_dev_streamid (smmu, dev, args->args[0 ]); +} +static struct iommu_ops arm_smmu_ops = { .capable = arm_smmu_capable, .domain_alloc = arm_smmu_domain_alloc,
@@ -1395,6 +1429,7 @@ static struct iommu_ops arm_smmu_ops = { .device_group = arm_smmu_device_group, .domain_get_attr = arm_smmu_domain_get_attr, .domain_set_attr = arm_smmu_domain_set_attr, + .of_xlate = arm_smmu_of_xlate, .pgsize_bitmap = -1UL, /* Restricted during device attach */ };
@@ -1731,6 +1766,9 @@ static int arm_smmu_probe_mmu_masters(struct arm_smmu_device *smmu)struct of_phandle_args masterspec;int err, i =0 ; +dev_notice (smmu->dev, +"Deprecated \"mmu-masters\" property found; update DT to \"iommus\" property if possible\n" ); +while (!of_parse_phandle_with_args (smmu->dev->of_node,"mmu-masters" ,"#stream-id-cells" , i, &masterspec)) {
@@ -1838,7 +1876,9 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) }platform_set_drvdata (pdev, smmu); -arm_smmu_probe_mmu_masters (smmu); +/* Check first to avoid of_parse_phandle_with_args complaining */ +if (of_property_read_bool (dev->of_node,"mmu-masters" )) +arm_smmu_probe_mmu_masters (smmu);arm_smmu_device_reset (smmu);return 0 ;
@@ -1881,8 +1921,11 @@ static struct platform_driver arm_smmu_driver = {static int __init arm_smmu_init (void ) {struct device_node *np; +static bool done;int ret; +if (done) +return 0 ;/* * Play nice with systems that don't have an ARM SMMU by checking that * an ARM SMMU exists in the system before proceeding with the driver
@@ -1912,6 +1955,7 @@ static int __init arm_smmu_init(void)bus_set_iommu (&pci_bus_type, &arm_smmu_ops);#endif + done = true;return 0 ; }
@@ -1923,6 +1967,30 @@ static void __exit arm_smmu_exit(void)subsys_initcall (arm_smmu_init);module_exit (arm_smmu_exit); +static int __init arm_smmu_of_init (struct device_node *np) +{ +struct arm_smmu_device *smmu; +struct platform_device *pdev; +int ret =arm_smmu_init (); + +if (ret) +return ret; + + pdev =of_platform_device_create (np,NULL , platform_bus_type.dev_root); +if (!pdev) +return -ENODEV ; + + smmu =platform_get_drvdata (pdev); +of_iommu_set_ops (np, &arm_smmu_ops); + +return 0 ; +} +IOMMU_OF_DECLARE (arm_smmuv1,"arm,smmu-v1" , arm_smmu_of_init); +IOMMU_OF_DECLARE (arm_smmuv2,"arm,smmu-v2" , arm_smmu_of_init); +IOMMU_OF_DECLARE (arm_mmu400,"arm,mmu-400" , arm_smmu_of_init); +IOMMU_OF_DECLARE (arm_mmu401,"arm,mmu-401" , arm_smmu_of_init); +IOMMU_OF_DECLARE (arm_mmu500,"arm,mmu-500" , arm_smmu_of_init); +MODULE_DESCRIPTION ("IOMMU API for ARM architected SMMU implementations" );MODULE_AUTHOR ("Will Deacon <[email protected]>" );MODULE_LICENSE ("GPL v2" );
Signature · 1 line
2.7.2.333.g70bd996.dirty
PATCH 11/12 iommu/arm-smmu: Generic IOMMU DT bindings support
Hi Robin,
Quoted 5 lines
-----Original Message-----
From: linux-arm-kernel [mailto:linux-arm-kernel- bounces at lists.infradead.org] On Behalf Of Robin Murphy
Sent: Monday, February 29, 2016 7:16 PM
To: iommu at lists.linux-foundation.org;
linux-arm-kernel at lists.infradead.org;
Quoted 11 lines
devicetree at vger.kernel.org
Cc: Thomas.Lendacky at amd.com; anup.patel at broadcom.com; thunder.leizhen at huawei.com; will.deacon at arm.com; stuart.yoder at nxp.com; Suravee.Suthikulpanit at amd.com; tchalamarla at caviumnetworks.com
Subject: [PATCH 11/12] iommu/arm-smmu: Generic IOMMU DT bindings supportImplement an
of_xlatecallback and the appropriate registration so that we can configure masters via generic DT bindings. Initially, we have the equivalent level of functionality with respect to groups, stream ID
limits, etc.
Quoted 1 line
as for the old bindings, but the door is now open for further
improvements.
Quoted 191 lines
Since
of_iommmu_configure()is not yet clever enough to enforce the necessary probe ordering dependencies, employ the same explicit device creation tactic as the Exynos IOMMU driver to ensure our actual SMMU instance is up and running in time to handleof_xlatecalls.Signed-off-by Robin Murphy <[email protected]>--- drivers/iommu/arm-smmu.c | 144 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 38 deletions(-)drivers/iommu/arm-smmu.c index renamed from drivers/iommu/arm-smmu.c +0
-091b0a1b..8fcf27a 100644
drivers/iommu/arm-smmu.c +5
-2@@ -39,6 +39,8 @@#include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_iommu.h> +#include <linux/of_platform.h> #include <linux/pci.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -421,6 +423,9 @@ static struct arm_smmu_master *find_smmu_master(struct device_node *dev_node) {struct arm_smmu_master *master; +if (!dev_node) +return NULL ; +read_lock (&arm_smmu_masters_lock);list_for_each_entry (master, &arm_smmu_masters, list)if (master->of_node == dev_node)@@ -1011,13 +1016,6 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, if (ret) return ret; -/* -* FIXME: This won't be needed once we have IOMMU-backed DMA ops
drivers/iommu/arm-smmu.c +51
-34-* for all devices behind the SMMU. -*/ -if (smmu_domain->domain.type ==IOMMU_DOMAIN_DMA ) -return 0 ; -for (i =0 ; i < cfg->num_streamids; ++i) {u32 idx = cfg->streamids[i].s2cr_idx -1 ; u32 s2cr = S2CR_TYPE_TRANS | S2CR_PRIVCFG_UNPRIV |@@ -1206,57 +1204,79 @@ static bool arm_smmu_capable(enum iommu_cap cap) } } +static int arm_smmu_add_dev_streamid (struct arm_smmu_device *smmu, +struct device *dev,u16 sid) +{ +struct arm_smmu_master_cfg *cfg = dev->archdata.iommu; +int i; + +if (!cfg) { + cfg =kzalloc (sizeof (*cfg),GFP_KERNEL ); +if (!cfg) +return -ENOMEM ; + + cfg->smmu = smmu; + dev->archdata.iommu = cfg; + } + +if (cfg->num_streamids >=MAX_MASTER_STREAMIDS ) +return -ENOSPC ; + +/* Avoid duplicate SIDs, as this can lead to SMR conflicts */ +for (i =0 ; i < cfg->num_streamids; ++i) +if (cfg->streamids[i].id == sid) { +dev_warn (dev,"Stream ID 0x%hx repeated; ignoring\n", + sid); +return 0 ; + } + + cfg->streamids[cfg->num_streamids++].id = sid; + +return 0 ; +} +static int __arm_smmu_get_pci_sid (struct pci_dev *pdev,u16 alias,void *data) { *((u16 *)data) = alias;return 0 ;/* Continue walking */ } -static int arm_smmu_init_pci_device (struct arm_smmu_device *smmu, -struct pci_dev *pdev) +static int arm_smmu_init_legacy_master (struct device *dev) { -struct arm_smmu_master_cfg *cfg; +struct arm_smmu_master *master; +struct device_node *np =dev_get_dev_node (dev);u16 sid; - cfg =kzalloc (sizeof (*cfg),GFP_KERNEL ); -if (!cfg) -return -ENOMEM ; -/* -* Assume Stream ID == Requester ID for now. -* We need a way to describe the ID mappings in FDT. -*/ -pci_for_each_dma_alias (pdev, __arm_smmu_get_pci_sid, &sid); + master =find_smmu_master (np); +if (!master) +return -ENODEV ; - cfg->streamids[0 ].id = sid; - cfg->num_streamids =1 ; +if (!dev_is_pci (dev)) { + dev->archdata.iommu = &master->cfg; +return 0 ; + } - cfg->smmu = smmu; - pdev->dev.archdata.iommu = cfg; - -return 0 ; +/* Legacy bindings assume Stream ID == Requester ID */ +pci_for_each_dma_alias (to_pci_dev (dev), __arm_smmu_get_pci_sid, &sid); +return arm_smmu_add_dev_streamid (master->cfg.smmu, dev, sid); }static int arm_smmu_add_device (struct device *dev) {struct iommu_group *group; -struct arm_smmu_master *master; -if (dev->archdata.iommu) -return -EEXIST ; +if (!dev->archdata.iommu) { +int ret =arm_smmu_init_legacy_master (dev); - master =find_smmu_master (dev_get_dev_node (dev)); -if (!master) -return -ENODEV ; - -if (dev_is_pci (dev)) { -int ret =arm_smmu_init_pci_device (master->cfg.smmu, -to_pci_dev (dev));if (ret)return ret; - }else { - dev->archdata.iommu = &master->cfg; } +/* +* For now, assume that the default group allocators suffice. +* We might have to do some preparatory work here to properly +* handle multiple devices sharing stream IDs. +*/ group =iommu_group_get_for_dev (dev);drivers/iommu/arm-smmu.c +14
-1if (IS_ERR (group))return PTR_ERR (group);@@ -1268,7 +1288,7 @@ static int arm_smmu_add_device(struct device *dev) static void arm_smmu_remove_device(struct device *dev) {iommu_group_remove_device (dev); -if (dev_is_pci (dev)) +if (!find_smmu_master (dev->of_node))kfree (dev->archdata.iommu); }@@ -1381,6 +1401,20 @@ out_unlock:return ret; } +static int arm_smmu_of_xlate (struct device *dev,struct of_phandle_args +*args) { +struct arm_smmu_device *smmu; +struct platform_device *smmu_pdev; + + smmu_pdev =of_find_device_by_node (args->np); +if (!smmu_pdev) +return -ENODEV ; + + smmu =platform_get_drvdata (smmu_pdev); + +return arm_smmu_add_dev_streamid (smmu, dev, args->args[0 ]); } +static struct iommu_ops arm_smmu_ops = { .capable = arm_smmu_capable, .domain_alloc = arm_smmu_domain_alloc,
drivers/iommu/arm-smmu.c static struct iommu_ops arm_smmu_ops = { · line 1433
Quoted 5 lines
drivers/iommu/arm-smmu.c +1
-0@@ -1395,6 +1429,7 @@ static struct iommu_ops arm_smmu_ops = { .device_group = arm_smmu_device_group, .domain_get_attr = arm_smmu_domain_get_attr, .domain_set_attr = arm_smmu_domain_set_attr, + .of_xlate = arm_smmu_of_xlate, .pgsize_bitmap = -1UL, /* Restricted during device attach
*/
Quoted 75 lines
drivers/iommu/arm-smmu.c +2
-0};@@ -1731,6 +1766,9 @@ static int arm_smmu_probe_mmu_masters(struct arm_smmu_device *smmu)struct of_phandle_args masterspec;int err, i =0 ; + dev_notice(smmu->dev, + "Deprecated \"mmu-masters\" property found; update DTto
+\"iommus\" property if possible\n");drivers/iommu/arm-smmu.c +1
-0+ while (!of_parse_phandle_with_args(smmu->dev->of_node, "mmu-masters",
drivers/iommu/arm-smmu.c +6
-1"#stream-id-cells", i, &masterspec)) {@@ -1838,7 +1876,9 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) }platform_set_drvdata (pdev, smmu); -arm_smmu_probe_mmu_masters (smmu); +/* Check first to avoid of_parse_phandle_with_args complaining */ +if (of_property_read_bool (dev->of_node,"mmu-masters" )) +arm_smmu_probe_mmu_masters (smmu);arm_smmu_device_reset (smmu);return 0 ;@@ -1881,8 +1921,11 @@ static struct platform_driver arm_smmu_driver = {static int __init arm_smmu_init (void ) {struct device_node *np; +static bool done;int ret; +if (done) +return 0 ;/* * Play nice with systems that don't have an ARM SMMU by checking that
* an ARM SMMU exists in the system before proceeding with the
driver @@ -1912,6 +1955,7 @@ static int__initarm_smmu_init(void)drivers/iommu/arm-smmu.c +22
-0bus_set_iommu (&pci_bus_type, &arm_smmu_ops);#endif + done = true;return 0 ; }@@ -1923,6 +1967,30 @@ static void __exit arm_smmu_exit(void)subsys_initcall (arm_smmu_init);module_exit (arm_smmu_exit); +static int __init arm_smmu_of_init (struct device_node *np) { +struct arm_smmu_device *smmu; +struct platform_device *pdev; +int ret =arm_smmu_init (); + +if (ret) +return ret; + + pdev =of_platform_device_create (np,NULL , platform_bus_type.dev_root); +if (!pdev) +return -ENODEV ; + + smmu =platform_get_drvdata (pdev); +of_iommu_set_ops (np, &arm_smmu_ops); + +return 0 ; +} +IOMMU_OF_DECLARE (arm_smmuv1,"arm,smmu-v1" , arm_smmu_of_init); +IOMMU_OF_DECLARE (arm_smmuv2,"arm,smmu-v2" , arm_smmu_of_init); +IOMMU_OF_DECLARE (arm_mmu400,"arm,mmu-400" , arm_smmu_of_init); +IOMMU_OF_DECLARE(arm_mmu401, "arm,mmu-401",
Quoted 4 lines
arm_smmu_of_init);
+IOMMU_OF_DECLARE(arm_mmu500, "arm,mmu-500",arm_smmu_of_init);
+
Thanks for this series. I am going to use and test this. Also I wanted to ask about the iommu probe deferral series [1] to avoid early device
registration and wanted know the direction on that ?
[1] http://lkml.iu.edu/hypermail/linux/kernel/1505.3/03280.html
Regards,
Sricharan
Robin Murphy
reviewerPATCH 11/12 iommu/arm-smmu: Generic IOMMU DT bindings support
Quoted 3 lines · Sricharan
Hi Robin,
Quoted 1 line
-----Original Message-----
[...]
Quoted 24 lines
Quoted 24 lines
drivers/iommu/arm-smmu.c +9
-0+static int __init arm_smmu_of_init (struct device_node *np) { +struct arm_smmu_device *smmu; +struct platform_device *pdev; +int ret =arm_smmu_init (); + +if (ret) +return ret; + + pdev = of_platform_device_create(np, NULL,platform_bus_type.dev_root);
drivers/iommu/arm-smmu.c +9
-0+if (!pdev) +return -ENODEV ; + + smmu =platform_get_drvdata (pdev); +of_iommu_set_ops (np, &arm_smmu_ops); + +return 0 ; +} +IOMMU_OF_DECLARE(arm_smmuv1, "arm,smmu-v1",
arm_smmu_of_init);
+IOMMU_OF_DECLARE(arm_smmuv2, "arm,smmu-v2",arm_smmu_of_init);
+IOMMU_OF_DECLARE(arm_mmu400, "arm,mmu-400",arm_smmu_of_init);
Quoted 8 lines
Quoted 5 lines
+IOMMU_OF_DECLARE(arm_mmu401, "arm,mmu-401",
arm_smmu_of_init);
+IOMMU_OF_DECLARE(arm_mmu500, "arm,mmu-500",arm_smmu_of_init);
+Thanks for this series. I am going to use and test this. Also I wanted to ask about the iommu probe deferral series [1] to avoid early device
registration and wanted know the direction on that ?
It's certainly on my near-term to-do list to revisit. I recall running into problems with that series if the IOMMU was ready but the device itself then requested probe deferral, and I have vague memories of thinking more needed to be done generally around the failure/device teardown path too. I also had high hopes for the on-demand device probing series from around the same time[2], which would have helped simplify things quite a bit, but that also seems to have died after a brief stint breaking things in -next.
Anyway, Marc reckons that we also have the exact same probe-dependency problem for things like IRQ-MSI bridges, so I'll be looking into a more general solution at some point unless anyone wants to beat me to it ;)
Thanks,
Robin.
[2]:http://thread.gmane.org/gmane.linux.acpi.devel/78833
Quoted 12 lines
[1] http://lkml.iu.edu/hypermail/linux/kernel/1505.3/03280.html
Regards,
Sricharan_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Robin Murphy
reviewerPATCH 12/12 iommu/arm-smmu: Group platform devices appropriately
In reply to Robin MurphyIOMMU groups are necessary to represent multiple devices which the IOMMU cannot tell apart; if two platform devices are wired up with the same stream ID, then the current approach of blindly placing every device in its own group makes it possible for each device to end up attached to a different domain. This is a Very Bad Thing under the SMMU architecture, where mapping a stream ID to more than one context leads to various unpleasant behaviours.
In the absence of a suitable common abstraction (which is difficult due to the implementation-specific nature of platform device IDs), implement a simple lookup table within the SMMU driver to ensure that any devices with overlapping stream IDs end up in the same group as they should.
--- drivers/iommu/arm-smmu.c | 61 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 11 deletions(-)
drivers/iommu/arm-smmu.c +50 -11
@@ -333,6 +333,7 @@ struct arm_smmu_device {u32 num_mapping_groups;DECLARE_BITMAP (smr_map,ARM_SMMU_MAX_SMRS ); +struct iommu_group **group_lut;unsigned long va_size;unsigned long ipa_size;
@@ -1272,11 +1273,6 @@ static int arm_smmu_add_device(struct device *dev)return ret; } -/* -* For now, assume that the default group allocators suffice. -* We might have to do some preparatory work here to properly -* handle multiple devices sharing stream IDs. -*/ group =iommu_group_get_for_dev (dev);if (IS_ERR (group))return PTR_ERR (group);
@@ -1297,6 +1293,45 @@ static void __arm_smmu_release_iommudata(void *data)kfree (data); } +static struct iommu_group *arm_smmu_group_lookup (struct device *dev) +{ +struct arm_smmu_master_cfg *cfg = dev->archdata.iommu; +struct arm_smmu_device *smmu = cfg->smmu; +struct iommu_group **lut = smmu->group_lut; +struct iommu_group *group =NULL ; +int i; + +if (!lut) { +/* +* Unfortunately this has to be sized for the worst-case until +* we get even cleverer with stream ID management. +*/ + lut =devm_kcalloc (smmu->dev,SMR_ID_MASK +1 , +sizeof (*lut),GFP_KERNEL ); +if (lut) + smmu->group_lut = lut; +else + group =ERR_PTR (-ENOMEM ); + }else { +/* Check for platform or cross-bus aliases */ +for (i =0 ; i < cfg->num_streamids; i++) { +struct iommu_group *tmp = lut[cfg->streamids[i].id]; + +if (!tmp) +continue ; + +if (group && tmp != group) { +dev_err (smmu->dev, +"Cannot handle master %s aliasing multiple groups\n" , +dev_name (dev)); +return ERR_PTR (-EBUSY ); + } + group = tmp; + } + } +return group; +} +static inline bool __streamid_match_sme (struct arm_smmu_streamid *sid,struct arm_smmu_stream_map_entry *sme) {
@@ -1306,22 +1341,24 @@ static inline bool __streamid_match_sme(struct arm_smmu_streamid *sid,static struct iommu_group *arm_smmu_device_group (struct device *dev) { -struct arm_smmu_master_cfg *master_cfg; +struct arm_smmu_master_cfg *master_cfg = dev->archdata.iommu;struct arm_smmu_group_cfg *group_cfg;struct arm_smmu_streamid *sids;struct arm_smmu_stream_map_entry *smes;struct iommu_group *group;int i, j; -if (dev_is_pci (dev)) - group =pci_device_group (dev); -else - group =generic_device_group (dev); + group =arm_smmu_group_lookup (dev); +if (!group) { +if (dev_is_pci (dev)) + group =pci_device_group (dev); +else + group =generic_device_group (dev); + }if (IS_ERR (group))return group; - master_cfg = dev->archdata.iommu; group_cfg =iommu_group_get_iommudata (group);if (!group_cfg) { group_cfg =kzalloc (sizeof (*group_cfg),GFP_KERNEL );
@@ -1336,6 +1373,8 @@ static struct iommu_group *arm_smmu_device_group(struct device *dev) sids = master_cfg->streamids; smes = group_cfg->smes;for (i =0 ; i < master_cfg->num_streamids; i++) { + master_cfg->smmu->group_lut[sids[i].id] = group; +for (j =0 ; j < group_cfg->num_smes; j++) {if (__streamid_match_sme (&sids[i], &smes[j])) { sids[i].s2cr_idx =STREAM_MULTIPLE ;
Signature · 1 line
2.7.2.333.g70bd996.dirty