[Arm-dev] [PATCH v1 85/87] ARM64, ACPI, PCI, MSI: I/O Remapping Table (IORT) initial support.

Thu Aug 13 13:19:22 UTC 2015
Vadim Lomovtsev <Vadim.Lomovtsev at caviumnetworks.com>

From: Tomasz Nowicki <tn at semihalf.com>

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 at linaro.org>
Signed-off-by: Hanjun Guo <hanjun.guo at linaro.org>
Signed-off-by: Robert Richter <rrichter at cavium.com>
Signed-off-by: Vadim Lomovtsev <Vadim.Lomovtsev at caviumnetworks.com>
---
 arch/arm64/kernel/pci-acpi.c     |   2 +
 drivers/acpi/Kconfig             |   3 +
 drivers/acpi/Makefile            |   1 +
 drivers/acpi/iort.c              | 272 +++++++++++++++++++++++++++++++++++++++
 drivers/irqchip/Kconfig          |   1 +
 drivers/irqchip/irq-gic-v3-its.c |  12 +-
 include/linux/iort.h             |  39 ++++++
 7 files changed, 326 insertions(+), 4 deletions(-)
 create mode 100644 drivers/acpi/iort.c
 create mode 100644 include/linux/iort.h

diff --git a/arch/arm64/kernel/pci-acpi.c b/arch/arm64/kernel/pci-acpi.c
index 517d570..5bbfbfb 100644
--- a/arch/arm64/kernel/pci-acpi.c
+++ b/arch/arm64/kernel/pci-acpi.c
@@ -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);
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 114cf48..bd9204b 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -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
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index e32b8cd..d1d1e7a 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -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
index 0000000..72240c6
--- /dev/null
+++ b/drivers/acpi/iort.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015, Linaro Ltd.
+ *	Author: Tomasz Nowicki <tomasz.nowicki at linaro.org>
+ *	Author: Hanjun Guo <hanjun.guo at 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);
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 120d815..9abf682 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -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
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index 4814954..3d47eb0 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -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>
@@ -1658,15 +1659,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
index 0000000..7b83a03
--- /dev/null
+++ b/include/linux/iort.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015, Linaro Ltd.
+ *	Author: Tomasz Nowicki <tomasz.nowicki at 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__ */
-- 
2.4.3