--- /dev/null
+/*
+ * 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);