[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