[PATCH v2 2/4] arm64: ras: Add Device Tree frontend
Umang Chheda <[email protected]> Mon, 20 Jul 2026 13:49:52 +0530
| Newsgroups | org.kernel.vger.linux-edac,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-acpi,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Add a Device Tree frontend for the ARM64 RAS driver, allowing it to be used on platforms without ACPI firmware. The error sources defined in DT are registered as arm64_ras platform devices at boot. The frontend produces the same software fwnode properties as the ACPI path, so the core driver probes DT and ACPI nodes identically without any firmware-specific knowledge. Signed-off-by: Umang Chheda <[email protected]> --- drivers/ras/arm64/Kconfig | 25 ++- drivers/ras/arm64/Makefile | 2 + drivers/ras/arm64/ras-of.c | 383 +++++++++++++++++++++++++++++++++++++ 3 files changed, 405 insertions(+), 5 deletions(-) create mode 100644 drivers/ras/arm64/ras-of.c diff --git a/drivers/ras/arm64/Kconfig b/drivers/ras/arm64/Kconfig index dcdeaa216d67..8bdb219bc90f 100644 --- a/drivers/ras/arm64/Kconfig +++ b/drivers/ras/arm64/Kconfig @@ -1,16 +1,31 @@ # SPDX-License-Identifier: GPL-2.0 # -# ARM Error Source Table Support +# ARM RAS driver # # Copyright (c) 2025, Alibaba Group. # +config ARM64_RAS_DT + bool "ARM64 RAS Device Tree support" + depends on ARM64_RAS_EXTN && OF + help + Enable Device Tree support for the ARM64 RAS driver. + + When selected, RAS error sources described in the Device Tree + (arm,ras-processor, arm,ras-smmu, arm,ras-gic etc) are registered + as platform devices at boot, allowing the driver to be used on + platforms without ACPI firmware. + config ARM64_RAS_DRIVER tristate "ARM64 RAS Driver" - depends on ARM64 && ACPI_AEST && RAS + depends on ARM64 && (ACPI_AEST || ARM64_RAS_DT) && RAS help - This is the RAS driver for the arm64 architecture. It depends on - the Arm Error Source Table (AEST) to provide basic register and - interrupt information. + This is the RAS driver for the arm64 architecture. It uses the + ARMv8 RAS extension register interface to discover, configure, + and handle hardware errors from processor caches, SMMUs, and GICs. + + On ACPI systems the error source topology is provided by the AEST + ACPI table (requires ACPI_AEST). On Device Tree systems it is + provided by "arm,ras-*" nodes in the DT root (requires ARM64_RAS_DT). If set, the kernel will report and process hardware errors. diff --git a/drivers/ras/arm64/Makefile b/drivers/ras/arm64/Makefile index 6897798f7314..aee2f9de37f6 100644 --- a/drivers/ras/arm64/Makefile +++ b/drivers/ras/arm64/Makefile @@ -7,3 +7,5 @@ arm64_ras-y += ras-sysfs.o arm64_ras-y += ras-inject.o arm64_ras-y += ras-cmn.o arm64_ras-y += ras-storm.o + +obj-$(CONFIG_ARM64_RAS_DT) += ras-of.o diff --git a/drivers/ras/arm64/ras-of.c b/drivers/ras/arm64/ras-of.c new file mode 100644 index 000000000000..e1aa8e13c077 --- /dev/null +++ b/drivers/ras/arm64/ras-of.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/acpi.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> + +#include <linux/acpi_aest.h> + +#undef pr_fmt +#define pr_fmt(fmt) "DT RAS: " fmt + +/* Maximum number of software fwnode properties per RAS node */ +#define RAS_OF_MAX_PROPS 20 + +static const unsigned long ras_of_type_processor __initconst = + ACPI_AEST_PROCESSOR_ERROR_NODE; +static const unsigned long ras_of_type_smmu __initconst = + ACPI_AEST_SMMU_ERROR_NODE; +static const unsigned long ras_of_type_gic __initconst = + ACPI_AEST_GIC_ERROR_NODE; + +static const struct of_device_id ras_of_match[] __initconst = { + { .compatible = "arm,ras-processor", .data = &ras_of_type_processor }, + { .compatible = "arm,ras-smmu", .data = &ras_of_type_smmu }, + { .compatible = "arm,ras-gic", .data = &ras_of_type_gic }, + { } +}; + +static const char * const __initconst ras_of_irq_res_names[] = { + [ACPI_AEST_NODE_FAULT_HANDLING] = AEST_FHI_NAME, + [ACPI_AEST_NODE_ERROR_RECOVERY] = AEST_ERI_NAME, +}; + +static const char * const __initconst ras_of_dt_irq_names[] = { + "fhi", "eri" +}; + +static int __init ras_of_build_node_data(struct device_node *np, + u8 node_type, int index, + int fhi_virq, + u8 **data_out, u32 *size_out) +{ + *data_out = NULL; + *size_out = 0; + + switch (node_type) { + case ACPI_AEST_PROCESSOR_ERROR_NODE: { + /* + * Allocate the processor header plus the cache sub-structure. + * resource_type is inferred from the presence of the 'cache' + * phandle: if present the resource is a cache, otherwise it + * is treated as a generic processor resource. + * flags (SHARED/GLOBAL) are inferred from the interrupt type: + * a PPI is per-PE (global); an SPI is shared across a cluster. + */ + size_t total = sizeof(struct acpi_aest_processor) + + sizeof(struct acpi_aest_processor_cache); + struct acpi_aest_processor_cache *c; + struct acpi_aest_processor *proc; + struct device_node *cache_np; + u8 pflags; + + proc = kzalloc(total, GFP_KERNEL); + if (!proc) + return -ENOMEM; + + cache_np = of_parse_phandle(np, "cache", 0); + if (cache_np) { + proc->resource_type = ACPI_AEST_CACHE_RESOURCE; + c = (struct acpi_aest_processor_cache *)(proc + 1); + /* + * alloc_ras_node_name() reads c->cache_reference as + * the name disambiguator for shared/global nodes. + */ + c->cache_reference = cache_np->phandle; + of_node_put(cache_np); + } else { + proc->resource_type = ACPI_AEST_GENERIC_RESOURCE; + } + + /* + * Infer scope from the FHI interrupt type: a PPI is + * per-PE (set GLOBAL); an SPI is cluster-shared (set SHARED). + * alloc_ras_node_name() uses flags to choose the name format. + */ + pflags = irq_is_percpu(fhi_virq) ? + ACPI_AEST_PROC_FLAG_GLOBAL : + ACPI_AEST_PROC_FLAG_SHARED; + proc->flags = pflags; + proc->processor_id = (u32)index; + + *data_out = (u8 *)proc; + *size_out = (u32)total; + break; + } + case ACPI_AEST_SMMU_ERROR_NODE: { + struct acpi_aest_smmu *smmu; + struct device_node *smmu_np; + + smmu = kzalloc_obj(smmu, GFP_KERNEL); + if (!smmu) + return -ENOMEM; + + smmu_np = of_parse_phandle(np, "iommus", 0); + if (smmu_np) { + smmu->iort_node_reference = smmu_np->phandle; + of_node_put(smmu_np); + } + + *data_out = (u8 *)smmu; + *size_out = sizeof(*smmu); + break; + } + case ACPI_AEST_GIC_ERROR_NODE: { + struct acpi_aest_gic *gic; + struct device_node *gic_np; + + gic = kzalloc_obj(gic, GFP_KERNEL); + if (!gic) + return -ENOMEM; + + gic_np = of_parse_phandle(np, "arm,gic-ref", 0); + if (gic_np) { + gic->instance_id = gic_np->phandle; + of_node_put(gic_np); + } + *data_out = (u8 *)gic; + *size_out = sizeof(*gic); + break; + } + default: + return -EINVAL; + } + + return 0; +} + +/* + * Determine the register access type from the DT node: + * no reg -> system-register access (ERRSELR_EL1 + ERX*_EL1) + * 1 range -> memory-mapped access + * 2+ ranges -> single-record memory-mapped access + */ +static u8 __init ras_of_interface_type(struct device_node *np) +{ + int addr_cells, size_cells, reg_len; + + if (!of_property_present(np, "reg")) + return ACPI_AEST_NODE_SYSTEM_REGISTER; + + addr_cells = of_n_addr_cells(np); + size_cells = of_n_size_cells(np); + if (addr_cells <= 0 || size_cells <= 0) + return ACPI_AEST_NODE_SYSTEM_REGISTER; + + reg_len = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (reg_len <= 0) + return ACPI_AEST_NODE_SYSTEM_REGISTER; + + if (reg_len <= addr_cells + size_cells) + return ACPI_AEST_NODE_MEMORY_MAPPED; + + return ACPI_AEST_NODE_SINGLE_RECORD_MEMORY_MAPPED; +} + +static resource_size_t __init ras_of_mem_size(u32 gfmt) +{ + switch (gfmt) { + case ACPI_AEST_NODE_GROUP_FORMAT_16K: + return SZ_16K; + case ACPI_AEST_NODE_GROUP_FORMAT_64K: + return SZ_64K; + default: + return SZ_4K; + } +} + +static u32 __init ras_of_virq_to_gsiv(int virq) +{ + struct irq_data *irqd = irq_get_irq_data(virq); + + return irqd ? (u32)irqd->hwirq : 0; +} + +static int __init ras_of_attach_fwnode(struct device_node *np, + struct platform_device *pdev, + u8 node_type, u8 itype, u32 gfmt, + int index, int fhi_virq, + u32 fhi_gsiv, u32 eri_gsiv) +{ + u64 rec_impl[14] = { }; + u64 stat_rep[14] = { }; + u64 addr_mode[14] = { }; + u64 err_group_base = 0, fault_inject_base = 0, irq_cfg_base = 0; + struct property_entry props[RAS_OF_MAX_PROPS] = { }; + struct resource res; + u8 *node_data = NULL; + u32 node_data_size = 0; + u32 nrec = 1; + int group_len, p = 0, i, ret; + + of_property_read_u32(np, "arm,num-records", &nrec); + + switch (gfmt) { + case ACPI_AEST_NODE_GROUP_FORMAT_16K: + group_len = 4; + break; + case ACPI_AEST_NODE_GROUP_FORMAT_64K: + group_len = 14; + break; + default: + group_len = 1; + break; + } + + of_property_read_u64_array(np, "arm,record-impl", rec_impl, group_len); + of_property_read_u64_array(np, "arm,status-reporting", stat_rep, group_len); + of_property_read_u64_array(np, "arm,addressing-mode", addr_mode, group_len); + + /* + * DT binding: bit=1 means record IS implemented. + * ras-core.c uses for_each_clear_bit() on record_implemented, so + * bit=0 means implemented internally. Invert before storing. + */ + for (i = 0; i < group_len; i++) + rec_impl[i] = ~rec_impl[i]; + + /* Named MMIO windows — only present on memory-mapped nodes */ + if (itype != ACPI_AEST_NODE_SYSTEM_REGISTER) { + int idx; + + idx = of_property_match_string(np, "reg-names", "err-group"); + if (idx >= 0 && !of_address_to_resource(np, idx, &res)) + err_group_base = res.start; + + idx = of_property_match_string(np, "reg-names", "fault-inject"); + if (idx >= 0 && !of_address_to_resource(np, idx, &res)) + fault_inject_base = res.start; + + idx = of_property_match_string(np, "reg-names", "irq-config"); + if (idx >= 0 && !of_address_to_resource(np, idx, &res)) + irq_cfg_base = res.start; + } + + ret = ras_of_build_node_data(np, node_type, index, fhi_virq, + &node_data, &node_data_size); + if (ret) + return ret; + + props[p++] = PROPERTY_ENTRY_U8("arm,node-type", node_type); + props[p++] = PROPERTY_ENTRY_U8("arm,interface-type", itype); + props[p++] = PROPERTY_ENTRY_U8("arm,group-format", (u8)gfmt); + props[p++] = PROPERTY_ENTRY_U32("arm,error-records-count", nrec); + props[p++] = PROPERTY_ENTRY_U32("arm,error-records-index", 0); + props[p++] = PROPERTY_ENTRY_U32("arm,interface-flags", 0); + props[p++] = PROPERTY_ENTRY_U64_ARRAY_LEN("arm,record-implemented", + rec_impl, group_len); + props[p++] = PROPERTY_ENTRY_U64_ARRAY_LEN("arm,status-reporting", + stat_rep, group_len); + props[p++] = PROPERTY_ENTRY_U64_ARRAY_LEN("arm,addressing-mode", + addr_mode, group_len); + props[p++] = PROPERTY_ENTRY_U64("arm,error-group-base", err_group_base); + props[p++] = PROPERTY_ENTRY_U64("arm,fault-inject-base", fault_inject_base); + props[p++] = PROPERTY_ENTRY_U64("arm,interrupt-config-base", irq_cfg_base); + props[p++] = PROPERTY_ENTRY_U32("arm,fhi-gsiv", fhi_gsiv); + props[p++] = PROPERTY_ENTRY_U32("arm,eri-gsiv", eri_gsiv); + + if (node_data && node_data_size) + props[p++] = PROPERTY_ENTRY_U8_ARRAY_LEN("arm,node-specific-data", + node_data, node_data_size); + + ret = device_create_managed_software_node(&pdev->dev, props, NULL); + + kfree(node_data); + return ret; +} + +static int __init ras_of_init_one_node(struct device_node *np, u8 node_type, + int index) +{ + struct resource res[AEST_MAX_INTERRUPT_PER_NODE + 1] = { }; + struct platform_device *pdev; + u32 gfmt = ACPI_AEST_NODE_GROUP_FORMAT_4K; + u32 gsiv[AEST_MAX_INTERRUPT_PER_NODE] = { }; + int virq[AEST_MAX_INTERRUPT_PER_NODE] = { }; + int nres = 0, ret, i; + u8 itype; + + itype = ras_of_interface_type(np); + of_property_read_u32(np, "arm,group-format", &gfmt); + + pdev = platform_device_alloc("arm64_ras", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + + if (itype != ACPI_AEST_NODE_SYSTEM_REGISTER) { + struct resource mmio_res; + + ret = of_address_to_resource(np, 0, &mmio_res); + if (ret) { + pr_err("node %pOF: missing 'reg' for MMIO interface\n", np); + goto err_put; + } + res[nres].name = AEST_NODE_NAME; + res[nres].start = mmio_res.start; + res[nres].end = mmio_res.start + ras_of_mem_size(gfmt) - 1; + res[nres].flags = IORESOURCE_MEM; + nres++; + } + + for (i = 0; i < AEST_MAX_INTERRUPT_PER_NODE; i++) { + int irq_num = of_irq_get_byname(np, ras_of_dt_irq_names[i]); + + if (irq_num <= 0) + continue; + + gsiv[i] = ras_of_virq_to_gsiv(irq_num); + virq[i] = irq_num; + + res[nres].name = ras_of_irq_res_names[i]; + res[nres].start = irq_num; + res[nres].end = irq_num; + res[nres].flags = IORESOURCE_IRQ; + nres++; + } + + ret = platform_device_add_resources(pdev, res, nres); + if (ret) + goto err_put; + + ret = ras_of_attach_fwnode(np, pdev, node_type, itype, gfmt, index, + virq[ACPI_AEST_NODE_FAULT_HANDLING], + gsiv[ACPI_AEST_NODE_FAULT_HANDLING], + gsiv[ACPI_AEST_NODE_ERROR_RECOVERY]); + if (ret) + goto err_put; + + ret = platform_device_add(pdev); + if (ret) + goto err_put; + + pr_debug("registered RAS node %pOF as arm64_ras.%d\n", np, pdev->id); + return 0; + +err_put: + platform_device_put(pdev); + return ret; +} + +static int __init ras_of_init(void) +{ + const struct of_device_id *match; + struct device_node *np; + int index = 0, ret; + + if (!acpi_disabled) + return 0; + + for_each_matching_node_and_match(np, ras_of_match, &match) { + u8 node_type = *(const unsigned long *)match->data; + + ret = ras_of_init_one_node(np, node_type, index++); + if (ret) { + pr_err("failed to register RAS node %pOF: %d\n", + np, ret); + of_node_put(np); + return ret; + } + } + + if (index) + pr_info("registered %d RAS error source(s) from DT\n", index); + + return 0; +} +subsys_initcall_sync(ras_of_init); -- 2.34.1