diff mbox

[V3,07/10] ARM64, ACPI, PCI: I/O Remapping Table (IORT) initial support.

Message ID 1453209083-3358-8-git-send-email-tn@semihalf.com (mailing list archive)
State New, archived
Headers show

Commit Message

Tomasz Nowicki Jan. 19, 2016, 1:11 p.m. UTC
IORT shows representation of IO topology for ARM based systems.
It describes how various components are connected together on
parent-child basis e.g. PCI RC -> SMMU -> ITS.

Initial support allows to:
- register ITS MSI chip along with ITS translation ID and domain token
- find registered domain token based on ITS translation ID
- find registered domain token corresponding to given PCI device
- find PCI device DeviceID based on its RequesterID

Additional features like:
- devices to SMMU binding
- finding platform device DeviceID based on its RequesterID
will be added in next series.

Signed-off-by: Tomasz Nowicki <tn@semihalf.com>
---
 drivers/acpi/Kconfig    |   3 +
 drivers/acpi/Makefile   |   1 +
 drivers/acpi/iort.c     | 326 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/irqchip/Kconfig |   1 +
 include/linux/iort.h    |  38 ++++++
 5 files changed, 369 insertions(+)
 create mode 100644 drivers/acpi/iort.c
 create mode 100644 include/linux/iort.h
diff mbox

Patch

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 5eef4cb..c3664be 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_DEBUGGER
 	bool "AML debugger interface (EXPERIMENTAL)"
 	select ACPI_DEBUG
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 675eaf3..96b3183 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -79,6 +79,7 @@  obj-$(CONFIG_ACPI_EC_DEBUGFS)	+= ec_sys.o
 obj-$(CONFIG_ACPI_CUSTOM_METHOD)+= custom_method.o
 obj-$(CONFIG_ACPI_BGRT)		+= bgrt.o
 obj-$(CONFIG_ACPI_CPPC_LIB)	+= cppc_acpi.o
+obj-$(CONFIG_IORT_TABLE) 	+= iort.o
 
 # processor has its own "processor." module_param namespace
 processor-y			:= processor_driver.o
diff --git a/drivers/acpi/iort.c b/drivers/acpi/iort.c
new file mode 100644
index 0000000..13efc5f
--- /dev/null
+++ b/drivers/acpi/iort.c
@@ -0,0 +1,326 @@ 
+/*
+ * 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.
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt)	"ACPI: IORT: " fmt
+
+#include <linux/export.h>
+#include <linux/iort.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+struct iort_its_msi_chip {
+	struct list_head	list;
+	struct fwnode_handle	*fw_node;
+	u32			translation_id;
+};
+
+typedef acpi_status (*iort_find_node_callback)
+	(struct acpi_iort_node *node, void *context);
+
+/* Root pointer to the mapped IORT table */
+static struct acpi_table_header *iort_table;
+
+static LIST_HEAD(iort_msi_chip_list);
+
+/**
+ * iort_register_domain_token() - register domain token and related ITS ID
+ * 				  to the list from where we can get it back
+ * 				  later on.
+ * @translation_id: ITS ID
+ * @token: domain token
+ *
+ * Returns: 0 on success, -ENOMEM if not memory when allocating list element.
+ */
+int iort_register_domain_token(int trans_id, struct fwnode_handle *fw_node)
+{
+	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->fw_node = fw_node;
+	its_msi_chip->translation_id = trans_id;
+
+	list_add(&its_msi_chip->list, &iort_msi_chip_list);
+	return 0;
+}
+
+/**
+ * iort_find_its_domain_token() - find domain token based on given ITS ID.
+ * @translation_id: ITS ID
+ *
+ * Returns: domain token when find on the list, NULL otherwise.
+ */
+struct fwnode_handle *iort_find_its_domain_token(int trans_id)
+{
+	struct iort_its_msi_chip *its_msi_chip;
+
+	list_for_each_entry(its_msi_chip, &iort_msi_chip_list, list) {
+		if (its_msi_chip->translation_id == trans_id)
+			return its_msi_chip->fw_node;
+	}
+
+	return NULL;
+}
+
+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 = (struct acpi_table_iort *)iort_table;
+
+	/* Get the first IORT node */
+	iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort,
+				 iort->node_offset);
+	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 struct acpi_iort_node *iort_find_parent_node(struct acpi_iort_node *node)
+{
+	struct acpi_iort_id_mapping *id;
+
+	if (!node || !node->mapping_offset || !node->mapping_count)
+		return NULL;
+
+	id = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node,
+			      node->mapping_offset);
+	/* Firmware bug! */
+	if (!id->output_reference) {
+		pr_err(FW_BUG "[node %p type %d] ID map has NULL parent reference\n",
+		       node, node->type);
+		return NULL;
+	}
+
+	node = ACPI_ADD_PTR(struct acpi_iort_node, iort_table,
+			    id->output_reference);
+	return node;
+}
+
+static acpi_status
+iort_find_dev_callback(struct acpi_iort_node *node, void *context)
+{
+	struct acpi_iort_root_complex *pci_rc;
+	struct device *dev = context;
+	struct pci_bus *bus;
+
+	switch (node->type) {
+	case ACPI_IORT_NODE_PCI_ROOT_COMPLEX:
+		bus = to_pci_bus(dev);
+		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 == pci_domain_nr(bus))
+			return AE_OK;
+
+		break;
+	}
+
+	return AE_NOT_FOUND;
+}
+
+/**
+ * iort_pci_find_its_id() - find the ITS identifier based on specified device.
+ * @dev: device
+ * @idx: index of the ITS identifier list
+ * @its_id: ITS identifier
+ *
+ * Returns: 0 on success, appropriate error value otherwise
+ */
+static int
+iort_pci_find_its_id(struct device *dev, unsigned int idx, int *its_id)
+{
+	struct acpi_iort_its_group *its;
+	struct acpi_iort_node *node;
+
+	node = iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
+			      iort_find_dev_callback, dev);
+	if (!node) {
+		pr_err("can't find node related to %s device\n", dev_name(dev));
+		return -ENXIO;
+	}
+
+	/* Go upstream until find its parent ITS node */
+	while (node->type != ACPI_IORT_NODE_ITS_GROUP) {
+		node = iort_find_parent_node(node);
+		if (!node)
+			return -ENXIO;
+	}
+
+	/* 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 -ENXIO;
+	}
+
+	*its_id = its->identifiers[idx];
+	return 0;
+}
+
+
+/**
+ * iort_find_pci_domain_token() - find registered domain token related to
+ * 				  PCI device
+ * @dev: device
+ *
+ * Returns: domain token on success, NULL otherwise
+ */
+struct fwnode_handle *iort_find_pci_domain_token(struct device *dev)
+{
+	static struct fwnode_handle *domain_handle;
+	int its_id;
+
+	if (iort_pci_find_its_id(dev, 0, &its_id))
+		return NULL;
+
+	domain_handle = iort_find_its_domain_token(its_id);
+	if (!domain_handle)
+		return NULL;
+
+	return domain_handle;
+}
+
+static int
+iort_translate_dev_to_devid(struct acpi_iort_node *node, u32 req_id,
+			    u32 *dev_id)
+{
+	u32 curr_id = req_id;
+
+	if (!node)
+		return -EINVAL;
+
+	/* Go upstream */
+	while (node->type != ACPI_IORT_NODE_ITS_GROUP) {
+		struct acpi_iort_id_mapping *id;
+		int i, found = 0;
+
+		/* Exit when no mapping array */
+		if (!node->mapping_offset || !node->mapping_count)
+			return -EINVAL;
+
+		id = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node,
+				  node->mapping_offset);
+
+		for (i = 0, found = 0; i < node->mapping_count; i++, id++) {
+			/*
+			 * Single mapping is not translation rule,
+			 * lets move on for this case
+			 */
+			if (id->flags & ACPI_IORT_ID_SINGLE_MAPPING)
+				continue;
+
+			if (curr_id < id->input_base ||
+			    (curr_id > id->input_base + id->id_count))
+				continue;
+
+			curr_id = id->output_base + (curr_id - id->input_base);
+			found = 1;
+			break;
+		}
+
+		if (!found)
+			return -ENXIO;
+
+		node = iort_find_parent_node(node);
+		if (!node)
+			return -ENXIO;
+	}
+
+	*dev_id = curr_id;
+	return 0;
+}
+
+/**
+ * iort_find_pci_id() - find PCI device ID based on requester ID
+ * @dev: device
+ * @req_id: requester ID
+ * @dev_id: device ID
+ *
+ * Returns: 0 on success, appropriate error value otherwise
+ */
+int iort_find_pci_id(struct pci_dev *pdev, u32 req_id, u32 *dev_id)
+{
+	struct pci_bus *bus = pdev->bus;
+	struct acpi_iort_node *node;
+	int err;
+
+	node = iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
+			      iort_find_dev_callback, &bus->dev);
+	if (!node) {
+		pr_err("can't find node related to %s device\n",
+		       dev_name(&pdev->dev));
+		return -ENXIO;
+	}
+
+	err = iort_translate_dev_to_devid(node, req_id, dev_id);
+	return err;
+}
+
+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 4d7294e..90f00b1 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
 
 config ARM_NVIC
 	bool
diff --git a/include/linux/iort.h b/include/linux/iort.h
new file mode 100644
index 0000000..92cde45
--- /dev/null
+++ b/include/linux/iort.h
@@ -0,0 +1,38 @@ 
+/*
+ * 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__
+
+#include <linux/acpi.h>
+
+#ifdef CONFIG_IORT_TABLE
+
+struct fwnode_handle;
+
+int iort_register_domain_token(int trans_id, struct fwnode_handle *fw_node);
+struct fwnode_handle *iort_find_its_domain_token(int trans_id);
+struct fwnode_handle *iort_find_pci_domain_token(struct device *dev);
+int iort_find_pci_id(struct pci_dev *pdev, u32 req_id, u32 *dev_id);
+#else /* CONFIG_IORT_TABLE */
+static inline int
+iort_find_pci_id(struct pci_dev *pdev, u32 req_id, u32 *dev_id)
+{ return -ENXIO; }
+#endif /* CONFIG_IORT_TABLE */
+
+#endif /* __IORT_H__ */