/* * Copyright (C) 2016, Semihalf * Author: Tomasz Nowicki * * 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 #include #include 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); } }