]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
irqchip/gic-v5: Add ACPI ITS probing
authorLorenzo Pieralisi <lpieralisi@kernel.org>
Thu, 15 Jan 2026 09:50:51 +0000 (10:50 +0100)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Tue, 27 Jan 2026 14:31:42 +0000 (15:31 +0100)
On ACPI ARM64 systems the GICv5 ITS configuration and translate frames
are described in the MADT table.

Refactor the current GICv5 ITS driver code to share common functions
between ACPI and OF and implement ACPI probing in the GICv5 ITS driver.

Add iort_msi_xlate() to map a device ID and retrieve an MSI controller
fwnode node for ACPI systems and update pci_msi_map_rid_ctlr_node() to
use it in its ACPI code path.

Add the required functions to IORT code for deviceID retrieval and IRQ
domain registration and look-up so that the GICv5 ITS driver in an
ACPI based system can be successfully probed.

Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Acked-by: Thomas Gleixner <tglx@kernel.org>
Link: https://patch.msgid.link/20260115-gicv5-host-acpi-v3-5-c13a9a150388@kernel.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/arm64/iort.c
drivers/irqchip/irq-gic-its-msi-parent.c
drivers/irqchip/irq-gic-v5-irs.c
drivers/irqchip/irq-gic-v5-its.c
drivers/pci/msi/irqdomain.c
include/linux/acpi_iort.h
include/linux/irqchip/arm-gic-v5.h

index 65f0f56ad7538a8fc705f8d60877017266a4c1f6..ddd857f05f46f9c205cc4e1656f4f1576867b6b4 100644 (file)
@@ -595,45 +595,45 @@ u32 iort_msi_map_id(struct device *dev, u32 input_id)
 }
 
 /**
- * iort_pmsi_get_dev_id() - Get the device id for a device
+ * iort_msi_xlate() - Map a MSI input ID for a device
  * @dev: The device for which the mapping is to be done.
- * @dev_id: The device ID found.
+ * @input_id: The device input ID.
+ * @fwnode: Pointer to store the fwnode.
  *
- * Returns: 0 for successful find a dev id, -ENODEV on error
+ * Returns: mapped MSI ID on success, input ID otherwise
+ *         On success, the fwnode pointer is initialized to the MSI
+ *         controller fwnode handle.
  */
-int iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
+u32 iort_msi_xlate(struct device *dev, u32 input_id, struct fwnode_handle **fwnode)
 {
-       int i, index;
+       struct acpi_iort_its_group *its;
        struct acpi_iort_node *node;
+       u32 dev_id;
 
        node = iort_find_dev_node(dev);
        if (!node)
-               return -ENODEV;
+               return input_id;
 
-       index = iort_get_id_mapping_index(node);
-       /* if there is a valid index, go get the dev_id directly */
-       if (index >= 0) {
-               if (iort_node_get_id(node, dev_id, index))
-                       return 0;
-       } else {
-               for (i = 0; i < node->mapping_count; i++) {
-                       if (iort_node_map_platform_id(node, dev_id,
-                                                     IORT_MSI_TYPE, i))
-                               return 0;
-               }
-       }
+       node = iort_node_map_id(node, input_id, &dev_id, IORT_MSI_TYPE);
+       if (!node)
+               return input_id;
 
-       return -ENODEV;
+       /* Move to ITS specific data */
+       its = (struct acpi_iort_its_group *)node->node_data;
+
+       *fwnode = iort_find_domain_token(its->identifiers[0]);
+
+       return dev_id;
 }
 
-static int __maybe_unused iort_find_its_base(u32 its_id, phys_addr_t *base)
+int iort_its_translate_pa(struct fwnode_handle *node, phys_addr_t *base)
 {
        struct iort_its_msi_chip *its_msi_chip;
        int ret = -ENODEV;
 
        spin_lock(&iort_msi_chip_lock);
        list_for_each_entry(its_msi_chip, &iort_msi_chip_list, list) {
-               if (its_msi_chip->translation_id == its_id) {
+               if (its_msi_chip->fw_node == node) {
                        *base = its_msi_chip->base_addr;
                        ret = 0;
                        break;
@@ -644,6 +644,62 @@ static int __maybe_unused iort_find_its_base(u32 its_id, phys_addr_t *base)
        return ret;
 }
 
+static int __maybe_unused iort_find_its_base(u32 its_id, phys_addr_t *base)
+{
+       struct fwnode_handle *fwnode = iort_find_domain_token(its_id);
+
+       if (!fwnode)
+               return -ENODEV;
+
+       return iort_its_translate_pa(fwnode, base);
+}
+
+/**
+ * iort_pmsi_get_msi_info() - Get the device id and translate frame PA for a device
+ * @dev: The device for which the mapping is to be done.
+ * @dev_id: The device ID found.
+ * @pa: optional pointer to store translate frame address.
+ *
+ * Returns: 0 for successful devid and pa retrieval, -ENODEV on error
+ */
+int iort_pmsi_get_msi_info(struct device *dev, u32 *dev_id, phys_addr_t *pa)
+{
+       struct acpi_iort_node *node, *parent = NULL;
+       struct acpi_iort_its_group *its;
+       int i, index;
+
+       node = iort_find_dev_node(dev);
+       if (!node)
+               return -ENODEV;
+
+       index = iort_get_id_mapping_index(node);
+       /* if there is a valid index, go get the dev_id directly */
+       if (index >= 0) {
+               parent = iort_node_get_id(node, dev_id, index);
+       } else {
+               for (i = 0; i < node->mapping_count; i++) {
+                       parent = iort_node_map_platform_id(node, dev_id,
+                                                     IORT_MSI_TYPE, i);
+                       if (parent)
+                               break;
+               }
+       }
+
+       if (!parent)
+               return -ENODEV;
+
+       if (pa) {
+               int ret;
+
+               its = (struct acpi_iort_its_group *)node->node_data;
+               ret = iort_find_its_base(its->identifiers[0], pa);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
 /**
  * iort_dev_find_its_id() - Find the ITS identifier for a device
  * @dev: The device.
index 4d1ad1ee005db6181d34fbaa99448eb375b9c7a3..a832cdb2e697840e97f683a92001e5d7ed3f789b 100644 (file)
                                 MSI_FLAG_PCI_MSIX      |       \
                                 MSI_FLAG_MULTI_PCI_MSI)
 
-static int its_translate_frame_address(struct device_node *msi_node, phys_addr_t *pa)
+static int its_translate_frame_address(struct fwnode_handle *msi_node, phys_addr_t *pa)
 {
        struct resource res;
        int ret;
 
-       ret = of_property_match_string(msi_node, "reg-names", "ns-translate");
-       if (ret < 0)
-               return ret;
+       if (is_of_node(msi_node)) {
+               struct device_node *msi_np = to_of_node(msi_node);
 
-       ret = of_address_to_resource(msi_node, ret, &res);
-       if (ret)
-               return ret;
+               ret = of_property_match_string(msi_np, "reg-names", "ns-translate");
+               if (ret < 0)
+                       return ret;
+
+               ret = of_address_to_resource(msi_np, ret, &res);
+               if (ret)
+                       return ret;
+       } else {
+               ret = iort_its_translate_pa(msi_node, &res.start);
+       }
 
        *pa = res.start;
        return 0;
@@ -120,7 +126,7 @@ static int its_v5_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
        if (!msi_node)
                return -ENODEV;
 
-       ret = its_translate_frame_address(to_of_node(msi_node), &pa);
+       ret = its_translate_frame_address(msi_node, &pa);
        if (ret)
                return -ENODEV;
 
@@ -161,7 +167,7 @@ static int of_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev, u
                                ret = -EINVAL;
 
                        if (!ret && pa)
-                               ret = its_translate_frame_address(it.node, pa);
+                               ret = its_translate_frame_address(of_fwnode_handle(it.node), pa);
 
                        if (!ret)
                                *dev_id = args;
@@ -176,11 +182,6 @@ static int of_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev, u
        return of_map_id(dev->of_node, dev->id, "msi-map", "msi-map-mask", &msi_ctrl, dev_id);
 }
 
-int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
-{
-       return -1;
-}
-
 static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
                            int nvec, msi_alloc_info_t *info)
 {
@@ -191,7 +192,7 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
        if (dev->of_node)
                ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, NULL);
        else
-               ret = iort_pmsi_get_dev_id(dev, &dev_id);
+               ret = iort_pmsi_get_msi_info(dev, &dev_id, NULL);
        if (ret)
                return ret;
 
@@ -214,10 +215,10 @@ static int its_v5_pmsi_prepare(struct irq_domain *domain, struct device *dev,
        u32 dev_id;
        int ret;
 
-       if (!dev->of_node)
-               return -ENODEV;
-
-       ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, &pa);
+       if (dev->of_node)
+               ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, &pa);
+       else
+               ret = iort_pmsi_get_msi_info(dev, &dev_id, &pa);
        if (ret)
                return ret;
 
index a27a01f9e7a2b3bc1769a0d39c572520254a8d7b..fcd032b5e2fa3994631573ad5b0ded0d6f6659c8 100644 (file)
@@ -814,8 +814,11 @@ void __init gicv5_irs_its_probe(void)
 {
        struct gicv5_irs_chip_data *irs_data;
 
-       list_for_each_entry(irs_data, &irs_nodes, entry)
-               gicv5_its_of_probe(to_of_node(irs_data->fwnode));
+       if (acpi_disabled)
+               list_for_each_entry(irs_data, &irs_nodes, entry)
+                       gicv5_its_of_probe(to_of_node(irs_data->fwnode));
+       else
+               gicv5_its_acpi_probe();
 }
 
 int __init gicv5_irs_of_probe(struct device_node *parent)
index 8e22134b9f48676573f6ab61b55fbc2cd6242157..e24ce3d9fb629173bec335b1bce694b30b4a71c9 100644 (file)
@@ -5,6 +5,8 @@
 
 #define pr_fmt(fmt)    "GICv5 ITS: " fmt
 
+#include <linux/acpi.h>
+#include <linux/acpi_iort.h>
 #include <linux/bitmap.h>
 #include <linux/iommu.h>
 #include <linux/init.h>
@@ -1115,7 +1117,7 @@ static int gicv5_its_init_domain(struct gicv5_its_chip_data *its, struct irq_dom
 }
 
 static int __init gicv5_its_init_bases(void __iomem *its_base, struct fwnode_handle *handle,
-                                      struct irq_domain *parent_domain)
+                                      struct irq_domain *parent_domain, bool noncoherent)
 {
        struct device_node *np = to_of_node(handle);
        struct gicv5_its_chip_data *its_node;
@@ -1208,7 +1210,8 @@ static int __init gicv5_its_init(struct device_node *node)
        }
 
        ret = gicv5_its_init_bases(its_base, of_fwnode_handle(node),
-                                  gicv5_global_data.lpi_domain);
+                                  gicv5_global_data.lpi_domain,
+                                  of_property_read_bool(node, "dma-noncoherent"));
        if (ret)
                goto out_unmap;
 
@@ -1231,3 +1234,128 @@ void __init gicv5_its_of_probe(struct device_node *parent)
                        pr_err("Failed to init ITS %s\n", np->full_name);
        }
 }
+
+#ifdef CONFIG_ACPI
+
+#define ACPI_GICV5_ITS_MEM_SIZE (SZ_64K)
+
+static struct acpi_madt_gicv5_translator *current_its_entry __initdata;
+static struct fwnode_handle *current_its_fwnode __initdata;
+
+static int __init gic_acpi_parse_madt_its_translate(union acpi_subtable_headers *header,
+                                                   const unsigned long end)
+{
+       struct acpi_madt_gicv5_translate_frame *its_frame;
+       struct fwnode_handle *msi_dom_handle;
+       struct resource res = {};
+       int err;
+
+       its_frame = (struct acpi_madt_gicv5_translate_frame *)header;
+       if (its_frame->linked_translator_id != current_its_entry->translator_id)
+               return 0;
+
+       res.start = its_frame->base_address;
+       res.end = its_frame->base_address + ACPI_GICV5_ITS_MEM_SIZE - 1;
+       res.flags = IORESOURCE_MEM;
+
+       msi_dom_handle = irq_domain_alloc_parented_fwnode(&res.start, current_its_fwnode);
+       if (!msi_dom_handle) {
+               pr_err("ITS@%pa: Unable to allocate GICv5 ITS translate domain token\n",
+                      &res.start);
+               return -ENOMEM;
+       }
+
+       err = iort_register_domain_token(its_frame->translate_frame_id, res.start,
+                                        msi_dom_handle);
+       if (err) {
+               pr_err("ITS@%pa: Unable to register GICv5 ITS domain token (ITS TRANSLATE FRAME ID %d) to IORT\n",
+                      &res.start, its_frame->translate_frame_id);
+               irq_domain_free_fwnode(msi_dom_handle);
+               return err;
+       }
+
+       return 0;
+}
+
+static int __init gic_acpi_free_madt_its_translate(union acpi_subtable_headers *header,
+                                                  const unsigned long end)
+{
+       struct acpi_madt_gicv5_translate_frame *its_frame;
+       struct fwnode_handle *msi_dom_handle;
+
+       its_frame = (struct acpi_madt_gicv5_translate_frame *)header;
+       if (its_frame->linked_translator_id != current_its_entry->translator_id)
+               return 0;
+
+       msi_dom_handle = iort_find_domain_token(its_frame->translate_frame_id);
+       if (!msi_dom_handle)
+               return 0;
+
+       iort_deregister_domain_token(its_frame->translate_frame_id);
+       irq_domain_free_fwnode(msi_dom_handle);
+
+       return 0;
+}
+
+static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header,
+                                         const unsigned long end)
+{
+       struct acpi_madt_gicv5_translator *its_entry;
+       struct fwnode_handle *dom_handle;
+       struct resource res = {};
+       void __iomem *its_base;
+       int err;
+
+       its_entry = (struct acpi_madt_gicv5_translator *)header;
+       res.start = its_entry->base_address;
+       res.end = its_entry->base_address + ACPI_GICV5_ITS_MEM_SIZE - 1;
+       res.flags = IORESOURCE_MEM;
+
+       if (!request_mem_region(res.start, resource_size(&res), "GICv5 ITS"))
+               return -EBUSY;
+
+       dom_handle = irq_domain_alloc_fwnode(&res.start);
+       if (!dom_handle) {
+               pr_err("ITS@%pa: Unable to allocate GICv5 ITS domain token\n",
+                      &res.start);
+               err = -ENOMEM;
+               goto out_rel_res;
+       }
+
+       current_its_entry = its_entry;
+       current_its_fwnode = dom_handle;
+
+       acpi_table_parse_madt(ACPI_MADT_TYPE_GICV5_ITS_TRANSLATE,
+                             gic_acpi_parse_madt_its_translate, 0);
+
+       its_base = ioremap(res.start, ACPI_GICV5_ITS_MEM_SIZE);
+       if (!its_base) {
+               err = -ENOMEM;
+               goto out_unregister;
+       }
+
+       err = gicv5_its_init_bases(its_base, dom_handle, gicv5_global_data.lpi_domain,
+                                  its_entry->flags & ACPI_MADT_GICV5_ITS_NON_COHERENT);
+       if (err)
+               goto out_unmap;
+
+       return 0;
+
+out_unmap:
+       iounmap(its_base);
+out_unregister:
+       acpi_table_parse_madt(ACPI_MADT_TYPE_GICV5_ITS_TRANSLATE,
+                             gic_acpi_free_madt_its_translate, 0);
+       irq_domain_free_fwnode(dom_handle);
+out_rel_res:
+       release_mem_region(res.start, resource_size(&res));
+       return err;
+}
+
+void __init gicv5_its_acpi_probe(void)
+{
+       acpi_table_parse_madt(ACPI_MADT_TYPE_GICV5_ITS, gic_acpi_parse_madt_its, 0);
+}
+#else
+void __init gicv5_its_acpi_probe(void) { }
+#endif
index 3a4038640fdaa560bfe9d9ad6bd6392df614a69f..6e65f0f44112e68c977eb3be32ae3f32f418e50c 100644 (file)
@@ -401,6 +401,8 @@ u32 pci_msi_map_rid_ctlr_node(struct irq_domain *domain, struct pci_dev *pdev,
                rid = of_msi_xlate(&pdev->dev, &msi_ctlr_node, rid);
                if (msi_ctlr_node)
                        *node = of_fwnode_handle(msi_ctlr_node);
+       } else {
+               rid = iort_msi_xlate(&pdev->dev, rid, node);
        }
 
        return rid;
index d4ed5622cf2b02060e6f748177e6286afff094e2..2d22268677a9cee7c76d11e79a63a566ef05cda5 100644 (file)
@@ -27,12 +27,14 @@ int iort_register_domain_token(int trans_id, phys_addr_t base,
                               struct fwnode_handle *fw_node);
 void iort_deregister_domain_token(int trans_id);
 struct fwnode_handle *iort_find_domain_token(int trans_id);
-int iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id);
 
 #ifdef CONFIG_ACPI_IORT
 u32 iort_msi_map_id(struct device *dev, u32 id);
+u32 iort_msi_xlate(struct device *dev, u32 id, struct fwnode_handle **node);
+int iort_its_translate_pa(struct fwnode_handle *node, phys_addr_t *base);
 struct irq_domain *iort_get_device_domain(struct device *dev, u32 id,
                                          enum irq_domain_bus_token bus_token);
+int iort_pmsi_get_msi_info(struct device *dev, u32 *dev_id, phys_addr_t *pa);
 void acpi_configure_pmsi_domain(struct device *dev);
 void iort_get_rmr_sids(struct fwnode_handle *iommu_fwnode,
                       struct list_head *head);
@@ -46,9 +48,15 @@ phys_addr_t acpi_iort_dma_get_max_cpu_address(void);
 #else
 static inline u32 iort_msi_map_id(struct device *dev, u32 id)
 { return id; }
+static inline u32 iort_msi_xlate(struct device *dev, u32 id, struct fwnode_handle **node)
+{ return id; }
+static inline int iort_its_translate_pa(struct fwnode_handle *node, phys_addr_t *base)
+{ return -ENODEV; }
 static inline struct irq_domain *iort_get_device_domain(
        struct device *dev, u32 id, enum irq_domain_bus_token bus_token)
 { return NULL; }
+static inline int iort_pmsi_get_msi_info(struct device *dev, u32 *dev_id, phys_addr_t *pa)
+{ return -ENODEV; }
 static inline void acpi_configure_pmsi_domain(struct device *dev) { }
 static inline
 void iort_get_rmr_sids(struct fwnode_handle *iommu_fwnode, struct list_head *head) { }
index ff5b1a4931d746d5a8d3a493d26688dde7c6de00..334b6986435c011e4b0f42979c9cffb37008fe35 100644 (file)
@@ -392,4 +392,5 @@ int gicv5_alloc_lpi(void);
 void gicv5_free_lpi(u32 lpi);
 
 void __init gicv5_its_of_probe(struct device_node *parent);
+void __init gicv5_its_acpi_probe(void);
 #endif