Re: [PATCH V6 1/7] ACPI: I/O Remapping Table (IORT) initial support

From: Tomasz Nowicki
Date: Fri Jun 17 2016 - 10:06:29 EST


On 15.06.2016 10:31, Marc Zyngier wrote:
On Mon, 13 Jun 2016 16:41:07 +0200
Tomasz Nowicki <tn@xxxxxxxxxxxx> wrote:

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. Also see IORT spec.

Initial support allows to:
- register ITS MSI chip along with ITS translation ID and domain token
- deregister ITS MSI chip based on ITS translation ID
- find registered domain token based on ITS translation ID
- map MSI RID for a device
- find domain token for a device

Signed-off-by: Tomasz Nowicki <tn@xxxxxxxxxxxx>
---
drivers/acpi/Kconfig | 3 +
drivers/acpi/Makefile | 1 +
drivers/acpi/iort.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/iort.h | 38 +++++
4 files changed, 428 insertions(+)
create mode 100644 drivers/acpi/iort.c
create mode 100644 include/linux/iort.h


[...]

+
+static struct acpi_iort_node *
+iort_node_map_rid(struct acpi_iort_node *node, u32 rid_in,
+ u32 *rid_out, u8 type)
+{
+
+ if (!node)
+ goto out;
+
+ /* Go upstream */
+ while (node->type != type) {
+ struct acpi_iort_id_mapping *id;
+ int i, found = 0;
+
+ /* Exit when no mapping array */
+ if (!node->mapping_offset || !node->mapping_count)
+ return NULL;
+
+ 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) {
+ if (node->type != ACPI_IORT_NODE_SMMU) {
+ rid_in = id->output_base;
+ found = 1;
+ break;
+ }
+
+ pr_warn(FW_BUG "[node %p type %d] SINGLE MAPPING flag not allowed for SMMU node, skipping ID map\n",
+ node, node->type);
+ continue;
+ }
+
+ if (rid_in < id->input_base ||
+ (rid_in > id->input_base + id->id_count))
+ continue;
+
+ rid_in = id->output_base + (rid_in - id->input_base);
+ found = 1;
+ break;
+ }
+
+ if (!found)
+ return NULL;

Why this special case? It would make more sense to use the normal
epilogue, and update rid_out. Unless not finding a translation for a
given rid is illegal?

We can use the same strategy as __of_msi_map_rid() which means we simply use rid_in in case of any error. I will update accordingly.


+
+ /* 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);
+ }
+
+out:
+ if (rid_out)
+ *rid_out = rid_in;
+ return node;
+}
+
+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);
+}
+
+/**
+ * iort_msi_map_rid() - Map a MSI requester ID for a device
+ * @dev: The device for which the mapping is to be done.
+ * @req_id: The device requester ID.
+ *
+ * Returns: mapped MSI RID on success, input requester ID otherwise
+ */
+u32 iort_msi_map_rid(struct device *dev, u32 req_id)
+{
+ struct acpi_iort_node *node;
+ u32 dev_id;
+
+ if (!iort_table)
+ return req_id;
+
+ node = iort_find_dev_node(dev);
+ if (!node) {
+ dev_err(dev, "can't find related IORT node\n");
+ return req_id;
+ }
+
+ if (!iort_node_map_rid(node, req_id, &dev_id,
+ ACPI_IORT_NODE_ITS_GROUP))
+ return req_id;

And once you've fixed the special case in iort_node_map_rid, you can
unconditionally return dev_id.

Right.


+
+ return dev_id;
+}
+
+/**
+ * iort_dev_find_its_id() - Find the ITS identifier for a device
+ * @dev: The device.
+ * @idx: Index of the ITS identifier list.
+ * @its_id: ITS identifier.
+ *
+ * Returns: 0 on success, appropriate error value otherwise
+ */
+static int
+iort_dev_find_its_id(struct device *dev, u32 req_id, unsigned int idx,
+ int *its_id)
+{
+ struct acpi_iort_its_group *its;
+ struct acpi_iort_node *node;
+
+ node = iort_find_dev_node(dev);
+ if (!node) {
+ dev_err(dev, "can't find related IORT node\n");
+ return -ENXIO;
+ }
+
+ node = iort_node_map_rid(node, req_id, NULL, ACPI_IORT_NODE_ITS_GROUP);
+ if (!node) {
+ dev_err(dev, "can't find related ITS node\n");
+ return -ENXIO;
+ }
+
+ /* Move to ITS specific data */
+ its = (struct acpi_iort_its_group *)node->node_data;
+ if (idx > its->its_count) {
+ dev_err(dev, "requested ITS ID index [%d] is greater than available [%d]\n",
+ idx, its->its_count);
+ return -ENXIO;
+ }
+
+ *its_id = its->identifiers[idx];
+ return 0;
+}
+
+/**
+ * iort_get_device_domain() - Find MSI domain related to a device
+ * @dev: The device.
+ * @req_id: Requester ID for the device.
+ *
+ * Returns: the MSI domain for this device, NULL otherwise
+ */
+struct irq_domain *
+iort_get_device_domain(struct device *dev, u32 req_id)
+{
+ static struct fwnode_handle *handle;
+ int its_id;
+
+ if (!iort_table)
+ return NULL;
+
+ if (iort_dev_find_its_id(dev, req_id, 0, &its_id))
+ return NULL;
+
+ handle = iort_find_domain_token(its_id);
+ if (!handle)
+ return NULL;

Can this actually happen? I can't see how, unless you have a race
between iort_dev_find_its_id and iort_find_domain_token. And given that
both these functions are only called from here, maybe you're better off
having a single function:

struct fwnode_handle *iort_dev_find_its_domain_token(struct device *dev,
u32 rid);

which returns the atomic lookup of the ITS handle. Or is there any
constraints preventing us from holding the lock?

Yes this may happen, let's say we have one ITS with ID = 0:
1. iort_register_domain_token() fails because of lack of memory (-ENOMEM)
2. iort_dev_find_its_id() would point us to ITS with ID = 0
3. iort_find_domain_token() return NULL due to no element on the list for ITS ID = 0

Actually iort_dev_find_its_id() finds out ITS ID related to a given device, it only interact with IORT content but not with iort_msi_chip_list list. iort_find_domain_token() has its own lock for iort_msi_chip_list so I am not sure why we need lock.

Thanks,
Tomasz