[PATCH v7 05/16] arm64: ras: Plumb AEST interrupts as platform IRQ resources

Ruidong Tian tianruidong at linux.alibaba.com
Tue Jun 2 00:15:28 PDT 2026


AEST describes one or more interrupts per error source for delivering
Fault Handling (FHI) and Error Recovery (ERI) events.

This change keeps the existing front-end / back-end split:

  - The ACPI front-end is the only place that understands AEST
    interrupt encodings; it registers each GSI and exposes the
    resulting Linux IRQs as named platform resources
    ("AEST:FHI" / "AEST:ERI").

  - The back-end looks up its IRQs by name and requests them
    according to routing type. SPIs are device-shared and use a
    normal shared handler. PPIs are inherently per-CPU, so the node
    is cloned into a percpu ras_node and the handler is installed
    via request_percpu_irq() with that percpu cookie, ensuring each
    CPU services its own copy of the source.

Signed-off-by: Ruidong Tian <tianruidong at linux.alibaba.com>
---
 drivers/acpi/arm64/aest.c    |  56 +++++++++++++++++-
 drivers/ras/arm64/ras-core.c | 109 +++++++++++++++++++++++++++++++++++
 drivers/ras/arm64/ras.h      |   2 +
 include/linux/acpi_aest.h    |   7 +++
 4 files changed, 172 insertions(+), 2 deletions(-)

diff --git a/drivers/acpi/arm64/aest.c b/drivers/acpi/arm64/aest.c
index 868013498abb..af03f4365cfa 100644
--- a/drivers/acpi/arm64/aest.c
+++ b/drivers/acpi/arm64/aest.c
@@ -15,6 +15,40 @@
 #undef pr_fmt
 #define pr_fmt(fmt) "ACPI AEST: " fmt
 
+static int acpi_aest_parse_irqs(struct platform_device *pdev,
+				struct acpi_aest_hdr *aest_hdr,
+				struct resource *res, int *res_idx)
+{
+	int i;
+	struct acpi_aest_node_interrupt_v2 *interrupt;
+	int trigger, irq;
+
+	interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt_v2, aest_hdr,
+				aest_hdr->node_interrupt_offset);
+	for (i = 0; i < aest_hdr->node_interrupt_count; i++, interrupt++) {
+		trigger = (interrupt->flags & AEST_INTERRUPT_MODE) ?
+				  ACPI_LEVEL_SENSITIVE :
+				  ACPI_EDGE_SENSITIVE;
+
+		irq = acpi_register_gsi(&pdev->dev, interrupt->gsiv, trigger,
+					ACPI_ACTIVE_HIGH);
+		if (irq <= 0) {
+			pr_err("failed to map AEST GSI %d\n", interrupt->gsiv);
+			return irq ? irq : -EINVAL;
+		}
+
+		res[*res_idx].start = irq;
+		res[*res_idx].end = irq;
+		res[*res_idx].flags = IORESOURCE_IRQ;
+		res[*res_idx].name = interrupt->type ? AEST_ERI_NAME :
+						       AEST_FHI_NAME;
+
+		(*res_idx)++;
+	}
+
+	return 0;
+}
+
 /*
  * Fill the per-AEST-entry inner properties (node-type / interface-type /
  * group-format / record bitmaps / register bases ...).
@@ -25,9 +59,11 @@ aest_init_node_props(struct acpi_aest_hdr *hdr, struct property_entry *props,
 {
 	struct acpi_aest_node_interface_header *interface;
 	struct acpi_aest_node_interface_common *common = NULL;
+	struct acpi_aest_node_interrupt_v2 *interrupt;
 	u64 *record_implemented = NULL;
 	u64 *status_reporting = NULL;
 	u64 *addressing_mode = NULL;
+	u32 fhi_gsiv = 0, eri_gsiv = 0;
 	int group_len = 0, i;
 	size_t len;
 
@@ -72,6 +108,15 @@ aest_init_node_props(struct acpi_aest_hdr *hdr, struct property_entry *props,
 		return -EINVAL;
 	}
 
+	interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt_v2, hdr,
+				 hdr->node_interrupt_offset);
+	for (i = 0; i < hdr->node_interrupt_count; i++, interrupt++) {
+		if (interrupt->type == ACPI_AEST_NODE_FAULT_HANDLING)
+			fhi_gsiv = interrupt->gsiv;
+		else if (interrupt->type == ACPI_AEST_NODE_ERROR_RECOVERY)
+			eri_gsiv = interrupt->gsiv;
+	}
+
 	if (interface->flags & AEST_XFACE_FLAG_ERROR_DEVICE) {
 		struct acpi_device *companion;
 		char uid[16];
@@ -112,6 +157,8 @@ aest_init_node_props(struct acpi_aest_hdr *hdr, struct property_entry *props,
 					   common->error_group_register_base);
 	props[(*p)++] = PROPERTY_ENTRY_U64("arm,fault-inject-base",
 					   common->fault_inject_register_base);
+	props[(*p)++] = PROPERTY_ENTRY_U32("arm,fhi-gsiv", fhi_gsiv);
+	props[(*p)++] = PROPERTY_ENTRY_U32("arm,eri-gsiv", eri_gsiv);
 
 	len = hdr->node_interface_offset - hdr->node_specific_offset;
 	props[(*p)++] =
@@ -124,7 +171,7 @@ aest_init_node_props(struct acpi_aest_hdr *hdr, struct property_entry *props,
 static int __init
 aest_create_node_fwnode(struct acpi_aest_hdr *hdr, struct platform_device *pdev)
 {
-	struct property_entry props[12] = { };
+	struct property_entry props[14] = { };
 	int p = 0;
 	int ret;
 
@@ -163,7 +210,8 @@ acpi_aest_alloc_pdev(struct acpi_aest_hdr *aest_hdr)
 	if (!pdev)
 		return ERR_PTR(-ENOMEM);
 
-	res = kcalloc(1, sizeof(*res), GFP_KERNEL);
+	res = kcalloc(AEST_MAX_INTERRUPT_PER_NODE + 1, sizeof(*res),
+		      GFP_KERNEL);
 	if (!res)
 		return ERR_PTR(-ENOMEM);
 
@@ -177,6 +225,10 @@ acpi_aest_alloc_pdev(struct acpi_aest_hdr *aest_hdr)
 		j++;
 	}
 
+	ret = acpi_aest_parse_irqs(pdev, aest_hdr, res, &j);
+	if (ret)
+		return ERR_PTR(ret);
+
 	ret = platform_device_add_resources(pdev, res, j);
 	if (ret)
 		return ERR_PTR(ret);
diff --git a/drivers/ras/arm64/ras-core.c b/drivers/ras/arm64/ras-core.c
index 1dd471376449..9520415df8cb 100644
--- a/drivers/ras/arm64/ras-core.c
+++ b/drivers/ras/arm64/ras-core.c
@@ -5,6 +5,7 @@
  * Copyright (c) 2025, Alibaba Group.
  */
 
+#include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/ras.h>
@@ -14,6 +15,8 @@
 #undef pr_fmt
 #define pr_fmt(fmt) "arm64_ras: " fmt
 
+static DEFINE_PER_CPU(struct ras_node, percpu_ras_node);
+
 static const char *const ras_node_name[] = {
 	[ACPI_AEST_PROCESSOR_ERROR_NODE] = "processor",
 	[ACPI_AEST_MEMORY_ERROR_NODE] = "memory",
@@ -42,6 +45,55 @@ const struct ras_group ras_group_config[] = {
 	},
 };
 
+static irqreturn_t ras_irq_func(int irq, void *input)
+{
+	struct ras_node *node = input;
+
+	return IRQ_HANDLED;
+}
+
+static int ras_register_irq(struct ras_node *node)
+{
+	int i, irq, ret;
+	char *irq_desc;
+
+	irq_desc = devm_kasprintf(node->dev, GFP_KERNEL, "%s.%s.",
+				  dev_driver_string(node->dev),
+				  node->name);
+	if (!irq_desc)
+		return -ENOMEM;
+
+	for (i = 0; i < AEST_MAX_INTERRUPT_PER_NODE; i++) {
+		irq = node->irq[i];
+
+		if (!irq)
+			continue;
+
+		if (irq_is_percpu_devid(irq)) {
+			ret = request_percpu_irq(irq, ras_irq_func, irq_desc,
+						 node->oncore_node);
+			if (ret)
+				goto free;
+		} else {
+			ret = devm_request_irq(node->dev, irq, ras_irq_func, IRQF_SHARED,
+					       irq_desc, node);
+			if (ret)
+				return ret;
+		}
+	}
+	return 0;
+
+free:
+	for (i = i - 1; i >= 0; i--) {
+		irq = node->irq[i];
+
+		if (irq_is_percpu_devid(irq))
+			free_percpu_irq(irq, node->oncore_node);
+	}
+
+	return ret;
+}
+
 static int ras_init_record(struct ras_record *record, int i, struct ras_node *node)
 {
 	record->name = devm_kasprintf(node->dev, GFP_KERNEL, "record%d", i);
@@ -249,6 +301,53 @@ static struct ras_node *ras_init_node(struct platform_device *pdev)
 	return node;
 }
 
+
+static int __setup_ppi(struct ras_node *node)
+{
+	int cpu;
+	struct ras_node *oncore_node;
+	size_t size;
+
+	node->oncore_node = &percpu_ras_node;
+	for_each_possible_cpu(cpu) {
+		oncore_node = per_cpu_ptr(&percpu_ras_node, cpu);
+		memcpy(oncore_node, node, sizeof(struct ras_node));
+
+		oncore_node->records = devm_kcalloc(
+			node->dev, oncore_node->record_count,
+			sizeof(struct ras_record), GFP_KERNEL);
+		if (!oncore_node->records)
+			return -ENOMEM;
+
+		size = oncore_node->record_count *
+			sizeof(struct ras_record);
+		memcpy(oncore_node->records, node->records, size);
+
+		ras_node_dbg(node, "Init node on CPU%d.\n", cpu);
+	}
+
+	return 0;
+}
+
+static int ras_setup_irq(struct platform_device *pdev, struct ras_node *node)
+{
+	int fhi_irq, eri_irq;
+
+	fhi_irq = platform_get_irq_byname_optional(pdev, AEST_FHI_NAME);
+	if (fhi_irq > 0)
+		node->irq[ACPI_AEST_NODE_FAULT_HANDLING] = fhi_irq;
+
+	eri_irq = platform_get_irq_byname_optional(pdev, AEST_ERI_NAME);
+	if (eri_irq > 0)
+		node->irq[ACPI_AEST_NODE_ERROR_RECOVERY] = eri_irq;
+
+	/* Allocate and initialise the percpu device pointer for PPI */
+	if (irq_is_percpu(fhi_irq) || irq_is_percpu(eri_irq))
+		return __setup_ppi(node);
+
+	return 0;
+}
+
 static int arm64_ras_probe(struct platform_device *pdev)
 {
 	int ret;
@@ -263,6 +362,16 @@ static int arm64_ras_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	ret = ras_setup_irq(pdev, node);
+	if (ret)
+		return ret;
+
+	ret = ras_register_irq(node);
+	if (ret) {
+		ras_node_err(node, "register irq failed\n");
+		return ret;
+	}
+
 	platform_set_drvdata(pdev, node);
 
 	return 0;
diff --git a/drivers/ras/arm64/ras.h b/drivers/ras/arm64/ras.h
index da03593e5f7f..b64eae59b6ac 100644
--- a/drivers/ras/arm64/ras.h
+++ b/drivers/ras/arm64/ras.h
@@ -85,6 +85,7 @@ struct ras_node {
 
 	struct device *dev;
 	const struct ras_group *group;
+	struct ras_node __percpu *oncore_node;
 
 	void __iomem *base;
 	void __iomem *errgsr;
@@ -124,6 +125,7 @@ struct ras_node {
 	u8 type;
 	u8 access_type;
 	u8 group_format;
+	u32 irq[AEST_MAX_INTERRUPT_PER_NODE];
 };
 
 #define CASE_READ(res, x)                           \
diff --git a/include/linux/acpi_aest.h b/include/linux/acpi_aest.h
index df6369bcc96b..a462895a7b5a 100644
--- a/include/linux/acpi_aest.h
+++ b/include/linux/acpi_aest.h
@@ -6,6 +6,13 @@
 
 /* AEST resource name */
 #define AEST_NODE_NAME "AEST:NODE"
+#define AEST_FHI_NAME "AEST:FHI"
+#define AEST_ERI_NAME "AEST:ERI"
+
+/* AEST interrupt */
+#define AEST_INTERRUPT_MODE BIT(0)
+
+#define AEST_MAX_INTERRUPT_PER_NODE 2
 
 /* AEST interface */
 #define AEST_XFACE_FLAG_SHARED		BIT(0)
-- 
2.51.2.612.gdc70283dfc




More information about the linux-arm-kernel mailing list