From: Tomasz Nowicki tomasz.nowicki@linaro.org
This patch first introduce some helper functions in iort.c, these helpers allow to walk up and down on IORT table and find out several SMMU dependencies, properties, topology of IO devices.
After that, using the information presented in SMMU iort node to init SMMU, since SMMU is fully described using static information in IORT table node in contrast to DSDT node which means platform device needs to be created manually. It happens during IORT initialization so that its driver can probe and get resources in normal way. Any fancy SMMU properties are stored in private platform device structure.
Till now driver has bound via DT so this patch is going to extract common code and move DT and ACPI specific code to corresponding functions.
Signed-off-by: Tomasz Nowicki tomasz.nowicki@linaro.org Signed-off-by: Hanjun Guo hanjun.guo@linaro.org --- drivers/acpi/iort.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++- drivers/iommu/arm-smmu.c | 206 ++++++++++++++++++++++++++++++++---- include/linux/iort.h | 24 +++++ 3 files changed, 474 insertions(+), 24 deletions(-)
diff --git a/drivers/acpi/iort.c b/drivers/acpi/iort.c index 01692bb..eaad01d 100644 --- a/drivers/acpi/iort.c +++ b/drivers/acpi/iort.c @@ -16,7 +16,7 @@ * reported to OS through firmware via I/O Remapping Table (IORT) * IORT document number: ARM DEN 0049A * - * These routines are used by ITS and PCI host bridge drivers. + * These routines are used by ITS, PCI host bridge and SMMU drivers. */
#define pr_fmt(fmt) "ACPI: IORT: " fmt @@ -29,6 +29,8 @@ #include <linux/module.h> #include <linux/msi.h> #include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/platform_device.h> #include <linux/slab.h>
struct iort_its_msi_chip { @@ -37,6 +39,13 @@ struct iort_its_msi_chip { u32 id; };
+struct iort_priv_ctx { + struct acpi_iort_node *parent; + unsigned int index; +}; + +#define IORT_NODE_TYPE_ANY (-1) + typedef acpi_status (*iort_find_node_callback) (struct acpi_iort_node *node, void *context);
@@ -174,7 +183,8 @@ iort_scan_node(enum acpi_iort_node_type type, return NULL; }
- if (iort_node->type == type) { + if (iort_node->type == type || + iort_node->type == IORT_NODE_TYPE_ANY) { if (ACPI_SUCCESS(callback(iort_node, context))) return iort_node; } @@ -252,6 +262,257 @@ struct msi_controller *iort_find_pci_msi_chip(int segment, unsigned int idx) } EXPORT_SYMBOL_GPL(iort_find_pci_msi_chip);
+static acpi_status +iort_find_node_idx_callback(struct acpi_iort_node *node, + void *context) +{ + unsigned int *count = context; + + if ((*count)--) + return AE_NOT_FOUND; + + return AE_OK; +} + +static struct acpi_iort_node * +iort_find_node(enum acpi_iort_node_type type, unsigned int idx) +{ + unsigned int count = idx; + + if (!iort_table) + return NULL; + return iort_scan_node(type, iort_find_node_idx_callback, &count); +} + +static acpi_status +iort_find_children_idx_callback(struct acpi_iort_node *node, void *context) +{ + struct iort_priv_ctx *info = context; + struct acpi_iort_id_mapping *id; + struct acpi_iort_node *parent; + int i, found = 0; + + /* Move to ID section */ + id = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node, + node->mapping_offset); + for (i = 0; i < node->mapping_count; i++) { + parent = ACPI_ADD_PTR(struct acpi_iort_node, iort_table, + id->output_reference); + if (parent == info->parent) { + found = 1; + break; + } + id++; + } + + if (!found || info->index--) + return AE_NOT_FOUND; + + return AE_OK; +} + +struct acpi_iort_node * +iort_find_node_children(struct acpi_iort_node *parent, + unsigned int idx) +{ + struct iort_priv_ctx info; + + info.parent = parent; + info.index = idx; + + return iort_scan_node(IORT_NODE_TYPE_ANY, + iort_find_children_idx_callback, &info); +} +EXPORT_SYMBOL_GPL(iort_find_node_children); + +int +iort_find_endpoint_id(struct acpi_iort_node *node, u32 *streamids) +{ + struct acpi_iort_id_mapping *id; + int i, num_streamids = 0; + + /* Move to ID section */ + id = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node, + node->mapping_offset); + /* Hunt for endpoint ID map */ + for (i = 0; i < node->mapping_count && + i < (sizeof(streamids) / sizeof(*streamids)); i++) + if (id[i].flags & ACPI_IORT_ID_SINGLE_MAPPING) + streamids[num_streamids++] = id[i].output_base; + + return num_streamids; +} +EXPORT_SYMBOL_GPL(iort_find_endpoint_id); + +int +iort_map_pcidev_to_streamid(struct pci_dev *pdev, u32 req_id, u32 *stream_id) +{ + struct acpi_iort_node *node; + struct acpi_iort_id_mapping *id; + int i; + int segment = pci_domain_nr(pdev->bus); + + node = iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX, + iort_find_pci_rc_callback, &segment); + if (!node) { + pr_err("can not find node related to PCI host bridge [segment %d]\n", + segment); + return -ENODEV; + } + + /* Move to ID section */ + id = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node, + node->mapping_offset); + + /* Look for request ID to stream ID map */ + for (i = 0; i < node->mapping_count; i++, id++) { + if (id->flags & ACPI_IORT_ID_SINGLE_MAPPING) + continue; + + if (req_id < id->input_base || + (req_id > id->input_base + id->id_count)) + continue; + + *stream_id = id->output_base + (req_id - id->input_base); + return 0; + } + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(iort_map_pcidev_to_streamid); + +static acpi_status +match_segment(acpi_handle handle, u32 lvl, void *context, void **ret_val) +{ + int *segment = context; + struct acpi_pci_root *root; + struct acpi_device *adev; + struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; + int err; + + if (!acpi_is_root_bridge(handle)) + return AE_OK; + + root = acpi_pci_find_root(handle); + if (!root) + return AE_OK; + + if (root->segment != *segment) + return AE_OK; + + err = acpi_bus_get_device(handle, &adev); + if (err) { + if (ACPI_FAILURE(acpi_get_name(handle, ACPI_FULL_PATHNAME, &string))) + pr_warn("Invalid link device, error %d\n", err); + else { + pr_warn("Invalid link for %s device\n", + (char *)string.pointer); + kfree(string.pointer); + } + return AE_OK; + } + + *ret_val = &adev->dev; + return AE_CTRL_TERMINATE; +} + +struct device * +iort_find_node_device(struct acpi_iort_node *node) +{ + struct acpi_iort_named_component *acpi_dev; + struct acpi_iort_root_complex *pci_rc; + struct acpi_device *adev; + struct device *device = NULL; + acpi_handle handle; + char *device_path; + int segment; + + switch (node->type) { + case ACPI_IORT_NODE_NAMED_COMPONENT: + acpi_dev = (struct acpi_iort_named_component *)node->node_data; + + device_path = acpi_dev->device_name; + if (ACPI_FAILURE(acpi_get_handle(ACPI_ROOT_OBJECT, device_path, + &handle))) { + pr_warn("Failed to find handle for ACPI object %s\n", + device_path); + break; + } + + if (acpi_bus_get_device(handle, &adev)) { + pr_warn("Failed to get device for ACPI object %s\n", + device_path); + break; + } + + device = &adev->dev; + break; + case ACPI_IORT_NODE_PCI_ROOT_COMPLEX: + pci_rc = (struct acpi_iort_root_complex *)node->node_data; + segment = pci_rc->pci_segment_number; + + /* fix me: also need to check PNP0A08 */ + acpi_get_devices("PNP0A03", match_segment, &segment, (void **)&device); + break; + default: + pr_err("can not find device for node type %d\n", node->type); + return NULL; + } + + return device; +} +EXPORT_SYMBOL_GPL(iort_find_node_device); + +static void iort_add_smmu_platform_device(struct acpi_iort_node *node) +{ + struct acpi_iort_smmu *smmu; + struct platform_device_info pdevinfo; + struct platform_device *pdev = NULL; + struct resource resources; + + /* Move to SMMU1/2 specific data */ + smmu = (struct acpi_iort_smmu *)node->node_data; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = NULL; + pdevinfo.name = "arm-smmu"; + pdevinfo.id = PLATFORM_DEVID_AUTO; + + memset(&resources, 0, sizeof(resources)); + resources.start = smmu->base_address; + resources.end = smmu->base_address + smmu->span - 1; + resources.flags = IORESOURCE_MEM; + + pdevinfo.res = &resources; + pdevinfo.num_res = 1; + + pdevinfo.data = &node; + pdevinfo.size_data = sizeof(node); + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) + pr_err("platform device creation failed: %ld\n", + PTR_ERR(pdev)); + else + pr_debug("Platform device arm-smmu created\n"); +} + +static int iort_add_smmu_devices(void) +{ + struct acpi_iort_node *iort_node = NULL; + unsigned int idx = 0; + + while (1) { + iort_node = iort_find_node(ACPI_IORT_NODE_SMMU, idx++); + if (!iort_node) + continue; + + iort_add_smmu_platform_device(iort_node); + } + + return 0; +} + /* Get the remapped IORT table */ static int __init iort_table_detect(void) { @@ -267,6 +528,9 @@ static int __init iort_table_detect(void) return -EINVAL; }
+ if(IS_ENABLED(CONFIG_ARM_SMMU)) + iort_add_smmu_devices(); + return 0; } arch_initcall(iort_table_detect); diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 55021ef..85946f5 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -28,6 +28,7 @@
#define pr_fmt(fmt) "arm-smmu: " fmt
+#include <linux/acpi.h> #include <linux/delay.h> #include <linux/dma-mapping.h> #include <linux/err.h> @@ -35,10 +36,13 @@ #include <linux/io.h> #include <linux/iommu.h> #include <linux/iopoll.h> +#include <linux/iort.h> +#include <linux/mm.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/pci.h> +#include <linux/pci-acpi.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/spinlock.h> @@ -384,6 +388,7 @@ static void parse_driver_options(struct arm_smmu_device *smmu) static struct device *dev_get_dev_node(struct device *dev) { struct pci_bus *bus; + struct device *device;
if (!dev_is_pci(dev)) return dev; @@ -393,7 +398,26 @@ static struct device *dev_get_dev_node(struct device *dev) while (!pci_is_root_bus(bus)) bus = bus->parent;
- return bus->bridge->parent; + device = bus->bridge->parent; + if (device) + return device; + +#ifdef CONFIG_ACPI + /* + * ACPI host bridge device is going to be found via ACPI companion + * device + */ + if (!acpi_disabled) { + struct acpi_device *adev; + + if (!acpi_bus_get_device(ACPI_HANDLE(bus->bridge), &adev)) + return &adev->dev; + } +#endif + + pr_warn("Failed to get PCI host bridge device for %s device\n", + dev_name(dev)); + return NULL; }
static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu, @@ -1341,6 +1365,27 @@ static int __arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data) return 0; /* Continue walking */ }
+static int arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data) +{ + u32 *stream_id = data; + + /* + * We need a way to describe the ID mappings in FDT. + * Assume Stream ID == Requester ID for now. + * + * ACPI is using IORT ID translation map. Each SMMU is represented by + * corresponding SMMU node and its children underneath. + * Children are referring to parent's ID map looking for appropriate + * translation rule. + */ + + /* include domain number with pci device id */ + if (acpi_disabled) + return __arm_smmu_get_pci_sid(pdev, alias, data); + + return iort_map_pcidev_to_streamid(pdev, alias, stream_id); +} + static void __arm_smmu_release_pci_iommudata(void *data) { kfree(data); @@ -1374,11 +1419,7 @@ static int arm_smmu_add_pci_device(struct pci_dev *pdev) goto out_put_group; }
- /* - * 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); + 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; @@ -1713,6 +1754,104 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return 0; }
+#ifdef CONFIG_ACPI +static int arm_smmu_acpi_probe(struct platform_device *pdev, + struct arm_smmu_device *smmu) +{ + struct device *dev = smmu->dev; + struct acpi_iort_node *node = + *(struct acpi_iort_node **)dev_get_platdata(dev); + struct acpi_iort_smmu *iort_smmu; + int num_irqs, i, err, trigger; + u64 *ctx_irq, *glb_irq; + + /* Move to SMMU1/2 specific data */ + iort_smmu = (struct acpi_iort_smmu *)node->node_data; + + smmu->version = (enum arm_smmu_arch_version)iort_smmu->model + 1; + + /* we may have one or two globle irqs, SMMU_NSgIrpt and probaly SMMU_NSgCfgIrpt */ + glb_irq = ACPI_ADD_PTR(u64, node, iort_smmu->global_interrupt_offset); + if (!IORT_IRQ_MASK(glb_irq[1])) /* 0 means not implemented */ + smmu->num_global_irqs = 1; + else + smmu->num_global_irqs = 2; + + smmu->num_context_irqs = iort_smmu->context_interrupt_count; + num_irqs = smmu->num_context_irqs + smmu->num_global_irqs; + + if (!smmu->num_context_irqs) { + dev_err(dev, "found %d interrupts but expected at least %d\n", + num_irqs, smmu->num_global_irqs + 1); + return -ENODEV; + } + + smmu->irqs = devm_kzalloc(dev, sizeof(*smmu->irqs) * num_irqs, + GFP_KERNEL); + if (!smmu->irqs) { + dev_err(dev, "failed to allocate %d irqs\n", num_irqs); + return -ENOMEM; + } + + for (i = 0; i < smmu->num_global_irqs; i++) { + int hw_irq = IORT_IRQ_MASK(glb_irq[i]); + trigger = IORT_IRQ_TRIGGER_MASK(glb_irq[i]); + smmu->irqs[0] = acpi_register_gsi(NULL, hw_irq, trigger, + ACPI_ACTIVE_HIGH); + } + + /* Time for context IRQs */ + ctx_irq = ACPI_ADD_PTR(u64, node, iort_smmu->context_interrupt_offset); + for (; i < smmu->num_context_irqs + smmu->num_global_irqs; i++) { + int hw_irq = IORT_IRQ_MASK(ctx_irq[i]); + trigger = IORT_IRQ_TRIGGER_MASK(ctx_irq[i]); + smmu->irqs[i + 1] = acpi_register_gsi(NULL, hw_irq, trigger, + ACPI_ACTIVE_HIGH); + } + + err = arm_smmu_device_cfg_probe(smmu); + if (err) + return err; + + i = 0; + smmu->masters = RB_ROOT; + while (1) { + struct acpi_iort_node *child; + uint32_t streamids[MAX_MASTER_STREAMIDS]; + struct device *child_dev; + int num_streamids; + + child = iort_find_node_children(node, i); + if (!child) + break; + + child_dev = iort_find_node_device(child); + if (!child_dev) + break; + + num_streamids = iort_find_endpoint_id(child, streamids); + err = register_smmu_master(smmu, dev, child_dev, streamids, + num_streamids); + if (err) { + dev_err(dev, "failed to add master %s\n", + dev_name(child_dev)); + return err; + } + + i++; + } + dev_notice(dev, "registered %d master devices\n", i); + + return 0; +} +#else +static inline int arm_smmu_acpi_probe(struct platform_device *pdev, + struct arm_smmu_device *smmu) +{ + return -ENODEV; +} +#endif + static const struct of_device_id arm_smmu_of_match[] = { { .compatible = "arm,smmu-v1", .data = (void *)ARM_SMMU_V1 }, { .compatible = "arm,smmu-v2", .data = (void *)ARM_SMMU_V2 }, @@ -1723,31 +1862,18 @@ static const struct of_device_id arm_smmu_of_match[] = { }; MODULE_DEVICE_TABLE(of, arm_smmu_of_match);
-static int arm_smmu_device_dt_probe(struct platform_device *pdev) +static int arm_smmu_dt_probe(struct platform_device *pdev, + struct arm_smmu_device *smmu) { const struct of_device_id *of_id; struct resource *res; - struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; struct of_phandle_args masterspec; int num_irqs, i, err;
- smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); - if (!smmu) { - dev_err(dev, "failed to allocate arm_smmu_device\n"); - return -ENOMEM; - } - smmu->dev = dev; - of_id = of_match_node(arm_smmu_of_match, dev->of_node); smmu->version = (enum arm_smmu_arch_version)of_id->data;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - smmu->base = devm_ioremap_resource(dev, res); - if (IS_ERR(smmu->base)) - return PTR_ERR(smmu->base); - smmu->size = resource_size(res); - if (of_property_read_u32(dev->of_node, "#global-interrupts", &smmu->num_global_irqs)) { dev_err(dev, "missing #global-interrupts property\n"); @@ -1813,6 +1939,37 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) } dev_notice(dev, "registered %d master devices\n", i);
+ return 0; +} + +static int arm_smmu_device_probe(struct platform_device *pdev) +{ + struct resource *res; + struct arm_smmu_device *smmu; + struct device *dev = &pdev->dev; + int i, err; + + smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); + if (!smmu) { + dev_err(dev, "failed to allocate arm_smmu_device\n"); + return -ENOMEM; + } + smmu->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + smmu->base = devm_ioremap_resource(dev, res); + if (IS_ERR(smmu->base)) + return PTR_ERR(smmu->base); + smmu->size = resource_size(res); + + if (acpi_disabled) + err = arm_smmu_dt_probe(pdev, smmu); + else + err = arm_smmu_acpi_probe(pdev, smmu); + + if (err) + return err; + parse_driver_options(smmu);
if (smmu->version > ARM_SMMU_V1 && @@ -1886,7 +2043,7 @@ static struct platform_driver arm_smmu_driver = { .name = "arm-smmu", .of_match_table = of_match_ptr(arm_smmu_of_match), }, - .probe = arm_smmu_device_dt_probe, + .probe = arm_smmu_device_probe, .remove = arm_smmu_device_remove, };
@@ -1919,6 +2076,11 @@ static int __init arm_smmu_init(void) bus_set_iommu(&amba_bustype, &arm_smmu_ops); #endif
+#ifdef CONFIG_ACPI + if (!acpi_disabled && !iommu_present(&acpi_bus_type)) + bus_set_iommu(&acpi_bus_type, &arm_smmu_ops); +#endif + #ifdef CONFIG_PCI if (!iommu_present(&pci_bus_type)) bus_set_iommu(&pci_bus_type, &arm_smmu_ops); diff --git a/include/linux/iort.h b/include/linux/iort.h index 7b83a03..f62ae09 100644 --- a/include/linux/iort.h +++ b/include/linux/iort.h @@ -21,10 +21,23 @@
struct msi_controller;
+#define IORT_IRQ_MASK(irq) (irq & 0xffffffffULL) +#define IORT_IRQ_TRIGGER_MASK(irq) ((irq >> 32) & 0xffffffffULL) + #ifdef CONFIG_IORT_TABLE int iort_pci_msi_chip_add(struct msi_controller *chip, u32 its_id); void iort_pci_msi_chip_remove(struct msi_controller *chip); struct msi_controller *iort_find_pci_msi_chip(int segment, unsigned int idx); + +struct acpi_iort_node * +iort_find_node_children(struct acpi_iort_node *parent, unsigned int idx); + +int iort_find_endpoint_id(struct acpi_iort_node *node, u32 *streamids); + +int iort_map_pcidev_to_streamid(struct pci_dev *pdev, u32 req_id, u32 *stream_id); + +struct device * +iort_find_node_device(struct acpi_iort_node *node); #else static inline int iort_pci_msi_chip_add(struct msi_controller *chip, u32 its_id) { return -ENODEV; } @@ -34,6 +47,17 @@ iort_pci_msi_chip_remove(struct msi_controller *chip) { }
static struct msi_controller * iort_find_pci_msi_chip(int segment, unsigned int idx) { return NULL; } +static inline struct acpi_iort_node * +iort_find_node_children(struct acpi_iort_node *parent, + unsigned int idx) { return NULL; } +static inline int +iort_find_endpoint_id(struct acpi_iort_node *node, u32 *streamids) +{ return AE_ERROR; } +static inline int +iort_map_pcidev_to_streamid(struct pci_dev *pdev, u32 req_id, u32 *stream_id) +{ return AE_ERROR; } +static inline struct device * +iort_find_node_device(struct acpi_iort_node *node) { return NULL; } #endif
#endif /* __IORT_H__ */