]> xenbits.xensource.com Git - people/julieng/linux-arm.git/commitdiff
ARM64, ACPI, PCI, MSI: I/O Remapping Table (IORT) initial support.
authorTomasz Nowicki <tn@semihalf.com>
Wed, 29 Jul 2015 10:17:33 +0000 (12:17 +0200)
committerJulien Grall <julien.grall@citrix.com>
Mon, 28 Sep 2015 11:05:22 +0000 (12:05 +0100)
IORT shows representation of IO topology that will be used by
ARM based systems. It describes how various components are connected
together e.g. which devices are connected to given ITS instance.

This patch implements calls which allow to:
- register/remove ITS as MSI chip
- parse all IORT nodes and form node tree (for easy lookup)
- find ITS (MSI chip) that device is assigned to

Signed-off-by: Tomasz Nowicki <tomasz.nowicki@linaro.org>
Signed-off-by: Hanjun Guo <hanjun.guo@linaro.org>
Signed-off-by: Robert Richter <rrichter@cavium.com>
Signed-off-by: Vadim Lomovtsev <Vadim.Lomovtsev@caviumnetworks.com>
arch/arm64/kernel/pci-acpi.c
drivers/acpi/Kconfig
drivers/acpi/Makefile
drivers/acpi/iort.c [new file with mode: 0644]
drivers/irqchip/Kconfig
drivers/irqchip/irq-gic-v3-its.c
include/linux/iort.h [new file with mode: 0644]

index 517d570afb1316def12955b5ee7fcb38f6b64281..5bbfbfb4e77fe75f7e04c78ac29d1398a2f7dff8 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/acpi.h>
 #include <linux/init.h>
 #include <linux/io.h>
+#include <linux/iort.h>
 #include <linux/kernel.h>
 #include <linux/mm.h>
 #include <linux/mmconfig.h>
@@ -352,6 +353,7 @@ struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
                __release_pci_root_info(info);
                return NULL;
        }
+       pbus->msi = iort_find_pci_msi_chip(domain, 0);
 
        pci_set_host_bridge_release(to_pci_host_bridge(pbus->bridge),
                        release_pci_root_info, info);
index 70ba3ef9a37b9e004db120577abd0eab961c13d7..573cc6e7dc3d4ba72cc47fafbc795b85e7f05204 100644 (file)
@@ -57,6 +57,9 @@ config ACPI_SYSTEM_POWER_STATES_SUPPORT
 config ACPI_CCA_REQUIRED
        bool
 
+config IORT_TABLE
+       bool
+
 config ACPI_SLEEP
        bool
        depends on SUSPEND || HIBERNATION
index e32b8cd125026a2fc266ef78d8274d5fb0ed5932..d1d1e7ab439922a6bf0f822e51f6ee9f730aba12 100644 (file)
@@ -79,6 +79,7 @@ obj-$(CONFIG_ACPI_HED)                += hed.o
 obj-$(CONFIG_ACPI_EC_DEBUGFS)  += ec_sys.o
 obj-$(CONFIG_ACPI_CUSTOM_METHOD)+= custom_method.o
 obj-$(CONFIG_ACPI_BGRT)                += bgrt.o
+obj-$(CONFIG_IORT_TABLE)       += iort.o
 
 # processor has its own "processor." module_param namespace
 processor-y                    := processor_driver.o processor_throttling.o
diff --git a/drivers/acpi/iort.c b/drivers/acpi/iort.c
new file mode 100644 (file)
index 0000000..72240c6
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015, Linaro Ltd.
+ *     Author: Tomasz Nowicki <tomasz.nowicki@linaro.org>
+ *     Author: Hanjun Guo <hanjun.guo@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * This file implements early detection/parsing of I/O mapping
+ * 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.
+ */
+
+#define pr_fmt(fmt)    "ACPI: IORT: " fmt
+
+#include <linux/acpi.h>
+#include <linux/export.h>
+#include <linux/iort.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+struct iort_its_msi_chip {
+       struct list_head        list;
+       struct msi_controller   *chip;
+       u32                     id;
+};
+
+typedef acpi_status (*iort_find_node_callback)
+       (struct acpi_iort_node *node, void *context);
+
+/* pointer to the mapped IORT table */
+static struct acpi_table_header *iort_table;
+static LIST_HEAD(iort_pci_msi_chip_list);
+static DEFINE_MUTEX(iort_pci_msi_chip_mutex);
+
+int iort_pci_msi_chip_add(struct msi_controller *chip, u32 its_id)
+{
+       struct iort_its_msi_chip *its_msi_chip;
+
+       its_msi_chip = kzalloc(sizeof(*its_msi_chip), GFP_KERNEL);
+       if (!its_msi_chip)
+               return -ENOMEM;
+
+       its_msi_chip->chip = chip;
+       its_msi_chip->id = its_id;
+
+       mutex_lock(&iort_pci_msi_chip_mutex);
+       list_add(&its_msi_chip->list, &iort_pci_msi_chip_list);
+       mutex_unlock(&iort_pci_msi_chip_mutex);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(iort_pci_msi_chip_add);
+
+void iort_pci_msi_chip_remove(struct msi_controller *chip)
+{
+       struct iort_its_msi_chip *its_msi_chip, *t;
+
+       mutex_lock(&iort_pci_msi_chip_mutex);
+       list_for_each_entry_safe(its_msi_chip, t, &iort_pci_msi_chip_list, list) {
+               if (its_msi_chip->chip == chip) {
+                       list_del(&its_msi_chip->list);
+                       kfree(its_msi_chip);
+                       break;
+               }
+       }
+       mutex_unlock(&iort_pci_msi_chip_mutex);
+}
+EXPORT_SYMBOL_GPL(iort_pci_msi_chip_remove);
+
+static struct msi_controller *iort_pci_find_msi_chip_by_its(u32 its_id)
+{
+       struct iort_its_msi_chip *its_msi_chip;
+
+       mutex_lock(&iort_pci_msi_chip_mutex);
+       list_for_each_entry(its_msi_chip, &iort_pci_msi_chip_list, list) {
+               if (its_msi_chip->id == its_id) {
+                       mutex_unlock(&iort_pci_msi_chip_mutex);
+                       return its_msi_chip->chip;
+               }
+       }
+       mutex_unlock(&iort_pci_msi_chip_mutex);
+
+       return NULL;
+}
+
+/**
+ * iort_find_root_its_node() - Get PCI root complex, device, or SMMU's
+ * parent ITS node.
+ * @node: node pointer to PCI root complex, device, or SMMU
+ *
+ * Returns: parent ITS node pointer on success
+ *         NULL on failure
+ */
+static struct acpi_iort_node *
+iort_find_parent_its_node(struct acpi_iort_node *node)
+{
+       struct acpi_iort_id_mapping *id_map;
+
+       if (!node)
+               return NULL;
+
+       /* Go upstream until find its parent ITS node */
+       while (node->type != ACPI_IORT_NODE_ITS_GROUP) {
+               /* TODO: handle multi ID mapping entries */
+               id_map = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node,
+                                     node->mapping_offset);
+
+               /* Firmware bug! */
+               if (!id_map->output_reference) {
+                       pr_err(FW_BUG "[node %p type %d] ID map has invalid parent reference\n",
+                              node, node->type);
+                       return NULL;
+               }
+
+               /* TODO: Components that do not generate MSIs but are connected to an SMMU */
+               node = ACPI_ADD_PTR(struct acpi_iort_node, iort_table,
+                                   id_map->output_reference);
+       }
+
+       return node;
+}
+
+/**
+ * iort_scan_node() - scan the IORT and call the handler with specific
+ * IORT node type
+ * @type: IORT node type
+ * @callback: callback with specific node type
+ * @context: context pass to the callback
+ *
+ * Returns: node pointer when the callback succeed
+ *         NULL on failure
+ */
+static struct acpi_iort_node *
+iort_scan_node(enum acpi_iort_node_type type,
+              iort_find_node_callback callback, void *context)
+{
+       struct acpi_iort_node *iort_node, *iort_end;
+       struct acpi_table_iort *iort;
+       int i;
+
+       if (!iort_table)
+               return NULL;
+
+       /*
+        * iort_table and iort both point to the start of IORT table, but
+        * have different struct types
+        */
+       iort = container_of(iort_table, struct acpi_table_iort, header);
+
+       /* Get the first iort node */
+       iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort,
+                                iort->node_offset);
+
+       /* pointer to the end of the table */
+       iort_end = ACPI_ADD_PTR(struct acpi_iort_node, iort_table,
+                               iort_table->length);
+
+       for (i = 0; i < iort->node_count; i++) {
+               if (iort_node >= iort_end) {
+                       pr_err("iort node pointer overflows, bad table\n");
+                       return NULL;
+               }
+
+               if (iort_node->type == type) {
+                       if (ACPI_SUCCESS(callback(iort_node, context)))
+                               return iort_node;
+               }
+
+               iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort_node,
+                                        iort_node->length);
+       }
+
+       return NULL;
+}
+
+static acpi_status
+iort_find_pci_rc_callback(struct acpi_iort_node *node, void *context)
+{
+       struct acpi_iort_root_complex *pci_rc;
+       int segment = *(int *)context;
+
+       pci_rc = (struct acpi_iort_root_complex *)node->node_data;
+
+       /*
+        * It is assumed that PCI segment numbers have a one-to-one mapping
+        * with root complexes. Each segment number can represent only one
+        * root complex.
+        */
+       if (pci_rc->pci_segment_number == segment)
+               return AE_OK;
+
+       return AE_NOT_FOUND;
+}
+
+/**
+ * iort_find_pci_msi_chip() - find the msi controller with root complex's
+ * segment number
+ * @segment: domain number of this pci root complex
+ * @idx: index of the ITS in the ITS group
+ *
+ * Returns: msi controller bind to the root complex with the segment
+ *         NULL on failure
+ */
+struct msi_controller *iort_find_pci_msi_chip(int segment, unsigned int idx)
+{
+       struct acpi_iort_its_group *its;
+       struct acpi_iort_node *node;
+       struct msi_controller *msi_chip;
+
+       node = iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
+                             iort_find_pci_rc_callback, &segment);
+       if (!node) {
+               pr_err("can't find node related to PCI host bridge [segment %d]\n",
+                      segment);
+               return NULL;
+       }
+
+       node = iort_find_parent_its_node(node);
+       if (!node) {
+               pr_err("can't find ITS parent node for PCI host bridge [segment %d]\n",
+                      segment);
+               return NULL;
+       }
+
+       /* Move to ITS specific data */
+       its = (struct acpi_iort_its_group *)node->node_data;
+       if (idx > its->its_count) {
+               pr_err("requested ITS ID index [%d] is greater than available ITS count [%d]\n",
+                      idx, its->its_count);
+               return NULL;
+       }
+
+       msi_chip = iort_pci_find_msi_chip_by_its(its->identifiers[idx]);
+       if (!msi_chip)
+               pr_err("can not find ITS chip ID:%d, not registered\n",
+                      its->identifiers[idx]);
+
+       return msi_chip;
+}
+EXPORT_SYMBOL_GPL(iort_find_pci_msi_chip);
+
+/* Get the remapped IORT table */
+static int __init iort_table_detect(void)
+{
+       acpi_status status;
+
+       if (acpi_disabled)
+               return -ENODEV;
+
+       status = acpi_get_table(ACPI_SIG_IORT, 0, &iort_table);
+       if (ACPI_FAILURE(status)) {
+               const char *msg = acpi_format_exception(status);
+               pr_err("Failed to get table, %s\n", msg);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+arch_initcall(iort_table_detect);
index 120d81543e53254ea725ff845631edc667dbdb07..9abf682fa4b83fcaed1b712b68d3e9ebfdb11646 100644 (file)
@@ -26,6 +26,7 @@ config ARM_GIC_V3
 config ARM_GIC_V3_ITS
        bool
        select PCI_MSI_IRQ_DOMAIN
+       select IORT_TABLE if (ACPI && PCI_MSI)
 
 config ARM_NVIC
        bool
index 4b9ddb56570554ff540c7ad7b9563d513c6cd48f..3073fc90702a9b6b3e072797b6612981c1bf1558 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/cpu.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
+#include <linux/iort.h>
 #include <linux/log2.h>
 #include <linux/mm.h>
 #include <linux/msi.h>
@@ -1698,15 +1699,18 @@ static int __init
 gic_acpi_parse_madt_its(struct acpi_subtable_header *header,
                        const unsigned long end)
 {
-       struct acpi_madt_generic_translator *its;
+       struct acpi_madt_generic_translator *its_table;
+       struct its_node *its;
 
        if (BAD_MADT_ENTRY(header, end))
                return -EINVAL;
 
-       its = (struct acpi_madt_generic_translator *)header;
+       its_table = (struct acpi_madt_generic_translator *)header;
 
-       pr_info("ITS: ID: 0x%x\n", its->translation_id);
-       its_probe(its->base_address, 2 * SZ_64K);
+       pr_info("ITS: ID: 0x%x\n", its_table->translation_id);
+       its = its_probe(its_table->base_address, 2 * SZ_64K);
+       if (!its_init_domain(NULL, its))
+               iort_pci_msi_chip_add(&its->msi_chip, its_table->translation_id);
        return 0;
 }
 
diff --git a/include/linux/iort.h b/include/linux/iort.h
new file mode 100644 (file)
index 0000000..7b83a03
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015, Linaro Ltd.
+ *     Author: Tomasz Nowicki <tomasz.nowicki@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef __IORT_H__
+#define __IORT_H__
+
+struct msi_controller;
+
+#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);
+#else
+static inline int
+iort_pci_msi_chip_add(struct msi_controller *chip, u32 its_id) { return -ENODEV; }
+
+static inline void
+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; }
+#endif
+
+#endif /* __IORT_H__ */