]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
irqchip/gic-v5: Add ACPI IWB probing
authorLorenzo Pieralisi <lpieralisi@kernel.org>
Thu, 15 Jan 2026 09:50:52 +0000 (10:50 +0100)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Tue, 27 Jan 2026 14:31:42 +0000 (15:31 +0100)
To probe an IWB in an ACPI based system it is required:

- to implement the IORT functions handling the IWB IORT node and create
  functions to retrieve IWB firmware information
- to augment the driver to match the DSDT ACPI "ARMH0003" device and
  retrieve the IWB wire and trigger mask from the GSI interrupt descriptor
  in the IWB msi_domain_ops.msi_translate() function

Make the required driver changes to enable IWB probing in ACPI systems.

The GICv5 GSI format requires special handling for IWB routed IRQs.

Add IWB GSI detection to the top level driver gic_v5_get_gsi_domain_id()
function so that the correct IRQ domain for a GSI can be detected by
parsing the GSI and check whether it is an IWB-backed IRQ or not.

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-6-c13a9a150388@kernel.org
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/arm64/iort.c
drivers/irqchip/irq-gic-v5-iwb.c
drivers/irqchip/irq-gic-v5.c
include/linux/acpi_iort.h
include/linux/irqchip/arm-gic-v5.h

index ddd857f05f46f9c205cc4e1656f4f1576867b6b4..ed827b2fc43759484d56ade62f50a55a760bb81f 100644 (file)
@@ -264,39 +264,47 @@ static acpi_status iort_match_node_callback(struct acpi_iort_node *node,
        struct device *dev = context;
        acpi_status status = AE_NOT_FOUND;
 
-       if (node->type == ACPI_IORT_NODE_NAMED_COMPONENT) {
+       if (node->type == ACPI_IORT_NODE_NAMED_COMPONENT ||
+           node->type == ACPI_IORT_NODE_IWB) {
                struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
-               struct acpi_device *adev;
                struct acpi_iort_named_component *ncomp;
-               struct device *nc_dev = dev;
+               struct acpi_iort_iwb *iwb;
+               struct device *cdev = dev;
+               struct acpi_device *adev;
+               const char *device_name;
 
                /*
                 * Walk the device tree to find a device with an
                 * ACPI companion; there is no point in scanning
-                * IORT for a device matching a named component if
+                * IORT for a device matching a named component or IWB if
                 * the device does not have an ACPI companion to
                 * start with.
                 */
                do {
-                       adev = ACPI_COMPANION(nc_dev);
+                       adev = ACPI_COMPANION(cdev);
                        if (adev)
                                break;
 
-                       nc_dev = nc_dev->parent;
-               } while (nc_dev);
+                       cdev = cdev->parent;
+               } while (cdev);
 
                if (!adev)
                        goto out;
 
                status = acpi_get_name(adev->handle, ACPI_FULL_PATHNAME, &buf);
                if (ACPI_FAILURE(status)) {
-                       dev_warn(nc_dev, "Can't get device full path name\n");
+                       dev_warn(cdev, "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;
+               if (node->type == ACPI_IORT_NODE_NAMED_COMPONENT) {
+                       ncomp = (struct acpi_iort_named_component *)node->node_data;
+                       device_name = ncomp->device_name;
+               } else {
+                       iwb = (struct acpi_iort_iwb *)node->node_data;
+                       device_name = iwb->device_name;
+               }
+               status = !strcmp(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;
@@ -317,12 +325,28 @@ out:
        return status;
 }
 
+static acpi_status iort_match_iwb_callback(struct acpi_iort_node *node, void *context)
+{
+       struct acpi_iort_iwb *iwb;
+       u32 *id = context;
+
+       if (node->type != ACPI_IORT_NODE_IWB)
+               return AE_NOT_FOUND;
+
+       iwb = (struct acpi_iort_iwb *)node->node_data;
+       if (iwb->iwb_index != *id)
+               return AE_NOT_FOUND;
+
+       return AE_OK;
+}
+
 static int iort_id_map(struct acpi_iort_id_mapping *map, u8 type, u32 rid_in,
                       u32 *rid_out, bool check_overlap)
 {
        /* 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_IWB             ||
                    type == ACPI_IORT_NODE_PCI_ROOT_COMPLEX) {
                        *rid_out = map->output_base;
                        return 0;
@@ -392,6 +416,7 @@ static struct acpi_iort_node *iort_node_get_id(struct acpi_iort_node *node,
 
        if (map->flags & ACPI_IORT_ID_SINGLE_MAPPING) {
                if (node->type == ACPI_IORT_NODE_NAMED_COMPONENT ||
+                   node->type == ACPI_IORT_NODE_IWB ||
                    node->type == ACPI_IORT_NODE_PCI_ROOT_COMPLEX ||
                    node->type == ACPI_IORT_NODE_SMMU_V3 ||
                    node->type == ACPI_IORT_NODE_PMCG) {
@@ -562,9 +587,14 @@ static struct acpi_iort_node *iort_find_dev_node(struct device *dev)
                        return node;
                /*
                 * if not, then it should be a platform device defined in
-                * DSDT/SSDT (with Named Component node in IORT)
+                * DSDT/SSDT (with Named Component node in IORT) or an
+                * IWB device in the DSDT/SSDT.
                 */
-               return iort_scan_node(ACPI_IORT_NODE_NAMED_COMPONENT,
+               node = iort_scan_node(ACPI_IORT_NODE_NAMED_COMPONENT,
+                                     iort_match_node_callback, dev);
+               if (node)
+                       return node;
+               return iort_scan_node(ACPI_IORT_NODE_IWB,
                                      iort_match_node_callback, dev);
        }
 
@@ -759,6 +789,35 @@ struct irq_domain *iort_get_device_domain(struct device *dev, u32 id,
        return irq_find_matching_fwnode(handle, bus_token);
 }
 
+struct fwnode_handle *iort_iwb_handle(u32 iwb_id)
+{
+       struct fwnode_handle *fwnode;
+       struct acpi_iort_node *node;
+       struct acpi_device *device;
+       struct acpi_iort_iwb *iwb;
+       acpi_status status;
+       acpi_handle handle;
+
+       /* find its associated IWB node */
+       node = iort_scan_node(ACPI_IORT_NODE_IWB, iort_match_iwb_callback, &iwb_id);
+       if (!node)
+               return NULL;
+
+       iwb = (struct acpi_iort_iwb *)node->node_data;
+       status = acpi_get_handle(NULL, iwb->device_name, &handle);
+       if (ACPI_FAILURE(status))
+               return NULL;
+
+       device = acpi_get_acpi_dev(handle);
+       if (!device)
+               return NULL;
+
+       fwnode = acpi_fwnode_handle(device);
+       acpi_put_acpi_dev(device);
+
+       return fwnode;
+}
+
 static void iort_set_device_domain(struct device *dev,
                                   struct acpi_iort_node *node)
 {
@@ -819,8 +878,14 @@ static struct irq_domain *iort_get_platform_device_domain(struct device *dev)
        /* find its associated iort node */
        node = iort_scan_node(ACPI_IORT_NODE_NAMED_COMPONENT,
                              iort_match_node_callback, dev);
-       if (!node)
-               return NULL;
+       if (!node) {
+               /* find its associated iort node */
+               node = iort_scan_node(ACPI_IORT_NODE_IWB,
+                                     iort_match_node_callback, dev);
+
+               if (!node)
+                       return NULL;
+       }
 
        /* then find its msi parent node */
        for (i = 0; i < node->mapping_count; i++) {
index ad9fdc14d1c632ecd034ffb54d760c2384768cea..c7d5fd34d053d141d1a99e13415c0b2f3816920f 100644 (file)
@@ -4,6 +4,7 @@
  */
 #define pr_fmt(fmt)    "GICv5 IWB: " fmt
 
+#include <linux/acpi.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/msi.h>
@@ -136,18 +137,31 @@ static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspe
                                          irq_hw_number_t *hwirq,
                                          unsigned int *type)
 {
-       if (!is_of_node(fwspec->fwnode))
-               return -EINVAL;
+       if (is_of_node(fwspec->fwnode)) {
 
-       if (fwspec->param_count < 2)
-               return -EINVAL;
+               if (fwspec->param_count < 2)
+                       return -EINVAL;
 
-       /*
-        * param[0] is be the wire
-        * param[1] is the interrupt type
-        */
-       *hwirq = fwspec->param[0];
-       *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+               /*
+                * param[0] is be the wire
+                * param[1] is the interrupt type
+                */
+               *hwirq = fwspec->param[0];
+               *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+       }
+
+       if (is_acpi_device_node(fwspec->fwnode)) {
+
+               if (fwspec->param_count < 2)
+                       return -EINVAL;
+
+               /*
+                * Extract the wire from param[0]
+                * param[1] is the interrupt type
+                */
+               *hwirq = FIELD_GET(GICV5_GSI_IWB_WIRE, fwspec->param[0]);
+               *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+       }
 
        return 0;
 }
@@ -265,10 +279,18 @@ static const struct of_device_id gicv5_iwb_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
 
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id iwb_acpi_match[] = {
+       { "ARMH0003", 0 },
+       {}
+};
+#endif
+
 static struct platform_driver gicv5_iwb_platform_driver = {
        .driver = {
                .name                   = "GICv5 IWB",
                .of_match_table         = gicv5_iwb_of_match,
+               .acpi_match_table       = ACPI_PTR(iwb_acpi_match),
                .suppress_bind_attrs    = true,
        },
        .probe                          = gicv5_iwb_device_probe,
index 23fd551c434720b7f17bffeb6bb36a8e5c58e36c..da867dd2e77dc0bbf483e676f596d479bc238a73 100644 (file)
@@ -5,6 +5,7 @@
 
 #define pr_fmt(fmt)    "GICv5: " fmt
 
+#include <linux/acpi_iort.h>
 #include <linux/cpuhotplug.h>
 #include <linux/idr.h>
 #include <linux/irqdomain.h>
@@ -1187,6 +1188,9 @@ static struct fwnode_handle *gsi_domain_handle;
 
 static struct fwnode_handle *gic_v5_get_gsi_domain_id(u32 gsi)
 {
+       if (FIELD_GET(GICV5_GSI_IC_TYPE, gsi) == GICV5_GSI_IWB_TYPE)
+               return iort_iwb_handle(FIELD_GET(GICV5_GSI_IWB_FRAME_ID, gsi));
+
        return gsi_domain_handle;
 }
 
index 2d22268677a9cee7c76d11e79a63a566ef05cda5..17bb3374f4ca7fc61f8c48843d7e41bbe925b161 100644 (file)
@@ -27,6 +27,7 @@ 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);
+struct fwnode_handle *iort_iwb_handle(u32 iwb_id);
 
 #ifdef CONFIG_ACPI_IORT
 u32 iort_msi_map_id(struct device *dev, u32 id);
index 334b6986435c011e4b0f42979c9cffb37008fe35..3da1ad80fc9ded1a8a2592b7a6c35fa92a7c5b2d 100644 (file)
 
 #define GICV5_IWB_WENABLE_STATUSR_IDLE         BIT(0)
 
+#define GICV5_GSI_IC_TYPE                      GENMASK(31, 29)
+#define GICV5_GSI_IWB_TYPE                     0x7
+
+#define GICV5_GSI_IWB_FRAME_ID                 GENMASK(28, 16)
+#define GICV5_GSI_IWB_WIRE                     GENMASK(15, 0)
+
 /*
  * Global Data structures and functions
  */