|
@@ -0,0 +1,216 @@
|
|
|
+/*
|
|
|
+ * Copyright (C) 2016, Semihalf
|
|
|
+ * Author: Tomasz Nowicki <tn@semihalf.com>
|
|
|
+ *
|
|
|
+ * 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/acpi_iort.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/pci.h>
|
|
|
+
|
|
|
+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);
|
|
|
+static DEFINE_SPINLOCK(iort_msi_chip_lock);
|
|
|
+
|
|
|
+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;
|
|
|
+
|
|
|
+ /* Get the first IORT node */
|
|
|
+ iort = (struct acpi_table_iort *)iort_table;
|
|
|
+ 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 (WARN_TAINT(iort_node >= iort_end, TAINT_FIRMWARE_WORKAROUND,
|
|
|
+ "IORT node pointer overflows, bad table!\n"))
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ if (iort_node->type == type &&
|
|
|
+ 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_match_node_callback(struct acpi_iort_node *node,
|
|
|
+ void *context)
|
|
|
+{
|
|
|
+ struct device *dev = context;
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ if (node->type == ACPI_IORT_NODE_NAMED_COMPONENT) {
|
|
|
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
|
+ struct acpi_device *adev = to_acpi_device_node(dev->fwnode);
|
|
|
+ struct acpi_iort_named_component *ncomp;
|
|
|
+
|
|
|
+ if (!adev) {
|
|
|
+ status = AE_NOT_FOUND;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ status = acpi_get_name(adev->handle, ACPI_FULL_PATHNAME, &buf);
|
|
|
+ if (ACPI_FAILURE(status)) {
|
|
|
+ dev_warn(dev, "Can't get device full path name\n");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ ncomp = (struct acpi_iort_named_component *)node->node_data;
|
|
|
+ status = !strcmp(ncomp->device_name, buf.pointer) ?
|
|
|
+ AE_OK : AE_NOT_FOUND;
|
|
|
+ acpi_os_free(buf.pointer);
|
|
|
+ } else if (node->type == ACPI_IORT_NODE_PCI_ROOT_COMPLEX) {
|
|
|
+ struct acpi_iort_root_complex *pci_rc;
|
|
|
+ struct pci_bus *bus;
|
|
|
+
|
|
|
+ bus = to_pci_bus(dev);
|
|
|
+ pci_rc = (struct acpi_iort_root_complex *)node->node_data;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * It is assumed that PCI segment numbers maps one-to-one
|
|
|
+ * with root complexes. Each segment number can represent only
|
|
|
+ * one root complex.
|
|
|
+ */
|
|
|
+ status = pci_rc->pci_segment_number == pci_domain_nr(bus) ?
|
|
|
+ AE_OK : AE_NOT_FOUND;
|
|
|
+ } else {
|
|
|
+ status = AE_NOT_FOUND;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+static int iort_id_map(struct acpi_iort_id_mapping *map, u8 type, u32 rid_in,
|
|
|
+ u32 *rid_out)
|
|
|
+{
|
|
|
+ /* Single mapping does not care for input id */
|
|
|
+ if (map->flags & ACPI_IORT_ID_SINGLE_MAPPING) {
|
|
|
+ if (type == ACPI_IORT_NODE_NAMED_COMPONENT ||
|
|
|
+ type == ACPI_IORT_NODE_PCI_ROOT_COMPLEX) {
|
|
|
+ *rid_out = map->output_base;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ pr_warn(FW_BUG "[map %p] SINGLE MAPPING flag not allowed for node type %d, skipping ID map\n",
|
|
|
+ map, type);
|
|
|
+ return -ENXIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rid_in < map->input_base ||
|
|
|
+ (rid_in >= map->input_base + map->id_count))
|
|
|
+ return -ENXIO;
|
|
|
+
|
|
|
+ *rid_out = map->output_base + (rid_in - map->input_base);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct acpi_iort_node *iort_node_map_rid(struct acpi_iort_node *node,
|
|
|
+ u32 rid_in, u32 *rid_out,
|
|
|
+ u8 type)
|
|
|
+{
|
|
|
+ u32 rid = rid_in;
|
|
|
+
|
|
|
+ /* Parse the ID mapping tree to find specified node type */
|
|
|
+ while (node) {
|
|
|
+ struct acpi_iort_id_mapping *map;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (node->type == type) {
|
|
|
+ if (rid_out)
|
|
|
+ *rid_out = rid;
|
|
|
+ return node;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!node->mapping_offset || !node->mapping_count)
|
|
|
+ goto fail_map;
|
|
|
+
|
|
|
+ map = ACPI_ADD_PTR(struct acpi_iort_id_mapping, node,
|
|
|
+ node->mapping_offset);
|
|
|
+
|
|
|
+ /* Firmware bug! */
|
|
|
+ if (!map->output_reference) {
|
|
|
+ pr_err(FW_BUG "[node %p type %d] ID map has NULL parent reference\n",
|
|
|
+ node, node->type);
|
|
|
+ goto fail_map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Do the RID translation */
|
|
|
+ for (i = 0; i < node->mapping_count; i++, map++) {
|
|
|
+ if (!iort_id_map(map, node->type, rid, &rid))
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i == node->mapping_count)
|
|
|
+ goto fail_map;
|
|
|
+
|
|
|
+ node = ACPI_ADD_PTR(struct acpi_iort_node, iort_table,
|
|
|
+ map->output_reference);
|
|
|
+ }
|
|
|
+
|
|
|
+fail_map:
|
|
|
+ /* Map input RID to output RID unchanged on mapping failure*/
|
|
|
+ if (rid_out)
|
|
|
+ *rid_out = rid_in;
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static struct acpi_iort_node *iort_find_dev_node(struct device *dev)
|
|
|
+{
|
|
|
+ struct pci_bus *pbus;
|
|
|
+
|
|
|
+ if (!dev_is_pci(dev))
|
|
|
+ return iort_scan_node(ACPI_IORT_NODE_NAMED_COMPONENT,
|
|
|
+ iort_match_node_callback, dev);
|
|
|
+
|
|
|
+ /* Find a PCI root bus */
|
|
|
+ pbus = to_pci_dev(dev)->bus;
|
|
|
+ while (!pci_is_root_bus(pbus))
|
|
|
+ pbus = pbus->parent;
|
|
|
+
|
|
|
+ return iort_scan_node(ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
|
|
|
+ iort_match_node_callback, &pbus->dev);
|
|
|
+}
|
|
|
+
|
|
|
+void __init acpi_iort_init(void)
|
|
|
+{
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ status = acpi_get_table(ACPI_SIG_IORT, 0, &iort_table);
|
|
|
+ if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) {
|
|
|
+ const char *msg = acpi_format_exception(status);
|
|
|
+ pr_err("Failed to get table, %s\n", msg);
|
|
|
+ }
|
|
|
+}
|