* Re: [PATCH v2 1/2] ACPI/AEST: Initial AEST driver
2024-03-21 2:53 ` [PATCH v2 1/2] ACPI/AEST: Initial AEST driver Ruidong Tian
@ 2024-03-21 3:52 ` Baolin Wang
2024-04-09 7:37 ` Ruidong Tian
2024-03-27 5:46 ` Shuai Xue
2024-05-10 5:25 ` Tomohiro Misono (Fujitsu)
2 siblings, 1 reply; 7+ messages in thread
From: Baolin Wang @ 2024-03-21 3:52 UTC (permalink / raw)
To: Ruidong Tian, catalin.marinas, will, lpieralisi, guohanjun,
sudeep.holla, xueshuai, linux-kernel, linux-acpi,
linux-arm-kernel, rafael, lenb, tony.luck, bp, linux-edac
Cc: tianruidond, Tyler Baicar
On 2024/3/21 10:53, Ruidong Tian wrote:
> From: Tyler Baicar <baicar@os.amperecomputing.com>
>
> Add support for parsing the ARM Error Source Table and basic handling of
> errors reported through both memory mapped and system register interfaces.
>
> Assume system register interfaces are only registered with private
> peripheral interrupts (PPIs); otherwise there is no guarantee the
> core handling the error is the core which took the error and has the
> syndrome info in its system registers.
>
> In kernel-first mode, all configuration is controlled by kernel, include
> CE ce_threshold and interrupt enable/disable.
>
> All detected errors will be processed as follow:
> - CE, DE: use a workqueue to log this hardware errors.
> - UER, UEO: log it and call memory_failure in workquee.
> - UC, UEU: panic in irq context.
>
> Signed-off-by: Tyler Baicar <baicar@os.amperecomputing.com>
> Signed-off-by: Ruidong Tian <tianruidong@linux.alibaba.com>
> ---
> MAINTAINERS | 11 +
> arch/arm64/include/asm/ras.h | 71 +++
> drivers/acpi/arm64/Kconfig | 10 +
> drivers/acpi/arm64/Makefile | 1 +
> drivers/acpi/arm64/aest.c | 834 +++++++++++++++++++++++++++++++++++
> include/linux/acpi_aest.h | 92 ++++
> include/linux/cpuhotplug.h | 1 +
> 7 files changed, 1020 insertions(+)
> create mode 100644 arch/arm64/include/asm/ras.h
> create mode 100644 drivers/acpi/arm64/aest.c
> create mode 100644 include/linux/acpi_aest.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dd5de540ec0b..34900d4bb677 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -330,6 +330,17 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Maintained
> F: drivers/acpi/arm64
>
> +ACPI AEST
> +M: Tyler Baicar <baicar@os.amperecomputing.com>
> +M: Ruidong Tian <tianruidond@linux.alibaba.com>
> +L: linux-acpi@vger.kernel.org
> +L: linux-arm-kernel@lists.infradead.org
> +S: Supported
> +F: arch/arm64/include/asm/ras.h
> +F: drivers/acpi/arm64/aest.c
> +F: include/linux/acpi_aest.h
> +
> +
> ACPI FOR RISC-V (ACPI/riscv)
> M: Sunil V L <sunilvl@ventanamicro.com>
> L: linux-acpi@vger.kernel.org
> diff --git a/arch/arm64/include/asm/ras.h b/arch/arm64/include/asm/ras.h
> new file mode 100644
> index 000000000000..04667f0de30f
> --- /dev/null
> +++ b/arch/arm64/include/asm/ras.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __ASM_RAS_H
> +#define __ASM_RAS_H
> +
> +#include <linux/types.h>
> +#include <linux/bits.h>
> +
> +/* ERR<n>FR */
> +#define ERR_FR_RP BIT(15)
> +#define ERR_FR_CEC GENMASK_ULL(14, 12)
> +
> +#define ERR_FR_RP_SINGLE_COUNTER 0
> +#define ERR_FR_RP_DOUBLE_COUNTER 1
> +
> +#define ERR_FR_CEC_0B_COUNTER 0
> +#define ERR_FR_CEC_8B_COUNTER BIT(1)
> +#define ERR_FR_CEC_16B_COUNTER BIT(2)
> +
> +/* ERR<n>STATUS */
> +#define ERR_STATUS_AV BIT(31)
> +#define ERR_STATUS_V BIT(30)
> +#define ERR_STATUS_UE BIT(29)
> +#define ERR_STATUS_ER BIT(28)
> +#define ERR_STATUS_OF BIT(27)
> +#define ERR_STATUS_MV BIT(26)
> +#define ERR_STATUS_CE (BIT(25) | BIT(24))
> +#define ERR_STATUS_DE BIT(23)
> +#define ERR_STATUS_PN BIT(22)
> +#define ERR_STATUS_UET (BIT(21) | BIT(20))
> +#define ERR_STATUS_CI BIT(19)
> +#define ERR_STATUS_IERR GENMASK_ULL(15, 8)
> +#define ERR_STATUS_SERR GENMASK_ULL(7, 0)
> +
> +/* These bit is write-one-to-clear */
> +#define ERR_STATUS_W1TC (ERR_STATUS_AV | ERR_STATUS_V | ERR_STATUS_UE | \
> + ERR_STATUS_ER | ERR_STATUS_OF | ERR_STATUS_MV | \
> + ERR_STATUS_CE | ERR_STATUS_DE | ERR_STATUS_PN | \
> + ERR_STATUS_UET | ERR_STATUS_CI)
> +
> +#define ERR_STATUS_UET_UC 0
> +#define ERR_STATUS_UET_UEU 1
> +#define ERR_STATUS_UET_UER 2
> +#define ERR_STATUS_UET_UEO 3
> +
> +/* ERR<n>CTLR */
> +#define ERR_CTLR_FI BIT(3)
> +#define ERR_CTLR_UI BIT(2)
> +
> +/* ERR<n>ADDR */
> +#define ERR_ADDR_AI BIT(61)
> +#define ERR_ADDR_PADDR GENMASK_ULL(55, 0)
> +
> +/* ERR<n>MISC0 */
> +
> +/* ERR<n>FR.CEC == 0b010, ERR<n>FR.RP == 0 */
> +#define ERR_MISC0_8B_OF BIT(39)
> +#define ERR_MISC0_8B_CEC GENMASK_ULL(38, 32)
> +
> +/* ERR<n>FR.CEC == 0b100, ERR<n>FR.RP == 0 */
> +#define ERR_MISC0_16B_OF BIT(47)
> +#define ERR_MISC0_16B_CEC GENMASK_ULL(46, 32)
> +
> +struct ras_ext_regs {
> + u64 err_fr;
> + u64 err_ctlr;
> + u64 err_status;
> + u64 err_addr;
> + u64 err_misc[4];
> +};
> +
> +#endif /* __ASM_RAS_H */
> diff --git a/drivers/acpi/arm64/Kconfig b/drivers/acpi/arm64/Kconfig
> index b3ed6212244c..639db671c5cf 100644
> --- a/drivers/acpi/arm64/Kconfig
> +++ b/drivers/acpi/arm64/Kconfig
> @@ -21,3 +21,13 @@ config ACPI_AGDI
>
> config ACPI_APMT
> bool
> +
> +config ACPI_AEST
> + bool "ARM Error Source Table Support"
> +
> + help
> + The Arm Error Source Table (AEST) provides details on ACPI
> + extensions that enable kernel-first handling of errors in a
> + system that supports the Armv8 RAS extensions.
> +
> + If set, the kernel will report and log hardware errors.
> diff --git a/drivers/acpi/arm64/Makefile b/drivers/acpi/arm64/Makefile
> index 143debc1ba4a..b5b740058c46 100644
> --- a/drivers/acpi/arm64/Makefile
> +++ b/drivers/acpi/arm64/Makefile
> @@ -5,3 +5,4 @@ obj-$(CONFIG_ACPI_GTDT) += gtdt.o
> obj-$(CONFIG_ACPI_APMT) += apmt.o
> obj-$(CONFIG_ARM_AMBA) += amba.o
> obj-y += dma.o init.o
> +obj-$(CONFIG_ACPI_AEST) += aest.o
> diff --git a/drivers/acpi/arm64/aest.c b/drivers/acpi/arm64/aest.c
> new file mode 100644
> index 000000000000..ab17aa5f5997
> --- /dev/null
> +++ b/drivers/acpi/arm64/aest.c
> @@ -0,0 +1,834 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ARM Error Source Table Support
> + *
> + * Copyright (c) 2021, Ampere Computing LLC
> + * Copyright (c) 2021-2024, Alibaba Group.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/acpi_aest.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/kernel.h>
> +#include <linux/genalloc.h>
> +#include <linux/llist.h>
> +#include <acpi/actbl.h>
> +#include <asm/ras.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "ACPI AEST: " fmt
> +
> +#define CASE_READ(res, x) \
> + case (x): { \
> + res = read_sysreg_s(SYS_##x##_EL1); \
> + break; \
> + }
> +
> +#define CASE_WRITE(val, x) \
> + case (x): { \
> + write_sysreg_s((val), SYS_##x##_EL1); \
> + break; \
> + }
> +
> +#define for_each_implemented_record(index, node) \
> + for ((index) = node->interface.record_start; \
> + (index) < node->interface.record_end; \
> + (index)++)
> +
> +#define AEST_LOG_PREFIX_BUFFER 64
> +
> +/*
> + * This memory pool is only to be used to save AEST node in AEST irq context.
> + * There can be 500 AEST node at most.
> + */
> +#define AEST_NODE_ALLOCED_MAX 500
> +
> +static struct acpi_table_header *aest_table;
> +
> +static struct aest_node __percpu **aest_ppi_data;
> +
> +static int *ppi_irqs;
> +static u8 num_ppi;
> +static u8 ppi_idx;
> +
> +static struct work_struct aest_work;
> +
> +static struct gen_pool *aest_node_pool;
> +static struct llist_head aest_node_llist;
> +
> +static u64 aest_sysreg_read(u64 __unused, u32 offset)
> +{
> + u64 res;
> +
> + switch (offset) {
> + CASE_READ(res, ERXFR)
> + CASE_READ(res, ERXCTLR)
> + CASE_READ(res, ERXSTATUS)
> + CASE_READ(res, ERXADDR)
> + CASE_READ(res, ERXMISC0)
> + CASE_READ(res, ERXMISC1)
> + CASE_READ(res, ERXMISC2)
> + CASE_READ(res, ERXMISC3)
> + default :
> + res = 0;
> + }
> + return res;
> +}
> +
> +static void aest_sysreg_write(u64 base, u32 offset, u64 val)
> +{
> + switch (offset) {
> + CASE_WRITE(val, ERXFR)
> + CASE_WRITE(val, ERXCTLR)
> + CASE_WRITE(val, ERXSTATUS)
> + CASE_WRITE(val, ERXADDR)
> + CASE_WRITE(val, ERXMISC0)
> + CASE_WRITE(val, ERXMISC1)
> + CASE_WRITE(val, ERXMISC2)
> + CASE_WRITE(val, ERXMISC3)
> + default :
> + return;
> + }
> +}
> +
> +static u64 aest_iomem_read(u64 base, u32 offset)
> +{
> + return readq_relaxed((void *)(base + offset));
> +}
> +
> +static void aest_iomem_write(u64 base, u32 offset, u64 val)
> +{
> + writeq_relaxed(val, (void *)(base + offset));
> +}
> +
> +static void aest_print(struct aest_node_llist *lnode)
> +{
> + static atomic_t seqno = { 0 };
> + unsigned int curr_seqno;
> + char pfx_seq[AEST_LOG_PREFIX_BUFFER];
> + int index;
> + struct ras_ext_regs *regs;
> +
> + curr_seqno = atomic_inc_return(&seqno);
> + snprintf(pfx_seq, sizeof(pfx_seq), "{%u}" HW_ERR, curr_seqno);
> + pr_info("%sHardware error from %s\n", pfx_seq, lnode->node_name);
> +
> + switch (lnode->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + pr_err("%s Error from CPU%d\n", pfx_seq, lnode->id0);
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + pr_err("%s Error from memory at SRAT proximity domain 0x%x\n",
> + pfx_seq, lnode->id0);
> + break;
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + pr_err("%s Error from SMMU IORT node 0x%x subcomponent 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + pr_err("%s Error from vendor hid 0x%x uid 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + case ACPI_AEST_GIC_ERROR_NODE:
> + pr_err("%s Error from GIC type 0x%x instance 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + default:
> + pr_err("%s Unknown AEST node type\n", pfx_seq);
> + return;
> + }
> +
> + index = lnode->index;
> + regs = lnode->regs;
> +
> + pr_err("%s ERR%uFR: 0x%llx\n", pfx_seq, index, regs->err_fr);
> + pr_err("%s ERR%uCTRL: 0x%llx\n", pfx_seq, index, regs->err_ctlr);
> + pr_err("%s ERR%uSTATUS: 0x%llx\n", pfx_seq, index, regs->err_status);
> + if (regs->err_status & ERR_STATUS_AV)
> + pr_err("%s ERR%uADDR: 0x%llx\n", pfx_seq, index, regs->err_addr);
> +
> + if (regs->err_status & ERR_STATUS_MV) {
> + pr_err("%s ERR%uMISC0: 0x%llx\n", pfx_seq, index, regs->err_misc[0]);
> + pr_err("%s ERR%uMISC1: 0x%llx\n", pfx_seq, index, regs->err_misc[1]);
> + pr_err("%s ERR%uMISC2: 0x%llx\n", pfx_seq, index, regs->err_misc[2]);
> + pr_err("%s ERR%uMISC3: 0x%llx\n", pfx_seq, index, regs->err_misc[3]);
> + }
> +}
> +
> +static void aest_handle_memory_failure(struct aest_node_llist *lnode)
> +{
> + unsigned long pfn;
> + u64 addr;
> +
> + if (test_bit(lnode->index, &lnode->addressing_mode) ||
> + (lnode->regs->err_addr & ERR_ADDR_AI))
> + return;
> +
> + addr = lnode->regs->err_addr & (1UL << CONFIG_ARM64_PA_BITS);
> + pfn = PHYS_PFN(addr);
> +
> + if (!pfn_valid(pfn)) {
> + pr_warn(HW_ERR "Invalid physical address: %#llx\n", addr);
> + return;
> + }
> +
> + memory_failure(pfn, 0);
> +}
> +
> +static void aest_node_pool_process(struct work_struct *__unused)
> +{
> + struct llist_node *head;
> + struct aest_node_llist *lnode, *tmp;
> + u64 status;
> +
> + head = llist_del_all(&aest_node_llist);
> + if (!head)
> + return;
> +
> + head = llist_reverse_order(head);
> + llist_for_each_entry_safe(lnode, tmp, head, llnode) {
> + aest_print(lnode);
> +
> + status = lnode->regs->err_status;
> + if ((status & ERR_STATUS_UE) &&
> + (status & ERR_STATUS_UET) > ERR_STATUS_UET_UEU)
> + aest_handle_memory_failure(lnode);
> + gen_pool_free(aest_node_pool, (unsigned long)lnode,
> + sizeof(*lnode));
> + }
> +}
> +
> +static int aest_node_gen_pool_add(struct aest_node *node, int index,
> + struct ras_ext_regs *regs)
> +{
> + struct aest_node_llist *list;
> +
> + if (!aest_node_pool)
> + return -EINVAL;
> +
> + list = (void *)gen_pool_alloc(aest_node_pool, sizeof(*list));
> + if (!list)
> + return -ENOMEM;
> +
> + list->type = node->type;
> + list->node_name = node->name;
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + list->id0 = node->spec.processor.processor_id;
> + if (node->spec.processor.flags & (ACPI_AEST_PROC_FLAG_SHARED |
> + ACPI_AEST_PROC_FLAG_GLOBAL))
> + list->id0 = smp_processor_id();
> +
> + list->id1 = node->spec.processor.resource_type;
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + list->id0 = node->spec.memory.srat_proximity_domain;
> + break;
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + list->id0 = node->spec.smmu.iort_node_reference;
> + list->id1 = node->spec.smmu.subcomponent_reference;
> + break;
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + list->id0 = node->spec.vendor.acpi_hid;
> + list->id1 = node->spec.vendor.acpi_uid;
> + break;
> + case ACPI_AEST_GIC_ERROR_NODE:
> + list->id0 = node->spec.gic.interface_type;
> + list->id1 = node->spec.gic.instance_id;
> + break;
> + default:
> + list->id0 = 0;
> + list->id1 = 0;
> + }
> +
> + list->regs = regs;
> + list->index = index;
> + list->addressing_mode = node->interface.addressing_mode;
> + llist_add(&list->llnode, &aest_node_llist);
> +
> + return 0;
> +}
> +
> +static int aest_node_pool_init(void)
> +{
> + unsigned long addr, size;
> + int rc;
> +
> + if (aest_node_pool)
> + return 0;
> +
> + size = ilog2(sizeof(struct aest_node_llist));
> + aest_node_pool = gen_pool_create(size, -1);
> + if (!aest_node_pool)
> + return -ENOMEM;
> +
> + addr = (unsigned long)vmalloc(PAGE_ALIGN(size * AEST_NODE_ALLOCED_MAX));
> + if (!addr)
> + goto err_pool_alloc;
> +
> + rc = gen_pool_add(aest_node_pool, addr, size, -1);
> + if (rc)
> + goto err_pool_add;
> +
> + return 0;
> +
> +err_pool_add:
> + vfree((void *)addr);
> +
> +err_pool_alloc:
> + gen_pool_destroy(aest_node_pool);
> +
> + return -ENOMEM;
> +}
> +
> +static void aest_log(struct aest_node *node, int index, struct ras_ext_regs *regs)
> +{
> + if (!aest_node_gen_pool_add(node, index, regs))
> + schedule_work(&aest_work);
> +}
> +
> +/*
> + * Each PE may has multi error record, you must selects an error record to
> + * be accessed through the Error Record System registers.
> + */
> +static inline void aest_select_record(struct aest_node *node, int i)
> +{
> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER) {
> + write_sysreg_s(i, SYS_ERRSELR_EL1);
> + isb();
> + }
> +}
> +
> +/* Ensure all writes has taken effect. */
> +static inline void aest_sync(struct aest_node *node)
> +{
> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER)
> + isb();
> +}
> +
> +static int aest_proc(struct aest_node *node)
> +{
> + struct ras_ext_regs regs = {0};
> + struct aest_access *access;
> + int i, count = 0;
> + u64 regs_p;
> +
> + for_each_implemented_record(i, node) {
> +
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> +
> + access = node->access;
> + regs_p = (u64)&node->interface.regs[i];
> +
> + regs.err_status = access->read(regs_p, ERXSTATUS);
> + if (!(regs.err_status & ERR_STATUS_V))
> + continue;
> +
> + count++;
> +
> + if (regs.err_status & ERR_STATUS_AV)
> + regs.err_addr = access->read(regs_p, ERXADDR);
> +
> + regs.err_fr = access->read(regs_p, ERXFR);
> + regs.err_ctlr = access->read(regs_p, ERXCTLR);
> +
> + if (regs.err_status & ERR_STATUS_MV) {
> + regs.err_misc[0] = access->read(regs_p, ERXMISC0);
> + regs.err_misc[1] = access->read(regs_p, ERXMISC1);
> + regs.err_misc[2] = access->read(regs_p, ERXMISC2);
> + regs.err_misc[3] = access->read(regs_p, ERXMISC3);
> + }
> +
> + if (node->interface.flags & ACPI_AEST_INTERFACE_CLEAR_MISC) {
> + access->write(regs_p, ERXMISC0, 0);
> + access->write(regs_p, ERXMISC1, 0);
> + access->write(regs_p, ERXMISC2, 0);
> + access->write(regs_p, ERXMISC3, 0);
> + } else
> + access->write(regs_p, ERXMISC0,
> + node->interface.ce_threshold[i]);
> +
> + aest_log(node, i, ®s);
> +
> + /* panic if unrecoverable and uncontainable error encountered */
> + if ((regs.err_status & ERR_STATUS_UE) &&
> + (regs.err_status & ERR_STATUS_UET) < ERR_STATUS_UET_UER)
> + panic("AEST: unrecoverable error encountered");
> +
> + /* Write-one-to-clear the bits we've seen */
> + regs.err_status &= ERR_STATUS_W1TC;
> +
> + /* Multi bit filed need to write all-ones to clear. */
> + if (regs.err_status & ERR_STATUS_CE)
> + regs.err_status |= ERR_STATUS_CE;
> +
> + /* Multi bit filed need to write all-ones to clear. */
> + if (regs.err_status & ERR_STATUS_UET)
> + regs.err_status |= ERR_STATUS_UET;
> +
> + access->write(regs_p, ERXSTATUS, regs.err_status);
> +
> + aest_sync(node);
> + }
> +
> + return count;
> +}
> +
> +static irqreturn_t aest_irq_func(int irq, void *input)
> +{
> + struct aest_node *node = input;
> +
> + if (aest_proc(node))
> + return IRQ_HANDLED;
> +
> + return IRQ_NONE;
> +}
> +
> +static int __init aest_register_gsi(u32 gsi, int trigger, void *data,
> + irq_handler_t aest_irq_func)
> +{
> + int cpu, irq;
> +
> + irq = acpi_register_gsi(NULL, gsi, trigger, ACPI_ACTIVE_HIGH);
> +
> + if (irq == -EINVAL) {
> + pr_err("failed to map AEST GSI %d\n", gsi);
> + return -EINVAL;
> + }
IMO, should be:
if (irq < 0) {
pr_err("failed to map AEST GSI %d\n", gsi);
return irq;
}
> +
> + if (irq_is_percpu_devid(irq)) {
> + ppi_irqs[ppi_idx] = irq;
> + for_each_possible_cpu(cpu) {
> + memcpy(per_cpu_ptr(aest_ppi_data[ppi_idx], cpu), data,
> + sizeof(struct aest_node));
> + }
> + if (request_percpu_irq(irq, aest_irq_func, "AEST",
> + aest_ppi_data[ppi_idx++])) {
> + pr_err("failed to register AEST IRQ %d\n", irq);
> + return -EINVAL;
Do not override the error number.
> + }
> + } else {
> + if (request_irq(irq, aest_irq_func, IRQF_SHARED, "AEST",
> + data)) {
> + pr_err("failed to register AEST IRQ %d\n", irq);
> + return -EINVAL;
ditto.
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init aest_init_interrupts(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + struct acpi_aest_node_interrupt *interrupt;
> + int i, trigger, ret = 0, err_ctlr, regs_p;
> +
> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, hdr,
> + hdr->node_interrupt_offset);
> +
> + for (i = 0; i < hdr->node_interrupt_count; i++, interrupt++) {
> + trigger = (interrupt->flags & AEST_INTERRUPT_MODE) ?
> + ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
> + if (aest_register_gsi(interrupt->gsiv, trigger, node,
> + aest_irq_func))
> + ret = -EINVAL;
Do not override the error number.
> + }
> +
> + /* Ensure RAS interrupt is enabled */
> + for_each_implemented_record(i, node) {
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> +
> + regs_p = (u64)&node->interface.regs[i];
> +
> + err_ctlr = node->access->read(regs_p, ERXCTLR);
> +
> + if (interrupt->type == ACPI_AEST_NODE_FAULT_HANDLING)
> + err_ctlr |= ERR_CTLR_FI;
> + if (interrupt->type == ACPI_AEST_NODE_ERROR_RECOVERY)
> + err_ctlr |= ERR_CTLR_UI;
> +
> + node->access->write(regs_p, ERXCTLR, err_ctlr);
> +
> + aest_sync(node);
> + }
> +
> + return ret;
> +}
> +
> +static void __init set_aest_node_name(struct aest_node *node)
> +{
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + node->name = kasprintf(GFP_KERNEL, "AEST-CPU%d",
> + node->spec.processor.processor_id);
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + case ACPI_AEST_GIC_ERROR_NODE:
> + node->name = kasprintf(GFP_KERNEL, "AEST-%llx",
> + node->interface.phy_addr);
> + break;
> + default:
> + node->name = kasprintf(GFP_KERNEL, "AEST-Unkown-Node");
IMO, better to check the return value for memory allocation.
> + }
> +}
> +
> +/* access type is decided by AEST interface type. */
> +static struct aest_access aest_access[] = {
> + [ACPI_AEST_NODE_SYSTEM_REGISTER] = {
> + .read = aest_sysreg_read,
> + .write = aest_sysreg_write,
> + },
> +
> + [ACPI_AEST_NODE_MEMORY_MAPPED] = {
> + .read = aest_iomem_read,
> + .write = aest_iomem_write,
> + },
> + { }
> +};
> +
> +/* In kernel-first mode, kernel will report every CE by default. */
> +static void __init aest_set_ce_threshold(struct aest_node *node)
> +{
> + u64 regs_p, err_fr, err_fr_cec, err_fr_rp, err_misc0, ce_threshold;
> + int i;
> +
> + for_each_implemented_record(i, node) {
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> + regs_p = (u64)&node->interface.regs[i];
> +
> + err_fr = node->access->read(regs_p, ERXFR);
> + err_fr_cec = FIELD_GET(ERR_FR_CEC, err_fr);
> + err_fr_rp = FIELD_GET(ERR_FR_RP, err_fr);
> + err_misc0 = node->access->read(regs_p, ERXMISC0);
> +
> + if (err_fr_cec == ERR_FR_CEC_0B_COUNTER)
> + pr_debug("%s-%d do not support CE threshold!\n",
> + node->name, i);
> + else if (err_fr_cec == ERR_FR_CEC_8B_COUNTER &&
> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
> + pr_debug("%s-%d support 8 bit CE threshold!\n",
> + node->name, i);
> + ce_threshold = err_misc0 | ERR_MISC0_8B_CEC;
> + } else if (err_fr_cec == ERR_FR_CEC_16B_COUNTER &&
> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
> + pr_debug("%s-%d support 16 bit CE threshold!\n",
> + node->name, i);
> + ce_threshold = err_misc0 | ERR_MISC0_16B_CEC;
> + } else
> + pr_debug("%s-%d do not support double counter yet!\n",
> + node->name, i);
Change to 'switch' statement will be more readable.
> +
> + node->access->write(regs_p, ERXMISC0, ce_threshold);
> + node->interface.ce_threshold[i] = ce_threshold;
> +
> + aest_sync(node);
> + }
> +}
> +
> +static int __init aest_init_interface(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + struct acpi_aest_node_interface *interface;
> + struct resource *res;
> + int size;
> +
> + interface = ACPI_ADD_PTR(struct acpi_aest_node_interface, hdr,
> + hdr->node_interface_offset);
> +
> + if (interface->type >= ACPI_AEST_XFACE_RESERVED) {
> + pr_err("invalid interface type: %d\n", interface->type);
> + return -EINVAL;
> + }
> +
> + node->interface.type = interface->type;
> + node->interface.phy_addr = interface->address;
> + node->interface.record_start = interface->error_record_index;
> + node->interface.record_end = interface->error_record_index +
> + interface->error_record_count;
> + node->interface.flags = interface->flags;
> + node->interface.record_implemented = interface->error_record_implemented;
> + node->interface.status_reporting = interface->error_status_reporting;
> + node->interface.addressing_mode = interface->addressing_mode;
> + node->access = &aest_access[interface->type];
> +
> + /*
> + * Currently SR based handling is done through the architected
> + * discovery exposed through SRs. That may change in the future
> + * if there is supplemental information in the AEST that is
> + * needed.
> + */
> + if (interface->type == ACPI_AEST_NODE_SYSTEM_REGISTER)
> + return 0;
> +
> + res = kzalloc(sizeof(struct resource), GFP_KERNEL);
> + if (!res)
> + return -ENOMEM;
> +
> + size = interface->error_record_count * sizeof(struct ras_ext_regs);
> + res->name = "AEST";
> + res->start = interface->address;
> + res->end = res->start + size;
> + res->flags = IORESOURCE_MEM;
> +
> + if (insert_resource(&iomem_resource, res)) {
> + pr_notice("request region conflict with %s\n",
> + res->name);
> + }
> +
> + node->interface.regs = ioremap(res->start, size);
> + if (!node->interface.regs) {
> + pr_err("Ioremap for %s failed!\n", node->name);
> + kfree(res);
> + return -EINVAL;
return -ENOMEM;
> + }
> +
> + node->interface.ce_threshold = kzalloc(sizeof(u64) *
> + interface->error_record_count, GFP_KERNEL);
> + if (!node->interface.ce_threshold)
kfree(res) and iounmap()
> + return -ENOMEM;
> +
> + aest_set_ce_threshold(node);
> +
> + return 0;
> +}
> +
> +static int __init aest_init_common(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + int ret;
> +
> + set_aest_node_name(node);
> +
> + ret = aest_init_interface(hdr, node);
> + if (ret) {
> + pr_err("failed to init interface\n");
> + return ret;
I did not see you free the node->name before returning an error.
> + }
> +
> + return aest_init_interrupts(hdr, node);
> +}
> +
> +static int __init aest_init_node_default(struct acpi_aest_hdr *hdr)
> +{
> + struct aest_node *node;
> + union aest_node_spec *node_spec;
> + int ret;
> +
> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = hdr->type;
> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
> + hdr->node_specific_offset);
> +
> + memcpy(&node->spec, node_spec,
> + hdr->node_interface_offset - hdr->node_specific_offset);
> +
> + ret = aest_init_common(hdr, node);
> + if (ret)
> + kfree(node);
> +
> + return ret;
> +}
> +
> +static int __init aest_init_processor_node(struct acpi_aest_hdr *hdr)
> +{
> + struct aest_node *node;
> + union aest_node_spec *node_spec;
> + union aest_node_processor *proc;
> + int ret;
> +
> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = hdr->type;
> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
> + hdr->node_specific_offset);
> +
> + memcpy(&node->spec, node_spec,
> + hdr->node_interface_offset - hdr->node_specific_offset);
> +
> + proc = ACPI_ADD_PTR(union aest_node_processor, node_spec,
> + sizeof(acpi_aest_processor));
> +
> + switch (node->spec.processor.resource_type) {
> + case ACPI_AEST_CACHE_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_cache));
> + break;
> + case ACPI_AEST_TLB_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_tlb));
> + break;
> + case ACPI_AEST_GENERIC_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_generic));
> + break;
> + }
> +
> + ret = aest_init_common(hdr, node);
> + if (ret)
> + kfree(node);
> +
> + return ret;
> +}
> +
> +static int __init aest_init_node(struct acpi_aest_hdr *node)
> +{
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + return aest_init_processor_node(node);
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + case ACPI_AEST_GIC_ERROR_NODE:
> + return aest_init_node_default(node);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static void __init aest_count_ppi(struct acpi_aest_hdr *header)
> +{
> + struct acpi_aest_node_interrupt *interrupt;
> + int i;
> +
> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, header,
> + header->node_interrupt_offset);
> +
> + for (i = 0; i < header->node_interrupt_count; i++, interrupt++) {
> + if (interrupt->gsiv >= 16 && interrupt->gsiv < 32)
> + num_ppi++;
> + }
> +}
> +
> +static int aest_starting_cpu(unsigned int cpu)
> +{
> + int i;
> +
> + for (i = 0; i < num_ppi; i++)
> + enable_percpu_irq(ppi_irqs[i], IRQ_TYPE_NONE);
> +
> + return 0;
> +}
> +
> +static int aest_dying_cpu(unsigned int cpu)
> +{
> + int i;
> +
> + for (i = 0; i < num_ppi; i++)
> + disable_percpu_irq(ppi_irqs[i]);
> +
> + return 0;
> +}
> +
> +int __init acpi_aest_init(void)
Should be 'static'.
> +{
> + struct acpi_aest_hdr *aest_node, *aest_end;
> + struct acpi_table_aest *aest;
> + int i, ret = 0;
> +
> + if (acpi_disabled)
> + return 0;
> +
> + if (!IS_ENABLED(CONFIG_ARM64_RAS_EXTN))
> + return 0;
I think you can move this into Kconfig file, that makes ACPI_AEST
dependent on this CONFIG_ARM64_RAS_EXTN?
> +
> + if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_AEST, 0, &aest_table)))
> + return -EINVAL;
> +
> + ret = aest_node_pool_init();
> + if (ret) {
> + pr_err("Failed init aest node pool.\n");
> + goto fail;
Just return ret;
> + }
> +
> + INIT_WORK(&aest_work, aest_node_pool_process);
> +
> + aest = (struct acpi_table_aest *)aest_table;
> +
> + /* Get the first AEST node */
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + sizeof(struct acpi_table_header));
> + /* Pointer to the end of the AEST table */
> + aest_end = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + aest_table->length);
> +
> + while (aest_node < aest_end) {
> + if (((u64)aest_node + aest_node->length) > (u64)aest_end) {
> + pr_err("AEST node pointer overflow, bad table.\n");
> + return -EINVAL;
You should destroy the node pool before returning errors.
> + }
> +
> + aest_count_ppi(aest_node);
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
> + aest_node->length);
> + }
> +
> + aest_ppi_data = kcalloc(num_ppi, sizeof(struct aest_node_data *),
> + GFP_KERNEL);
> + if (!aest_ppi_data) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + ppi_irqs = kcalloc(num_ppi, sizeof(int), GFP_KERNEL);
> + if (!ppi_irqs) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + for (i = 0; i < num_ppi; i++) {
> + aest_ppi_data[i] = alloc_percpu(struct aest_node);
> + if (!aest_ppi_data[i]) {
> + pr_err("Failed percpu allocation.\n");
> + ret = -ENOMEM;
> + goto fail;
> + }
> + }
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + sizeof(struct acpi_table_header));
> +
> + while (aest_node < aest_end) {
> + ret = aest_init_node(aest_node);
> + if (ret) {
> + pr_err("failed to init node: %d", ret);
> + goto fail;
> + }
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
> + aest_node->length);
> + }
> +
> +
> +
> + return cpuhp_setup_state(CPUHP_AP_ARM_AEST_STARTING,
> + "drivers/acpi/arm64/aest:starting",
> + aest_starting_cpu, aest_dying_cpu);
Need free the resources you requested if an error occurs.
> +
> +fail:
> + for (i = 0; i < num_ppi; i++)
> + free_percpu(aest_ppi_data[i]);
> + kfree(aest_ppi_data);
> + return ret;
> +}
> +subsys_initcall(acpi_aest_init);
> diff --git a/include/linux/acpi_aest.h b/include/linux/acpi_aest.h
> new file mode 100644
> index 000000000000..679187505dc6
> --- /dev/null
> +++ b/include/linux/acpi_aest.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef AEST_H
> +#define AEST_H
> +
> +#include <acpi/actbl.h>
> +#include <asm/ras.h>
> +
> +#define AEST_INTERRUPT_MODE BIT(0)
> +
> +#define ACPI_AEST_PROC_FLAG_GLOBAL (1<<0)
> +#define ACPI_AEST_PROC_FLAG_SHARED (1<<1)
> +
> +#define ACPI_AEST_INTERFACE_CLEAR_MISC (1<<0)
> +
> +#define ERXFR 0x0
> +#define ERXCTLR 0x8
> +#define ERXSTATUS 0x10
> +#define ERXADDR 0x18
> +#define ERXMISC0 0x20
> +#define ERXMISC1 0x28
> +#define ERXMISC2 0x30
> +#define ERXMISC3 0x38
> +
> +struct aest_node_interface {
> + u8 type;
> + u64 phy_addr;
> + u16 record_start;
> + u16 record_end;
> + u32 flags;
> + unsigned long record_implemented;
> + unsigned long status_reporting;
> + unsigned long addressing_mode;
> + struct ras_ext_regs *regs;
> + u64 *ce_threshold;
> +};
> +
> +union aest_node_processor {
> + struct acpi_aest_processor_cache cache_data;
> + struct acpi_aest_processor_tlb tlb_data;
> + struct acpi_aest_processor_generic generic_data;
> +};
> +
> +union aest_node_spec {
> + struct acpi_aest_processor processor;
> + struct acpi_aest_memory memory;
> + struct acpi_aest_smmu smmu;
> + struct acpi_aest_vendor vendor;
> + struct acpi_aest_gic gic;
> +};
> +
> +struct aest_access {
> + u64 (*read)(u64 base, u32 offset);
> + void (*write)(u64 base, u32 offset, u64 val);
> +};
> +
> +struct aest_node {
> + char *name;
> + u8 type;
> + struct aest_node_interface interface;
> + union aest_node_spec spec;
> + union aest_node_processor proc;
> + struct aest_access *access;
> +};
> +
> +struct aest_node_llist {
> + struct llist_node llnode;
> + char *node_name;
> + int type;
> + /*
> + * Different nodes have different meanings:
> + * - Processor node : processor number.
> + * - Memory node : SRAT proximity domain.
> + * - SMMU node : IORT proximity domain.
> + * - Vendor node : hardware ID.
> + * - GIC node : interface type.
> + */
> + u32 id0;
> + /*
> + * Different nodes have different meanings:
> + * - Processor node : processor resource type.
> + * - Memory node : Non.
> + * - SMMU node : subcomponent reference.
> + * - Vendor node : Unique ID.
> + * - GIC node : instance identifier.
> + */
> + u32 id1;
> + int index;
> + unsigned long addressing_mode;
> + struct ras_ext_regs *regs;
> +};
These are only aest-related structures? If so, I think they should be in
aest.c file.
> +
> +#endif /* AEST_H */
> diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> index 624d4a38c358..f0dda08dbad2 100644
> --- a/include/linux/cpuhotplug.h
> +++ b/include/linux/cpuhotplug.h
> @@ -186,6 +186,7 @@ enum cpuhp_state {
> CPUHP_AP_CSKY_TIMER_STARTING,
> CPUHP_AP_TI_GP_TIMER_STARTING,
> CPUHP_AP_HYPERV_TIMER_STARTING,
> + CPUHP_AP_ARM_AEST_STARTING,
> /* Must be the last timer callback */
> CPUHP_AP_DUMMY_TIMER_STARTING,
> CPUHP_AP_ARM_XEN_STARTING,
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/2] ACPI/AEST: Initial AEST driver
2024-03-21 3:52 ` Baolin Wang
@ 2024-04-09 7:37 ` Ruidong Tian
0 siblings, 0 replies; 7+ messages in thread
From: Ruidong Tian @ 2024-04-09 7:37 UTC (permalink / raw)
To: Baolin Wang, catalin.marinas, will, lpieralisi, guohanjun,
sudeep.holla, xueshuai, linux-kernel, linux-acpi,
linux-arm-kernel, rafael, lenb, tony.luck, bp, linux-edac
Cc: tianruidong, Tyler Baicar
在 2024/3/21 11:52, Baolin Wang 写道:
>
>
> On 2024/3/21 10:53, Ruidong Tian wrote:
>> From: Tyler Baicar <baicar@os.amperecomputing.com>
>>
>> Add support for parsing the ARM Error Source Table and basic handling of
>> errors reported through both memory mapped and system register
>> interfaces.
>>
>> Assume system register interfaces are only registered with private
>> peripheral interrupts (PPIs); otherwise there is no guarantee the
>> core handling the error is the core which took the error and has the
>> syndrome info in its system registers.
>>
>> In kernel-first mode, all configuration is controlled by kernel, include
>> CE ce_threshold and interrupt enable/disable.
>>
>> All detected errors will be processed as follow:
>> - CE, DE: use a workqueue to log this hardware errors.
>> - UER, UEO: log it and call memory_failure in workquee.
>> - UC, UEU: panic in irq context.
>>
>> Signed-off-by: Tyler Baicar <baicar@os.amperecomputing.com>
>> Signed-off-by: Ruidong Tian <tianruidong@linux.alibaba.com>
>> ---
>> MAINTAINERS | 11 +
>> arch/arm64/include/asm/ras.h | 71 +++
>> drivers/acpi/arm64/Kconfig | 10 +
>> drivers/acpi/arm64/Makefile | 1 +
>> drivers/acpi/arm64/aest.c | 834 +++++++++++++++++++++++++++++++++++
>> include/linux/acpi_aest.h | 92 ++++
>> include/linux/cpuhotplug.h | 1 +
>> 7 files changed, 1020 insertions(+)
>> create mode 100644 arch/arm64/include/asm/ras.h
>> create mode 100644 drivers/acpi/arm64/aest.c
>> create mode 100644 include/linux/acpi_aest.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index dd5de540ec0b..34900d4bb677 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -330,6 +330,17 @@ L: linux-arm-kernel@lists.infradead.org
>> (moderated for non-subscribers)
>> S: Maintained
>> F: drivers/acpi/arm64
>> +ACPI AEST
>> +M: Tyler Baicar <baicar@os.amperecomputing.com>
>> +M: Ruidong Tian <tianruidond@linux.alibaba.com>
>> +L: linux-acpi@vger.kernel.org
>> +L: linux-arm-kernel@lists.infradead.org
>> +S: Supported
>> +F: arch/arm64/include/asm/ras.h
>> +F: drivers/acpi/arm64/aest.c
>> +F: include/linux/acpi_aest.h
>> +
>> +
>> ACPI FOR RISC-V (ACPI/riscv)
>> M: Sunil V L <sunilvl@ventanamicro.com>
>> L: linux-acpi@vger.kernel.org
>> diff --git a/arch/arm64/include/asm/ras.h b/arch/arm64/include/asm/ras.h
>> new file mode 100644
>> index 000000000000..04667f0de30f
>> --- /dev/null
>> +++ b/arch/arm64/include/asm/ras.h
>> @@ -0,0 +1,71 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +#ifndef __ASM_RAS_H
>> +#define __ASM_RAS_H
>> +
>> +#include <linux/types.h>
>> +#include <linux/bits.h>
>> +
>> +/* ERR<n>FR */
>> +#define ERR_FR_RP BIT(15)
>> +#define ERR_FR_CEC GENMASK_ULL(14, 12)
>> +
>> +#define ERR_FR_RP_SINGLE_COUNTER 0
>> +#define ERR_FR_RP_DOUBLE_COUNTER 1
>> +
>> +#define ERR_FR_CEC_0B_COUNTER 0
>> +#define ERR_FR_CEC_8B_COUNTER BIT(1)
>> +#define ERR_FR_CEC_16B_COUNTER BIT(2)
>> +
>> +/* ERR<n>STATUS */
>> +#define ERR_STATUS_AV BIT(31)
>> +#define ERR_STATUS_V BIT(30)
>> +#define ERR_STATUS_UE BIT(29)
>> +#define ERR_STATUS_ER BIT(28)
>> +#define ERR_STATUS_OF BIT(27)
>> +#define ERR_STATUS_MV BIT(26)
>> +#define ERR_STATUS_CE (BIT(25) | BIT(24))
>> +#define ERR_STATUS_DE BIT(23)
>> +#define ERR_STATUS_PN BIT(22)
>> +#define ERR_STATUS_UET (BIT(21) | BIT(20))
>> +#define ERR_STATUS_CI BIT(19)
>> +#define ERR_STATUS_IERR GENMASK_ULL(15, 8)
>> +#define ERR_STATUS_SERR GENMASK_ULL(7, 0)
>> +
>> +/* These bit is write-one-to-clear */
>> +#define ERR_STATUS_W1TC (ERR_STATUS_AV | ERR_STATUS_V |
>> ERR_STATUS_UE | \
>> + ERR_STATUS_ER | ERR_STATUS_OF | ERR_STATUS_MV | \
>> + ERR_STATUS_CE | ERR_STATUS_DE | ERR_STATUS_PN | \
>> + ERR_STATUS_UET | ERR_STATUS_CI)
>> +
>> +#define ERR_STATUS_UET_UC 0
>> +#define ERR_STATUS_UET_UEU 1
>> +#define ERR_STATUS_UET_UER 2
>> +#define ERR_STATUS_UET_UEO 3
>> +
>> +/* ERR<n>CTLR */
>> +#define ERR_CTLR_FI BIT(3)
>> +#define ERR_CTLR_UI BIT(2)
>> +
>> +/* ERR<n>ADDR */
>> +#define ERR_ADDR_AI BIT(61)
>> +#define ERR_ADDR_PADDR GENMASK_ULL(55, 0)
>> +
>> +/* ERR<n>MISC0 */
>> +
>> +/* ERR<n>FR.CEC == 0b010, ERR<n>FR.RP == 0 */
>> +#define ERR_MISC0_8B_OF BIT(39)
>> +#define ERR_MISC0_8B_CEC GENMASK_ULL(38, 32)
>> +
>> +/* ERR<n>FR.CEC == 0b100, ERR<n>FR.RP == 0 */
>> +#define ERR_MISC0_16B_OF BIT(47)
>> +#define ERR_MISC0_16B_CEC GENMASK_ULL(46, 32)
>> +
>> +struct ras_ext_regs {
>> + u64 err_fr;
>> + u64 err_ctlr;
>> + u64 err_status;
>> + u64 err_addr;
>> + u64 err_misc[4];
>> +};
>> +
>> +#endif /* __ASM_RAS_H */
>> diff --git a/drivers/acpi/arm64/Kconfig b/drivers/acpi/arm64/Kconfig
>> index b3ed6212244c..639db671c5cf 100644
>> --- a/drivers/acpi/arm64/Kconfig
>> +++ b/drivers/acpi/arm64/Kconfig
>> @@ -21,3 +21,13 @@ config ACPI_AGDI
>> config ACPI_APMT
>> bool
>> +
>> +config ACPI_AEST
>> + bool "ARM Error Source Table Support"
>> +
>> + help
>> + The Arm Error Source Table (AEST) provides details on ACPI
>> + extensions that enable kernel-first handling of errors in a
>> + system that supports the Armv8 RAS extensions.
>> +
>> + If set, the kernel will report and log hardware errors.
>> diff --git a/drivers/acpi/arm64/Makefile b/drivers/acpi/arm64/Makefile
>> index 143debc1ba4a..b5b740058c46 100644
>> --- a/drivers/acpi/arm64/Makefile
>> +++ b/drivers/acpi/arm64/Makefile
>> @@ -5,3 +5,4 @@ obj-$(CONFIG_ACPI_GTDT) += gtdt.o
>> obj-$(CONFIG_ACPI_APMT) += apmt.o
>> obj-$(CONFIG_ARM_AMBA) += amba.o
>> obj-y += dma.o init.o
>> +obj-$(CONFIG_ACPI_AEST) += aest.o
>> diff --git a/drivers/acpi/arm64/aest.c b/drivers/acpi/arm64/aest.c
>> new file mode 100644
>> index 000000000000..ab17aa5f5997
>> --- /dev/null
>> +++ b/drivers/acpi/arm64/aest.c
>> @@ -0,0 +1,834 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * ARM Error Source Table Support
>> + *
>> + * Copyright (c) 2021, Ampere Computing LLC
>> + * Copyright (c) 2021-2024, Alibaba Group.
>> + */
>> +
>> +#include <linux/acpi.h>
>> +#include <linux/acpi_aest.h>
>> +#include <linux/cpuhotplug.h>
>> +#include <linux/kernel.h>
>> +#include <linux/genalloc.h>
>> +#include <linux/llist.h>
>> +#include <acpi/actbl.h>
>> +#include <asm/ras.h>
>> +
>> +#undef pr_fmt
>> +#define pr_fmt(fmt) "ACPI AEST: " fmt
>> +
>> +#define CASE_READ(res, x) \
>> + case (x): { \
>> + res = read_sysreg_s(SYS_##x##_EL1); \
>> + break; \
>> + }
>> +
>> +#define CASE_WRITE(val, x) \
>> + case (x): { \
>> + write_sysreg_s((val), SYS_##x##_EL1); \
>> + break; \
>> + }
>> +
>> +#define for_each_implemented_record(index, node) \
>> + for ((index) = node->interface.record_start; \
>> + (index) < node->interface.record_end; \
>> + (index)++)
>> +
>> +#define AEST_LOG_PREFIX_BUFFER 64
>> +
>> +/*
>> + * This memory pool is only to be used to save AEST node in AEST irq
>> context.
>> + * There can be 500 AEST node at most.
>> + */
>> +#define AEST_NODE_ALLOCED_MAX 500
>> +
>> +static struct acpi_table_header *aest_table;
>> +
>> +static struct aest_node __percpu **aest_ppi_data;
>> +
>> +static int *ppi_irqs;
>> +static u8 num_ppi;
>> +static u8 ppi_idx;
>> +
>> +static struct work_struct aest_work;
>> +
>> +static struct gen_pool *aest_node_pool;
>> +static struct llist_head aest_node_llist;
>> +
>> +static u64 aest_sysreg_read(u64 __unused, u32 offset)
>> +{
>> + u64 res;
>> +
>> + switch (offset) {
>> + CASE_READ(res, ERXFR)
>> + CASE_READ(res, ERXCTLR)
>> + CASE_READ(res, ERXSTATUS)
>> + CASE_READ(res, ERXADDR)
>> + CASE_READ(res, ERXMISC0)
>> + CASE_READ(res, ERXMISC1)
>> + CASE_READ(res, ERXMISC2)
>> + CASE_READ(res, ERXMISC3)
>> + default :
>> + res = 0;
>> + }
>> + return res;
>> +}
>> +
>> +static void aest_sysreg_write(u64 base, u32 offset, u64 val)
>> +{
>> + switch (offset) {
>> + CASE_WRITE(val, ERXFR)
>> + CASE_WRITE(val, ERXCTLR)
>> + CASE_WRITE(val, ERXSTATUS)
>> + CASE_WRITE(val, ERXADDR)
>> + CASE_WRITE(val, ERXMISC0)
>> + CASE_WRITE(val, ERXMISC1)
>> + CASE_WRITE(val, ERXMISC2)
>> + CASE_WRITE(val, ERXMISC3)
>> + default :
>> + return;
>> + }
>> +}
>> +
>> +static u64 aest_iomem_read(u64 base, u32 offset)
>> +{
>> + return readq_relaxed((void *)(base + offset));
>> +}
>> +
>> +static void aest_iomem_write(u64 base, u32 offset, u64 val)
>> +{
>> + writeq_relaxed(val, (void *)(base + offset));
>> +}
>> +
>> +static void aest_print(struct aest_node_llist *lnode)
>> +{
>> + static atomic_t seqno = { 0 };
>> + unsigned int curr_seqno;
>> + char pfx_seq[AEST_LOG_PREFIX_BUFFER];
>> + int index;
>> + struct ras_ext_regs *regs;
>> +
>> + curr_seqno = atomic_inc_return(&seqno);
>> + snprintf(pfx_seq, sizeof(pfx_seq), "{%u}" HW_ERR, curr_seqno);
>> + pr_info("%sHardware error from %s\n", pfx_seq, lnode->node_name);
>> +
>> + switch (lnode->type) {
>> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
>> + pr_err("%s Error from CPU%d\n", pfx_seq, lnode->id0);
>> + break;
>> + case ACPI_AEST_MEMORY_ERROR_NODE:
>> + pr_err("%s Error from memory at SRAT proximity domain 0x%x\n",
>> + pfx_seq, lnode->id0);
>> + break;
>> + case ACPI_AEST_SMMU_ERROR_NODE:
>> + pr_err("%s Error from SMMU IORT node 0x%x subcomponent 0x%x\n",
>> + pfx_seq, lnode->id0, lnode->id1);
>> + break;
>> + case ACPI_AEST_VENDOR_ERROR_NODE:
>> + pr_err("%s Error from vendor hid 0x%x uid 0x%x\n",
>> + pfx_seq, lnode->id0, lnode->id1);
>> + break;
>> + case ACPI_AEST_GIC_ERROR_NODE:
>> + pr_err("%s Error from GIC type 0x%x instance 0x%x\n",
>> + pfx_seq, lnode->id0, lnode->id1);
>> + break;
>> + default:
>> + pr_err("%s Unknown AEST node type\n", pfx_seq);
>> + return;
>> + }
>> +
>> + index = lnode->index;
>> + regs = lnode->regs;
>> +
>> + pr_err("%s ERR%uFR: 0x%llx\n", pfx_seq, index, regs->err_fr);
>> + pr_err("%s ERR%uCTRL: 0x%llx\n", pfx_seq, index, regs->err_ctlr);
>> + pr_err("%s ERR%uSTATUS: 0x%llx\n", pfx_seq, index,
>> regs->err_status);
>> + if (regs->err_status & ERR_STATUS_AV)
>> + pr_err("%s ERR%uADDR: 0x%llx\n", pfx_seq, index,
>> regs->err_addr);
>> +
>> + if (regs->err_status & ERR_STATUS_MV) {
>> + pr_err("%s ERR%uMISC0: 0x%llx\n", pfx_seq, index,
>> regs->err_misc[0]);
>> + pr_err("%s ERR%uMISC1: 0x%llx\n", pfx_seq, index,
>> regs->err_misc[1]);
>> + pr_err("%s ERR%uMISC2: 0x%llx\n", pfx_seq, index,
>> regs->err_misc[2]);
>> + pr_err("%s ERR%uMISC3: 0x%llx\n", pfx_seq, index,
>> regs->err_misc[3]);
>> + }
>> +}
>> +
>> +static void aest_handle_memory_failure(struct aest_node_llist *lnode)
>> +{
>> + unsigned long pfn;
>> + u64 addr;
>> +
>> + if (test_bit(lnode->index, &lnode->addressing_mode) ||
>> + (lnode->regs->err_addr & ERR_ADDR_AI))
>> + return;
>> +
>> + addr = lnode->regs->err_addr & (1UL << CONFIG_ARM64_PA_BITS);
>> + pfn = PHYS_PFN(addr);
>> +
>> + if (!pfn_valid(pfn)) {
>> + pr_warn(HW_ERR "Invalid physical address: %#llx\n", addr);
>> + return;
>> + }
>> +
>> + memory_failure(pfn, 0);
>> +}
>> +
>> +static void aest_node_pool_process(struct work_struct *__unused)
>> +{
>> + struct llist_node *head;
>> + struct aest_node_llist *lnode, *tmp;
>> + u64 status;
>> +
>> + head = llist_del_all(&aest_node_llist);
>> + if (!head)
>> + return;
>> +
>> + head = llist_reverse_order(head);
>> + llist_for_each_entry_safe(lnode, tmp, head, llnode) {
>> + aest_print(lnode);
>> +
>> + status = lnode->regs->err_status;
>> + if ((status & ERR_STATUS_UE) &&
>> + (status & ERR_STATUS_UET) > ERR_STATUS_UET_UEU)
>> + aest_handle_memory_failure(lnode);
>> + gen_pool_free(aest_node_pool, (unsigned long)lnode,
>> + sizeof(*lnode));
>> + }
>> +}
>> +
>> +static int aest_node_gen_pool_add(struct aest_node *node, int index,
>> + struct ras_ext_regs *regs)
>> +{
>> + struct aest_node_llist *list;
>> +
>> + if (!aest_node_pool)
>> + return -EINVAL;
>> +
>> + list = (void *)gen_pool_alloc(aest_node_pool, sizeof(*list));
>> + if (!list)
>> + return -ENOMEM;
>> +
>> + list->type = node->type;
>> + list->node_name = node->name;
>> + switch (node->type) {
>> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
>> + list->id0 = node->spec.processor.processor_id;
>> + if (node->spec.processor.flags & (ACPI_AEST_PROC_FLAG_SHARED |
>> + ACPI_AEST_PROC_FLAG_GLOBAL))
>> + list->id0 = smp_processor_id();
>> +
>> + list->id1 = node->spec.processor.resource_type;
>> + break;
>> + case ACPI_AEST_MEMORY_ERROR_NODE:
>> + list->id0 = node->spec.memory.srat_proximity_domain;
>> + break;
>> + case ACPI_AEST_SMMU_ERROR_NODE:
>> + list->id0 = node->spec.smmu.iort_node_reference;
>> + list->id1 = node->spec.smmu.subcomponent_reference;
>> + break;
>> + case ACPI_AEST_VENDOR_ERROR_NODE:
>> + list->id0 = node->spec.vendor.acpi_hid;
>> + list->id1 = node->spec.vendor.acpi_uid;
>> + break;
>> + case ACPI_AEST_GIC_ERROR_NODE:
>> + list->id0 = node->spec.gic.interface_type;
>> + list->id1 = node->spec.gic.instance_id;
>> + break;
>> + default:
>> + list->id0 = 0;
>> + list->id1 = 0;
>> + }
>> +
>> + list->regs = regs;
>> + list->index = index;
>> + list->addressing_mode = node->interface.addressing_mode;
>> + llist_add(&list->llnode, &aest_node_llist);
>> +
>> + return 0;
>> +}
>> +
>> +static int aest_node_pool_init(void)
>> +{
>> + unsigned long addr, size;
>> + int rc;
>> +
>> + if (aest_node_pool)
>> + return 0;
>> +
>> + size = ilog2(sizeof(struct aest_node_llist));
>> + aest_node_pool = gen_pool_create(size, -1);
>> + if (!aest_node_pool)
>> + return -ENOMEM;
>> +
>> + addr = (unsigned long)vmalloc(PAGE_ALIGN(size *
>> AEST_NODE_ALLOCED_MAX));
>> + if (!addr)
>> + goto err_pool_alloc;
>> +
>> + rc = gen_pool_add(aest_node_pool, addr, size, -1);
>> + if (rc)
>> + goto err_pool_add;
>> +
>> + return 0;
>> +
>> +err_pool_add:
>> + vfree((void *)addr);
>> +
>> +err_pool_alloc:
>> + gen_pool_destroy(aest_node_pool);
>> +
>> + return -ENOMEM;
>> +}
>> +
>> +static void aest_log(struct aest_node *node, int index, struct
>> ras_ext_regs *regs)
>> +{
>> + if (!aest_node_gen_pool_add(node, index, regs))
>> + schedule_work(&aest_work);
>> +}
>> +
>> +/*
>> + * Each PE may has multi error record, you must selects an error
>> record to
>> + * be accessed through the Error Record System registers.
>> + */
>> +static inline void aest_select_record(struct aest_node *node, int i)
>> +{
>> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER) {
>> + write_sysreg_s(i, SYS_ERRSELR_EL1);
>> + isb();
>> + }
>> +}
>> +
>> +/* Ensure all writes has taken effect. */
>> +static inline void aest_sync(struct aest_node *node)
>> +{
>> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER)
>> + isb();
>> +}
>> +
>> +static int aest_proc(struct aest_node *node)
>> +{
>> + struct ras_ext_regs regs = {0};
>> + struct aest_access *access;
>> + int i, count = 0;
>> + u64 regs_p;
>> +
>> + for_each_implemented_record(i, node) {
>> +
>> + /* 1b: Error record at i index is not implemented */
>> + if (test_bit(i, &node->interface.record_implemented))
>> + continue;
>> +
>> + aest_select_record(node, i);
>> +
>> + access = node->access;
>> + regs_p = (u64)&node->interface.regs[i];
>> +
>> + regs.err_status = access->read(regs_p, ERXSTATUS);
>> + if (!(regs.err_status & ERR_STATUS_V))
>> + continue;
>> +
>> + count++;
>> +
>> + if (regs.err_status & ERR_STATUS_AV)
>> + regs.err_addr = access->read(regs_p, ERXADDR);
>> +
>> + regs.err_fr = access->read(regs_p, ERXFR);
>> + regs.err_ctlr = access->read(regs_p, ERXCTLR);
>> +
>> + if (regs.err_status & ERR_STATUS_MV) {
>> + regs.err_misc[0] = access->read(regs_p, ERXMISC0);
>> + regs.err_misc[1] = access->read(regs_p, ERXMISC1);
>> + regs.err_misc[2] = access->read(regs_p, ERXMISC2);
>> + regs.err_misc[3] = access->read(regs_p, ERXMISC3);
>> + }
>> +
>> + if (node->interface.flags & ACPI_AEST_INTERFACE_CLEAR_MISC) {
>> + access->write(regs_p, ERXMISC0, 0);
>> + access->write(regs_p, ERXMISC1, 0);
>> + access->write(regs_p, ERXMISC2, 0);
>> + access->write(regs_p, ERXMISC3, 0);
>> + } else
>> + access->write(regs_p, ERXMISC0,
>> + node->interface.ce_threshold[i]);
>> +
>> + aest_log(node, i, ®s);
>> +
>> + /* panic if unrecoverable and uncontainable error encountered */
>> + if ((regs.err_status & ERR_STATUS_UE) &&
>> + (regs.err_status & ERR_STATUS_UET) < ERR_STATUS_UET_UER)
>> + panic("AEST: unrecoverable error encountered");
>> +
>> + /* Write-one-to-clear the bits we've seen */
>> + regs.err_status &= ERR_STATUS_W1TC;
>> +
>> + /* Multi bit filed need to write all-ones to clear. */
>> + if (regs.err_status & ERR_STATUS_CE)
>> + regs.err_status |= ERR_STATUS_CE;
>> +
>> + /* Multi bit filed need to write all-ones to clear. */
>> + if (regs.err_status & ERR_STATUS_UET)
>> + regs.err_status |= ERR_STATUS_UET;
>> +
>> + access->write(regs_p, ERXSTATUS, regs.err_status);
>> +
>> + aest_sync(node);
>> + }
>> +
>> + return count;
>> +}
>> +
>> +static irqreturn_t aest_irq_func(int irq, void *input)
>> +{
>> + struct aest_node *node = input;
>> +
>> + if (aest_proc(node))
>> + return IRQ_HANDLED;
>> +
>> + return IRQ_NONE;
>> +}
>> +
>> +static int __init aest_register_gsi(u32 gsi, int trigger, void *data,
>> + irq_handler_t aest_irq_func)
>> +{
>> + int cpu, irq;
>> +
>> + irq = acpi_register_gsi(NULL, gsi, trigger, ACPI_ACTIVE_HIGH);
>> +
>> + if (irq == -EINVAL) {
>> + pr_err("failed to map AEST GSI %d\n", gsi);
>> + return -EINVAL;
>> + }
>
> IMO, should be:
> if (irq < 0) {
> pr_err("failed to map AEST GSI %d\n", gsi);
> return irq;
> }
>
>> +
>> + if (irq_is_percpu_devid(irq)) {
>> + ppi_irqs[ppi_idx] = irq;
>> + for_each_possible_cpu(cpu) {
>> + memcpy(per_cpu_ptr(aest_ppi_data[ppi_idx], cpu), data,
>> + sizeof(struct aest_node));
>> + }
>> + if (request_percpu_irq(irq, aest_irq_func, "AEST",
>> + aest_ppi_data[ppi_idx++])) {
>> + pr_err("failed to register AEST IRQ %d\n", irq);
>> + return -EINVAL;
>
> Do not override the error number.
>
>> + }
>> + } else {
>> + if (request_irq(irq, aest_irq_func, IRQF_SHARED, "AEST",
>> + data)) {
>> + pr_err("failed to register AEST IRQ %d\n", irq);
>> + return -EINVAL;
>
> ditto.
>
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int __init aest_init_interrupts(struct acpi_aest_hdr *hdr,
>> + struct aest_node *node)
>> +{
>> + struct acpi_aest_node_interrupt *interrupt;
>> + int i, trigger, ret = 0, err_ctlr, regs_p;
>> +
>> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, hdr,
>> + hdr->node_interrupt_offset);
>> +
>> + for (i = 0; i < hdr->node_interrupt_count; i++, interrupt++) {
>> + trigger = (interrupt->flags & AEST_INTERRUPT_MODE) ?
>> + ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
>> + if (aest_register_gsi(interrupt->gsiv, trigger, node,
>> + aest_irq_func))
>> + ret = -EINVAL;
>
> Do not override the error number.
>
>> + }
>> +
>> + /* Ensure RAS interrupt is enabled */
>> + for_each_implemented_record(i, node) {
>> + /* 1b: Error record at i index is not implemented */
>> + if (test_bit(i, &node->interface.record_implemented))
>> + continue;
>> +
>> + aest_select_record(node, i);
>> +
>> + regs_p = (u64)&node->interface.regs[i];
>> +
>> + err_ctlr = node->access->read(regs_p, ERXCTLR);
>> +
>> + if (interrupt->type == ACPI_AEST_NODE_FAULT_HANDLING)
>> + err_ctlr |= ERR_CTLR_FI;
>> + if (interrupt->type == ACPI_AEST_NODE_ERROR_RECOVERY)
>> + err_ctlr |= ERR_CTLR_UI;
>> +
>> + node->access->write(regs_p, ERXCTLR, err_ctlr);
>> +
>> + aest_sync(node);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static void __init set_aest_node_name(struct aest_node *node)
>> +{
>> + switch (node->type) {
>> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
>> + node->name = kasprintf(GFP_KERNEL, "AEST-CPU%d",
>> + node->spec.processor.processor_id);
>> + break;
>> + case ACPI_AEST_MEMORY_ERROR_NODE:
>> + case ACPI_AEST_SMMU_ERROR_NODE:
>> + case ACPI_AEST_VENDOR_ERROR_NODE:
>> + case ACPI_AEST_GIC_ERROR_NODE:
>> + node->name = kasprintf(GFP_KERNEL, "AEST-%llx",
>> + node->interface.phy_addr);
>> + break;
>> + default:
>> + node->name = kasprintf(GFP_KERNEL, "AEST-Unkown-Node");
>
> IMO, better to check the return value for memory allocation.
>
>> + }
>> +}
>> +
>> +/* access type is decided by AEST interface type. */
>> +static struct aest_access aest_access[] = {
>> + [ACPI_AEST_NODE_SYSTEM_REGISTER] = {
>> + .read = aest_sysreg_read,
>> + .write = aest_sysreg_write,
>> + },
>> +
>> + [ACPI_AEST_NODE_MEMORY_MAPPED] = {
>> + .read = aest_iomem_read,
>> + .write = aest_iomem_write,
>> + },
>> + { }
>> +};
>> +
>> +/* In kernel-first mode, kernel will report every CE by default. */
>> +static void __init aest_set_ce_threshold(struct aest_node *node)
>> +{
>> + u64 regs_p, err_fr, err_fr_cec, err_fr_rp, err_misc0, ce_threshold;
>> + int i;
>> +
>> + for_each_implemented_record(i, node) {
>> + /* 1b: Error record at i index is not implemented */
>> + if (test_bit(i, &node->interface.record_implemented))
>> + continue;
>> +
>> + aest_select_record(node, i);
>> + regs_p = (u64)&node->interface.regs[i];
>> +
>> + err_fr = node->access->read(regs_p, ERXFR);
>> + err_fr_cec = FIELD_GET(ERR_FR_CEC, err_fr);
>> + err_fr_rp = FIELD_GET(ERR_FR_RP, err_fr);
>> + err_misc0 = node->access->read(regs_p, ERXMISC0);
>> +
>> + if (err_fr_cec == ERR_FR_CEC_0B_COUNTER)
>> + pr_debug("%s-%d do not support CE threshold!\n",
>> + node->name, i);
>> + else if (err_fr_cec == ERR_FR_CEC_8B_COUNTER &&
>> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
>> + pr_debug("%s-%d support 8 bit CE threshold!\n",
>> + node->name, i);
>> + ce_threshold = err_misc0 | ERR_MISC0_8B_CEC;
>> + } else if (err_fr_cec == ERR_FR_CEC_16B_COUNTER &&
>> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
>> + pr_debug("%s-%d support 16 bit CE threshold!\n",
>> + node->name, i);
>> + ce_threshold = err_misc0 | ERR_MISC0_16B_CEC;
>> + } else
>> + pr_debug("%s-%d do not support double counter yet!\n",
>> + node->name, i);
>
> Change to 'switch' statement will be more readable.
>
>> +
>> + node->access->write(regs_p, ERXMISC0, ce_threshold);
>> + node->interface.ce_threshold[i] = ce_threshold;
>> +
>> + aest_sync(node);
>> + }
>> +}
>> +
>> +static int __init aest_init_interface(struct acpi_aest_hdr *hdr,
>> + struct aest_node *node)
>> +{
>> + struct acpi_aest_node_interface *interface;
>> + struct resource *res;
>> + int size;
>> +
>> + interface = ACPI_ADD_PTR(struct acpi_aest_node_interface, hdr,
>> + hdr->node_interface_offset);
>> +
>> + if (interface->type >= ACPI_AEST_XFACE_RESERVED) {
>> + pr_err("invalid interface type: %d\n", interface->type);
>> + return -EINVAL;
>> + }
>> +
>> + node->interface.type = interface->type;
>> + node->interface.phy_addr = interface->address;
>> + node->interface.record_start = interface->error_record_index;
>> + node->interface.record_end = interface->error_record_index +
>> + interface->error_record_count;
>> + node->interface.flags = interface->flags;
>> + node->interface.record_implemented =
>> interface->error_record_implemented;
>> + node->interface.status_reporting =
>> interface->error_status_reporting;
>> + node->interface.addressing_mode = interface->addressing_mode;
>> + node->access = &aest_access[interface->type];
>> +
>> + /*
>> + * Currently SR based handling is done through the architected
>> + * discovery exposed through SRs. That may change in the future
>> + * if there is supplemental information in the AEST that is
>> + * needed.
>> + */
>> + if (interface->type == ACPI_AEST_NODE_SYSTEM_REGISTER)
>> + return 0;
>> +
>> + res = kzalloc(sizeof(struct resource), GFP_KERNEL);
>> + if (!res)
>> + return -ENOMEM;
>> +
>> + size = interface->error_record_count * sizeof(struct ras_ext_regs);
>> + res->name = "AEST";
>> + res->start = interface->address;
>> + res->end = res->start + size;
>> + res->flags = IORESOURCE_MEM;
>> +
>> + if (insert_resource(&iomem_resource, res)) {
>> + pr_notice("request region conflict with %s\n",
>> + res->name);
>> + }
>> +
>> + node->interface.regs = ioremap(res->start, size);
>> + if (!node->interface.regs) {
>> + pr_err("Ioremap for %s failed!\n", node->name);
>> + kfree(res);
>> + return -EINVAL;
>
> return -ENOMEM;
>
>> + }
>> +
>> + node->interface.ce_threshold = kzalloc(sizeof(u64) *
>> + interface->error_record_count, GFP_KERNEL);
>> + if (!node->interface.ce_threshold)
>
> kfree(res) and iounmap()
>
>> + return -ENOMEM;
>> +
>> + aest_set_ce_threshold(node);
>> +
>> + return 0;
>> +}
>> +
>> +static int __init aest_init_common(struct acpi_aest_hdr *hdr,
>> + struct aest_node *node)
>> +{
>> + int ret;
>> +
>> + set_aest_node_name(node);
>> +
>> + ret = aest_init_interface(hdr, node);
>> + if (ret) {
>> + pr_err("failed to init interface\n");
>> + return ret;
>
> I did not see you free the node->name before returning an error.
>
>> + }
>> +
>> + return aest_init_interrupts(hdr, node);
>> +}
>> +
>> +static int __init aest_init_node_default(struct acpi_aest_hdr *hdr)
>> +{
>> + struct aest_node *node;
>> + union aest_node_spec *node_spec;
>> + int ret;
>> +
>> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
>> + if (!node)
>> + return -ENOMEM;
>> +
>> + node->type = hdr->type;
>> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
>> + hdr->node_specific_offset);
>> +
>> + memcpy(&node->spec, node_spec,
>> + hdr->node_interface_offset - hdr->node_specific_offset);
>> +
>> + ret = aest_init_common(hdr, node);
>> + if (ret)
>> + kfree(node);
>> +
>> + return ret;
>> +}
>> +
>> +static int __init aest_init_processor_node(struct acpi_aest_hdr *hdr)
>> +{
>> + struct aest_node *node;
>> + union aest_node_spec *node_spec;
>> + union aest_node_processor *proc;
>> + int ret;
>> +
>> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
>> + if (!node)
>> + return -ENOMEM;
>> +
>> + node->type = hdr->type;
>> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
>> + hdr->node_specific_offset);
>> +
>> + memcpy(&node->spec, node_spec,
>> + hdr->node_interface_offset - hdr->node_specific_offset);
>> +
>> + proc = ACPI_ADD_PTR(union aest_node_processor, node_spec,
>> + sizeof(acpi_aest_processor));
>> +
>> + switch (node->spec.processor.resource_type) {
>> + case ACPI_AEST_CACHE_RESOURCE:
>> + memcpy(&node->proc, proc,
>> + sizeof(struct acpi_aest_processor_cache));
>> + break;
>> + case ACPI_AEST_TLB_RESOURCE:
>> + memcpy(&node->proc, proc,
>> + sizeof(struct acpi_aest_processor_tlb));
>> + break;
>> + case ACPI_AEST_GENERIC_RESOURCE:
>> + memcpy(&node->proc, proc,
>> + sizeof(struct acpi_aest_processor_generic));
>> + break;
>> + }
>> +
>> + ret = aest_init_common(hdr, node);
>> + if (ret)
>> + kfree(node);
>> +
>> + return ret;
>> +}
>> +
>> +static int __init aest_init_node(struct acpi_aest_hdr *node)
>> +{
>> + switch (node->type) {
>> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
>> + return aest_init_processor_node(node);
>> + case ACPI_AEST_MEMORY_ERROR_NODE:
>> + case ACPI_AEST_VENDOR_ERROR_NODE:
>> + case ACPI_AEST_SMMU_ERROR_NODE:
>> + case ACPI_AEST_GIC_ERROR_NODE:
>> + return aest_init_node_default(node);
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +static void __init aest_count_ppi(struct acpi_aest_hdr *header)
>> +{
>> + struct acpi_aest_node_interrupt *interrupt;
>> + int i;
>> +
>> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, header,
>> + header->node_interrupt_offset);
>> +
>> + for (i = 0; i < header->node_interrupt_count; i++, interrupt++) {
>> + if (interrupt->gsiv >= 16 && interrupt->gsiv < 32)
>> + num_ppi++;
>> + }
>> +}
>> +
>> +static int aest_starting_cpu(unsigned int cpu)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < num_ppi; i++)
>> + enable_percpu_irq(ppi_irqs[i], IRQ_TYPE_NONE);
>> +
>> + return 0;
>> +}
>> +
>> +static int aest_dying_cpu(unsigned int cpu)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < num_ppi; i++)
>> + disable_percpu_irq(ppi_irqs[i]);
>> +
>> + return 0;
>> +}
>> +
>> +int __init acpi_aest_init(void)
>
> Should be 'static'.
>
>> +{
>> + struct acpi_aest_hdr *aest_node, *aest_end;
>> + struct acpi_table_aest *aest;
>> + int i, ret = 0;
>> +
>> + if (acpi_disabled)
>> + return 0;
>> +
>> + if (!IS_ENABLED(CONFIG_ARM64_RAS_EXTN))
>> + return 0;
>
> I think you can move this into Kconfig file, that makes ACPI_AEST
> dependent on this CONFIG_ARM64_RAS_EXTN?
>
>> +
>> + if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_AEST, 0, &aest_table)))
>> + return -EINVAL;
>> +
>> + ret = aest_node_pool_init();
>> + if (ret) {
>> + pr_err("Failed init aest node pool.\n");
>> + goto fail;
>
> Just return ret;
>
>> + }
>> +
>> + INIT_WORK(&aest_work, aest_node_pool_process);
>> +
>> + aest = (struct acpi_table_aest *)aest_table;
>> +
>> + /* Get the first AEST node */
>> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
>> + sizeof(struct acpi_table_header));
>> + /* Pointer to the end of the AEST table */
>> + aest_end = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
>> + aest_table->length);
>> +
>> + while (aest_node < aest_end) {
>> + if (((u64)aest_node + aest_node->length) > (u64)aest_end) {
>> + pr_err("AEST node pointer overflow, bad table.\n");
>> + return -EINVAL;
>
> You should destroy the node pool before returning errors.
>
>> + }
>> +
>> + aest_count_ppi(aest_node);
>> +
>> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
>> + aest_node->length);
>> + }
>> +
>> + aest_ppi_data = kcalloc(num_ppi, sizeof(struct aest_node_data *),
>> + GFP_KERNEL);
>> + if (!aest_ppi_data) {
>> + ret = -ENOMEM;
>> + goto fail;
>> + }
>> +
>> + ppi_irqs = kcalloc(num_ppi, sizeof(int), GFP_KERNEL);
>> + if (!ppi_irqs) {
>> + ret = -ENOMEM;
>> + goto fail;
>> + }
>> +
>> + for (i = 0; i < num_ppi; i++) {
>> + aest_ppi_data[i] = alloc_percpu(struct aest_node);
>> + if (!aest_ppi_data[i]) {
>> + pr_err("Failed percpu allocation.\n");
>> + ret = -ENOMEM;
>> + goto fail;
>> + }
>> + }
>> +
>> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
>> + sizeof(struct acpi_table_header));
>> +
>> + while (aest_node < aest_end) {
>> + ret = aest_init_node(aest_node);
>> + if (ret) {
>> + pr_err("failed to init node: %d", ret);
>> + goto fail;
>> + }
>> +
>> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
>> + aest_node->length);
>> + }
>> +
>> +
>> +
>> + return cpuhp_setup_state(CPUHP_AP_ARM_AEST_STARTING,
>> + "drivers/acpi/arm64/aest:starting",
>> + aest_starting_cpu, aest_dying_cpu);
>
> Need free the resources you requested if an error occurs.
>
>> +
>> +fail:
>> + for (i = 0; i < num_ppi; i++)
>> + free_percpu(aest_ppi_data[i]);
>> + kfree(aest_ppi_data);
>> + return ret;
>> +}
>> +subsys_initcall(acpi_aest_init);
>> diff --git a/include/linux/acpi_aest.h b/include/linux/acpi_aest.h
>> new file mode 100644
>> index 000000000000..679187505dc6
>> --- /dev/null
>> +++ b/include/linux/acpi_aest.h
>> @@ -0,0 +1,92 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +#ifndef AEST_H
>> +#define AEST_H
>> +
>> +#include <acpi/actbl.h>
>> +#include <asm/ras.h>
>> +
>> +#define AEST_INTERRUPT_MODE BIT(0)
>> +
>> +#define ACPI_AEST_PROC_FLAG_GLOBAL (1<<0)
>> +#define ACPI_AEST_PROC_FLAG_SHARED (1<<1)
>> +
>> +#define ACPI_AEST_INTERFACE_CLEAR_MISC (1<<0)
>> +
>> +#define ERXFR 0x0
>> +#define ERXCTLR 0x8
>> +#define ERXSTATUS 0x10
>> +#define ERXADDR 0x18
>> +#define ERXMISC0 0x20
>> +#define ERXMISC1 0x28
>> +#define ERXMISC2 0x30
>> +#define ERXMISC3 0x38
>> +
>> +struct aest_node_interface {
>> + u8 type;
>> + u64 phy_addr;
>> + u16 record_start;
>> + u16 record_end;
>> + u32 flags;
>> + unsigned long record_implemented;
>> + unsigned long status_reporting;
>> + unsigned long addressing_mode;
>> + struct ras_ext_regs *regs;
>> + u64 *ce_threshold;
>> +};
>> +
>> +union aest_node_processor {
>> + struct acpi_aest_processor_cache cache_data;
>> + struct acpi_aest_processor_tlb tlb_data;
>> + struct acpi_aest_processor_generic generic_data;
>> +};
>> +
>> +union aest_node_spec {
>> + struct acpi_aest_processor processor;
>> + struct acpi_aest_memory memory;
>> + struct acpi_aest_smmu smmu;
>> + struct acpi_aest_vendor vendor;
>> + struct acpi_aest_gic gic;
>> +};
>> +
>> +struct aest_access {
>> + u64 (*read)(u64 base, u32 offset);
>> + void (*write)(u64 base, u32 offset, u64 val);
>> +};
>> +
>> +struct aest_node {
>> + char *name;
>> + u8 type;
>> + struct aest_node_interface interface;
>> + union aest_node_spec spec;
>> + union aest_node_processor proc;
>> + struct aest_access *access;
>> +};
>> +
>> +struct aest_node_llist {
>> + struct llist_node llnode;
>> + char *node_name;
>> + int type;
>> + /*
>> + * Different nodes have different meanings:
>> + * - Processor node : processor number.
>> + * - Memory node : SRAT proximity domain.
>> + * - SMMU node : IORT proximity domain.
>> + * - Vendor node : hardware ID.
>> + * - GIC node : interface type.
>> + */
>> + u32 id0;
>> + /*
>> + * Different nodes have different meanings:
>> + * - Processor node : processor resource type.
>> + * - Memory node : Non.
>> + * - SMMU node : subcomponent reference.
>> + * - Vendor node : Unique ID.
>> + * - GIC node : instance identifier.
>> + */
>> + u32 id1;
>> + int index;
>> + unsigned long addressing_mode;
>> + struct ras_ext_regs *regs;
>> +};
>
> These are only aest-related structures? If so, I think they should be in
> aest.c file.
>
>> +
>> +#endif /* AEST_H */
>> diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
>> index 624d4a38c358..f0dda08dbad2 100644
>> --- a/include/linux/cpuhotplug.h
>> +++ b/include/linux/cpuhotplug.h
>> @@ -186,6 +186,7 @@ enum cpuhp_state {
>> CPUHP_AP_CSKY_TIMER_STARTING,
>> CPUHP_AP_TI_GP_TIMER_STARTING,
>> CPUHP_AP_HYPERV_TIMER_STARTING,
>> + CPUHP_AP_ARM_AEST_STARTING,
>> /* Must be the last timer callback */
>> CPUHP_AP_DUMMY_TIMER_STARTING,
>> CPUHP_AP_ARM_XEN_STARTING,
All accept, thanks for reviewing.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/2] ACPI/AEST: Initial AEST driver
2024-03-21 2:53 ` [PATCH v2 1/2] ACPI/AEST: Initial AEST driver Ruidong Tian
2024-03-21 3:52 ` Baolin Wang
@ 2024-03-27 5:46 ` Shuai Xue
2024-05-10 5:25 ` Tomohiro Misono (Fujitsu)
2 siblings, 0 replies; 7+ messages in thread
From: Shuai Xue @ 2024-03-27 5:46 UTC (permalink / raw)
To: Ruidong Tian, catalin.marinas, will, lpieralisi, guohanjun,
sudeep.holla, baolin.wang, linux-kernel, linux-acpi,
linux-arm-kernel, rafael, lenb, tony.luck, bp, linux-edac,
james.morse
Cc: tianruidond, Tyler Baicar
On 2024/3/21 10:53, Ruidong Tian wrote:
> From: Tyler Baicar <baicar@os.amperecomputing.com>
>
> Add support for parsing the ARM Error Source Table and basic handling of
> errors reported through both memory mapped and system register interfaces.
>
> Assume system register interfaces are only registered with private
> peripheral interrupts (PPIs); otherwise there is no guarantee the
> core handling the error is the core which took the error and has the
> syndrome info in its system registers.
>
> In kernel-first mode, all configuration is controlled by kernel, include
> CE ce_threshold and interrupt enable/disable.
>
> All detected errors will be processed as follow:
> - CE, DE: use a workqueue to log this hardware errors.
> - UER, UEO: log it and call memory_failure in workquee.
> - UC, UEU: panic in irq context.
>
> Signed-off-by: Tyler Baicar <baicar@os.amperecomputing.com>
> Signed-off-by: Ruidong Tian <tianruidong@linux.alibaba.com>
> ---
> MAINTAINERS | 11 +
> arch/arm64/include/asm/ras.h | 71 +++
> drivers/acpi/arm64/Kconfig | 10 +
> drivers/acpi/arm64/Makefile | 1 +
> drivers/acpi/arm64/aest.c | 834 +++++++++++++++++++++++++++++++++++
> include/linux/acpi_aest.h | 92 ++++
> include/linux/cpuhotplug.h | 1 +
> 7 files changed, 1020 insertions(+)
> create mode 100644 arch/arm64/include/asm/ras.h
> create mode 100644 drivers/acpi/arm64/aest.c
> create mode 100644 include/linux/acpi_aest.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dd5de540ec0b..34900d4bb677 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -330,6 +330,17 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Maintained
> F: drivers/acpi/arm64
>
> +ACPI AEST
> +M: Tyler Baicar <baicar@os.amperecomputing.com>
> +M: Ruidong Tian <tianruidond@linux.alibaba.com>
> +L: linux-acpi@vger.kernel.org
> +L: linux-arm-kernel@lists.infradead.org
> +S: Supported
> +F: arch/arm64/include/asm/ras.h
> +F: drivers/acpi/arm64/aest.c
> +F: include/linux/acpi_aest.h
> +
> +
> ACPI FOR RISC-V (ACPI/riscv)
> M: Sunil V L <sunilvl@ventanamicro.com>
> L: linux-acpi@vger.kernel.org
> diff --git a/arch/arm64/include/asm/ras.h b/arch/arm64/include/asm/ras.h
> new file mode 100644
> index 000000000000..04667f0de30f
> --- /dev/null
> +++ b/arch/arm64/include/asm/ras.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __ASM_RAS_H
> +#define __ASM_RAS_H
> +
> +#include <linux/types.h>
> +#include <linux/bits.h>
> +
> +/* ERR<n>FR */
> +#define ERR_FR_RP BIT(15)
> +#define ERR_FR_CEC GENMASK_ULL(14, 12)
> +
> +#define ERR_FR_RP_SINGLE_COUNTER 0
> +#define ERR_FR_RP_DOUBLE_COUNTER 1
> +
> +#define ERR_FR_CEC_0B_COUNTER 0
> +#define ERR_FR_CEC_8B_COUNTER BIT(1)
> +#define ERR_FR_CEC_16B_COUNTER BIT(2)
> +
> +/* ERR<n>STATUS */
> +#define ERR_STATUS_AV BIT(31)
> +#define ERR_STATUS_V BIT(30)
> +#define ERR_STATUS_UE BIT(29)
> +#define ERR_STATUS_ER BIT(28)
> +#define ERR_STATUS_OF BIT(27)
> +#define ERR_STATUS_MV BIT(26)
> +#define ERR_STATUS_CE (BIT(25) | BIT(24))
> +#define ERR_STATUS_DE BIT(23)
> +#define ERR_STATUS_PN BIT(22)
> +#define ERR_STATUS_UET (BIT(21) | BIT(20))
> +#define ERR_STATUS_CI BIT(19)
> +#define ERR_STATUS_IERR GENMASK_ULL(15, 8)
> +#define ERR_STATUS_SERR GENMASK_ULL(7, 0)
> +
> +/* These bit is write-one-to-clear */
Typo: s/These bit is/These bits are
> +#define ERR_STATUS_W1TC (ERR_STATUS_AV | ERR_STATUS_V | ERR_STATUS_UE | \
> + ERR_STATUS_ER | ERR_STATUS_OF | ERR_STATUS_MV | \
> + ERR_STATUS_CE | ERR_STATUS_DE | ERR_STATUS_PN | \
> + ERR_STATUS_UET | ERR_STATUS_CI)
> +
> +#define ERR_STATUS_UET_UC 0
> +#define ERR_STATUS_UET_UEU 1
> +#define ERR_STATUS_UET_UER 2
> +#define ERR_STATUS_UET_UEO 3
> +
> +/* ERR<n>CTLR */
> +#define ERR_CTLR_FI BIT(3)
> +#define ERR_CTLR_UI BIT(2)
> +
> +/* ERR<n>ADDR */
> +#define ERR_ADDR_AI BIT(61)
> +#define ERR_ADDR_PADDR GENMASK_ULL(55, 0)
> +
> +/* ERR<n>MISC0 */
> +
> +/* ERR<n>FR.CEC == 0b010, ERR<n>FR.RP == 0 */
> +#define ERR_MISC0_8B_OF BIT(39)
> +#define ERR_MISC0_8B_CEC GENMASK_ULL(38, 32)
> +
> +/* ERR<n>FR.CEC == 0b100, ERR<n>FR.RP == 0 */
> +#define ERR_MISC0_16B_OF BIT(47)
> +#define ERR_MISC0_16B_CEC GENMASK_ULL(46, 32)
> +
> +struct ras_ext_regs {
> + u64 err_fr;
> + u64 err_ctlr;
> + u64 err_status;
> + u64 err_addr;
> + u64 err_misc[4];
> +};
> +
> +#endif /* __ASM_RAS_H */
> diff --git a/drivers/acpi/arm64/Kconfig b/drivers/acpi/arm64/Kconfig
> index b3ed6212244c..639db671c5cf 100644
> --- a/drivers/acpi/arm64/Kconfig
> +++ b/drivers/acpi/arm64/Kconfig
> @@ -21,3 +21,13 @@ config ACPI_AGDI
>
> config ACPI_APMT
> bool
> +
> +config ACPI_AEST
> + bool "ARM Error Source Table Support"
> +
> + help
> + The Arm Error Source Table (AEST) provides details on ACPI
> + extensions that enable kernel-first handling of errors in a
> + system that supports the Armv8 RAS extensions.
> +
> + If set, the kernel will report and log hardware errors.
> diff --git a/drivers/acpi/arm64/Makefile b/drivers/acpi/arm64/Makefile
> index 143debc1ba4a..b5b740058c46 100644
> --- a/drivers/acpi/arm64/Makefile
> +++ b/drivers/acpi/arm64/Makefile
> @@ -5,3 +5,4 @@ obj-$(CONFIG_ACPI_GTDT) += gtdt.o
> obj-$(CONFIG_ACPI_APMT) += apmt.o
> obj-$(CONFIG_ARM_AMBA) += amba.o
> obj-y += dma.o init.o
> +obj-$(CONFIG_ACPI_AEST) += aest.o
> diff --git a/drivers/acpi/arm64/aest.c b/drivers/acpi/arm64/aest.c
> new file mode 100644
> index 000000000000..ab17aa5f5997
> --- /dev/null
> +++ b/drivers/acpi/arm64/aest.c
> @@ -0,0 +1,834 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ARM Error Source Table Support
> + *
> + * Copyright (c) 2021, Ampere Computing LLC
> + * Copyright (c) 2021-2024, Alibaba Group.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/acpi_aest.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/kernel.h>
> +#include <linux/genalloc.h>
> +#include <linux/llist.h>
> +#include <acpi/actbl.h>
> +#include <asm/ras.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "ACPI AEST: " fmt
> +
> +#define CASE_READ(res, x) \
> + case (x): { \
> + res = read_sysreg_s(SYS_##x##_EL1); \
> + break; \
> + }
> +
> +#define CASE_WRITE(val, x) \
> + case (x): { \
> + write_sysreg_s((val), SYS_##x##_EL1); \
> + break; \
> + }
> +
> +#define for_each_implemented_record(index, node) \
> + for ((index) = node->interface.record_start; \
> + (index) < node->interface.record_end; \
> + (index)++)
> +
> +#define AEST_LOG_PREFIX_BUFFER 64
> +
> +/*
> + * This memory pool is only to be used to save AEST node in AEST irq context.
> + * There can be 500 AEST node at most.
> + */
> +#define AEST_NODE_ALLOCED_MAX 500
> +
> +static struct acpi_table_header *aest_table;
> +
> +static struct aest_node __percpu **aest_ppi_data;
> +
> +static int *ppi_irqs;
> +static u8 num_ppi;
> +static u8 ppi_idx;
> +
> +static struct work_struct aest_work;
> +
> +static struct gen_pool *aest_node_pool;
> +static struct llist_head aest_node_llist;
> +
> +static u64 aest_sysreg_read(u64 __unused, u32 offset)
> +{
> + u64 res;
> +
> + switch (offset) {
> + CASE_READ(res, ERXFR)
> + CASE_READ(res, ERXCTLR)
> + CASE_READ(res, ERXSTATUS)
> + CASE_READ(res, ERXADDR)
> + CASE_READ(res, ERXMISC0)
> + CASE_READ(res, ERXMISC1)
> + CASE_READ(res, ERXMISC2)
> + CASE_READ(res, ERXMISC3)
> + default :
> + res = 0;
> + }
> + return res;
> +}
> +
> +static void aest_sysreg_write(u64 base, u32 offset, u64 val)
> +{
> + switch (offset) {
> + CASE_WRITE(val, ERXFR)
> + CASE_WRITE(val, ERXCTLR)
> + CASE_WRITE(val, ERXSTATUS)
> + CASE_WRITE(val, ERXADDR)
> + CASE_WRITE(val, ERXMISC0)
> + CASE_WRITE(val, ERXMISC1)
> + CASE_WRITE(val, ERXMISC2)
> + CASE_WRITE(val, ERXMISC3)
> + default :
> + return;
> + }
> +}
> +
> +static u64 aest_iomem_read(u64 base, u32 offset)
> +{
> + return readq_relaxed((void *)(base + offset));
> +}
> +
> +static void aest_iomem_write(u64 base, u32 offset, u64 val)
> +{
> + writeq_relaxed(val, (void *)(base + offset));
> +}
> +
> +static void aest_print(struct aest_node_llist *lnode)
> +{
> + static atomic_t seqno = { 0 };
> + unsigned int curr_seqno;
> + char pfx_seq[AEST_LOG_PREFIX_BUFFER];
> + int index;
> + struct ras_ext_regs *regs;
> +
> + curr_seqno = atomic_inc_return(&seqno);
> + snprintf(pfx_seq, sizeof(pfx_seq), "{%u}" HW_ERR, curr_seqno);
> + pr_info("%sHardware error from %s\n", pfx_seq, lnode->node_name);
> +
> + switch (lnode->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + pr_err("%s Error from CPU%d\n", pfx_seq, lnode->id0);
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + pr_err("%s Error from memory at SRAT proximity domain 0x%x\n",
> + pfx_seq, lnode->id0);
> + break;
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + pr_err("%s Error from SMMU IORT node 0x%x subcomponent 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + pr_err("%s Error from vendor hid 0x%x uid 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + case ACPI_AEST_GIC_ERROR_NODE:
> + pr_err("%s Error from GIC type 0x%x instance 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + default:
> + pr_err("%s Unknown AEST node type\n", pfx_seq);
> + return;
> + }
> +
> + index = lnode->index;
> + regs = lnode->regs;
> +
> + pr_err("%s ERR%uFR: 0x%llx\n", pfx_seq, index, regs->err_fr);
> + pr_err("%s ERR%uCTRL: 0x%llx\n", pfx_seq, index, regs->err_ctlr);
> + pr_err("%s ERR%uSTATUS: 0x%llx\n", pfx_seq, index, regs->err_status);
> + if (regs->err_status & ERR_STATUS_AV)
> + pr_err("%s ERR%uADDR: 0x%llx\n", pfx_seq, index, regs->err_addr);
> +
> + if (regs->err_status & ERR_STATUS_MV) {
> + pr_err("%s ERR%uMISC0: 0x%llx\n", pfx_seq, index, regs->err_misc[0]);
> + pr_err("%s ERR%uMISC1: 0x%llx\n", pfx_seq, index, regs->err_misc[1]);
> + pr_err("%s ERR%uMISC2: 0x%llx\n", pfx_seq, index, regs->err_misc[2]);
> + pr_err("%s ERR%uMISC3: 0x%llx\n", pfx_seq, index, regs->err_misc[3]);
> + }
> +}
> +
> +static void aest_handle_memory_failure(struct aest_node_llist *lnode)
> +{
> + unsigned long pfn;
> + u64 addr;
> +
> + if (test_bit(lnode->index, &lnode->addressing_mode) ||
> + (lnode->regs->err_addr & ERR_ADDR_AI))
> + return;
> +
> + addr = lnode->regs->err_addr & (1UL << CONFIG_ARM64_PA_BITS);
> + pfn = PHYS_PFN(addr);
> +
> + if (!pfn_valid(pfn)) {
> + pr_warn(HW_ERR "Invalid physical address: %#llx\n", addr);
> + return;
> + }
> +
> + memory_failure(pfn, 0);
> +}
> +
> +static void aest_node_pool_process(struct work_struct *__unused)
> +{
> + struct llist_node *head;
> + struct aest_node_llist *lnode, *tmp;
> + u64 status;
> +
> + head = llist_del_all(&aest_node_llist);
> + if (!head)
> + return;
> +
> + head = llist_reverse_order(head);
> + llist_for_each_entry_safe(lnode, tmp, head, llnode) {
Do we really need to pretect the llnode with _safe() here?
> + aest_print(lnode);
> +
> + status = lnode->regs->err_status;
> + if ((status & ERR_STATUS_UE) &&
> + (status & ERR_STATUS_UET) > ERR_STATUS_UET_UEU)
> + aest_handle_memory_failure(lnode);
> + gen_pool_free(aest_node_pool, (unsigned long)lnode,
> + sizeof(*lnode));
> + }
> +}
> +
> +static int aest_node_gen_pool_add(struct aest_node *node, int index,
> + struct ras_ext_regs *regs)
> +{
> + struct aest_node_llist *list;
I know name is alway a hard work, but list is meanless to aest.
> +
> + if (!aest_node_pool)
> + return -EINVAL;
> +
> + list = (void *)gen_pool_alloc(aest_node_pool, sizeof(*list));
> + if (!list)
> + return -ENOMEM;
> +
> + list->type = node->type;
> + list->node_name = node->name;
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + list->id0 = node->spec.processor.processor_id;
> + if (node->spec.processor.flags & (ACPI_AEST_PROC_FLAG_SHARED |
> + ACPI_AEST_PROC_FLAG_GLOBAL))
> + list->id0 = smp_processor_id();
> +
> + list->id1 = node->spec.processor.resource_type;
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + list->id0 = node->spec.memory.srat_proximity_domain;
> + break;
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + list->id0 = node->spec.smmu.iort_node_reference;
> + list->id1 = node->spec.smmu.subcomponent_reference;
> + break;
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + list->id0 = node->spec.vendor.acpi_hid;
> + list->id1 = node->spec.vendor.acpi_uid;
> + break;
> + case ACPI_AEST_GIC_ERROR_NODE:
> + list->id0 = node->spec.gic.interface_type;
> + list->id1 = node->spec.gic.instance_id;
> + break;
> + default:
> + list->id0 = 0;
> + list->id1 = 0;
> + }
> +
> + list->regs = regs;
> + list->index = index;
> + list->addressing_mode = node->interface.addressing_mode;
> + llist_add(&list->llnode, &aest_node_llist);
> +
> + return 0;
> +}
> +
> +static int aest_node_pool_init(void)
> +{
> + unsigned long addr, size;
> + int rc;
> +
> + if (aest_node_pool)
> + return 0;
> +
> + size = ilog2(sizeof(struct aest_node_llist));
> + aest_node_pool = gen_pool_create(size, -1);
> + if (!aest_node_pool)
> + return -ENOMEM;
> +
> + addr = (unsigned long)vmalloc(PAGE_ALIGN(size * AEST_NODE_ALLOCED_MAX));
> + if (!addr)
> + goto err_pool_alloc;
> +
> + rc = gen_pool_add(aest_node_pool, addr, size, -1);
The size you added here is not equal to the size of buffer you alloced from vmalloc().
> + if (rc)
> + goto err_pool_add;
> +
> + return 0;
> +
> +err_pool_add:
> + vfree((void *)addr);
> +
> +err_pool_alloc:
> + gen_pool_destroy(aest_node_pool);
> +
> + return -ENOMEM;
> +}
> +
> +static void aest_log(struct aest_node *node, int index, struct ras_ext_regs *regs)
> +{
> + if (!aest_node_gen_pool_add(node, index, regs))
> + schedule_work(&aest_work);
> +}
> +
> +/*
> + * Each PE may has multi error record, you must selects an error record to
> + * be accessed through the Error Record System registers.
> + */
> +static inline void aest_select_record(struct aest_node *node, int i)
> +{
> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER) {
> + write_sysreg_s(i, SYS_ERRSELR_EL1);
should check if ERRSELR_EL1.SEL is greater than or equal to ERRIDR_EL1.NUM,
> + isb();
> + }
> +}
> +
> +/* Ensure all writes has taken effect. */
> +static inline void aest_sync(struct aest_node *node)
> +{
> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER)
> + isb();
> +}
> +
> +static int aest_proc(struct aest_node *node)
> +{
> + struct ras_ext_regs regs = {0};
> + struct aest_access *access;
> + int i, count = 0;
> + u64 regs_p;
> +
> + for_each_implemented_record(i, node) {
> +
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> +
> + access = node->access;
> + regs_p = (u64)&node->interface.regs[i];
> +
> + regs.err_status = access->read(regs_p, ERXSTATUS);
> + if (!(regs.err_status & ERR_STATUS_V))
> + continue;
> +
> + count++;
> +
> + if (regs.err_status & ERR_STATUS_AV)
> + regs.err_addr = access->read(regs_p, ERXADDR);
> +
> + regs.err_fr = access->read(regs_p, ERXFR);
> + regs.err_ctlr = access->read(regs_p, ERXCTLR);
> +
> + if (regs.err_status & ERR_STATUS_MV) {
> + regs.err_misc[0] = access->read(regs_p, ERXMISC0);
> + regs.err_misc[1] = access->read(regs_p, ERXMISC1);
> + regs.err_misc[2] = access->read(regs_p, ERXMISC2);
> + regs.err_misc[3] = access->read(regs_p, ERXMISC3);
> + }
> +
> + if (node->interface.flags & ACPI_AEST_INTERFACE_CLEAR_MISC) {
> + access->write(regs_p, ERXMISC0, 0);
> + access->write(regs_p, ERXMISC1, 0);
> + access->write(regs_p, ERXMISC2, 0);
> + access->write(regs_p, ERXMISC3, 0);
> + } else
> + access->write(regs_p, ERXMISC0,
> + node->interface.ce_threshold[i]);
> +
> + aest_log(node, i, ®s);
aest_log() should be calledafter panic check?
> +
> + /* panic if unrecoverable and uncontainable error encountered */
> + if ((regs.err_status & ERR_STATUS_UE) &&
> + (regs.err_status & ERR_STATUS_UET) < ERR_STATUS_UET_UER)
> + panic("AEST: unrecoverable error encountered");
Is arm64_is_fatal_ras_serror() applicable here? @James, can you help to confirm?
> +
> + /* Write-one-to-clear the bits we've seen */
> + regs.err_status &= ERR_STATUS_W1TC;
> +
> + /* Multi bit filed need to write all-ones to clear. */
> + if (regs.err_status & ERR_STATUS_CE)
> + regs.err_status |= ERR_STATUS_CE;
> +
> + /* Multi bit filed need to write all-ones to clear. */
> + if (regs.err_status & ERR_STATUS_UET)
> + regs.err_status |= ERR_STATUS_UET;
> +
> + access->write(regs_p, ERXSTATUS, regs.err_status);
> +
> + aest_sync(node);
> + }
> +
> + return count;
> +}
> +
> +static irqreturn_t aest_irq_func(int irq, void *input)
> +{
> + struct aest_node *node = input;
> +
> + if (aest_proc(node))
> + return IRQ_HANDLED;
> +
> + return IRQ_NONE;
> +}
> +
> +static int __init aest_register_gsi(u32 gsi, int trigger, void *data,
> + irq_handler_t aest_irq_func)
> +{
> + int cpu, irq;
> +
> + irq = acpi_register_gsi(NULL, gsi, trigger, ACPI_ACTIVE_HIGH);
> +
> + if (irq == -EINVAL) {
> + pr_err("failed to map AEST GSI %d\n", gsi);
> + return -EINVAL;
> + }
> +
> + if (irq_is_percpu_devid(irq)) {
> + ppi_irqs[ppi_idx] = irq;
> + for_each_possible_cpu(cpu) {
> + memcpy(per_cpu_ptr(aest_ppi_data[ppi_idx], cpu), data,
> + sizeof(struct aest_node));
> + }
> + if (request_percpu_irq(irq, aest_irq_func, "AEST",
> + aest_ppi_data[ppi_idx++])) {
> + pr_err("failed to register AEST IRQ %d\n", irq);
> + return -EINVAL;
> + }
> + } else {
> + if (request_irq(irq, aest_irq_func, IRQF_SHARED, "AEST",
> + data)) {
> + pr_err("failed to register AEST IRQ %d\n", irq);
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init aest_init_interrupts(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + struct acpi_aest_node_interrupt *interrupt;
> + int i, trigger, ret = 0, err_ctlr, regs_p;
> +
> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, hdr,
> + hdr->node_interrupt_offset);
> +
> + for (i = 0; i < hdr->node_interrupt_count; i++, interrupt++) {
> + trigger = (interrupt->flags & AEST_INTERRUPT_MODE) ?
> + ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
> + if (aest_register_gsi(interrupt->gsiv, trigger, node,
> + aest_irq_func))
> + ret = -EINVAL;
> + }
> +
> + /* Ensure RAS interrupt is enabled */
> + for_each_implemented_record(i, node) {
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> +
> + regs_p = (u64)&node->interface.regs[i];
> +
> + err_ctlr = node->access->read(regs_p, ERXCTLR);
> +
> + if (interrupt->type == ACPI_AEST_NODE_FAULT_HANDLING)
> + err_ctlr |= ERR_CTLR_FI;
> + if (interrupt->type == ACPI_AEST_NODE_ERROR_RECOVERY)
> + err_ctlr |= ERR_CTLR_UI;
Fault handling interrupts (ERR<n>CTLR.CFI) on corrected errors should also be enabled.
> +
> + node->access->write(regs_p, ERXCTLR, err_ctlr);
> +
> + aest_sync(node);
> + }
> +
> + return ret;
> +}
> +
> +static void __init set_aest_node_name(struct aest_node *node)
> +{
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + node->name = kasprintf(GFP_KERNEL, "AEST-CPU%d",
> + node->spec.processor.processor_id);
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + case ACPI_AEST_GIC_ERROR_NODE:
> + node->name = kasprintf(GFP_KERNEL, "AEST-%llx",
> + node->interface.phy_addr);
> + break;
> + default:
> + node->name = kasprintf(GFP_KERNEL, "AEST-Unkown-Node");
> + }
> +}
> +
> +/* access type is decided by AEST interface type. */
> +static struct aest_access aest_access[] = {
> + [ACPI_AEST_NODE_SYSTEM_REGISTER] = {
> + .read = aest_sysreg_read,
> + .write = aest_sysreg_write,
> + },
> +
> + [ACPI_AEST_NODE_MEMORY_MAPPED] = {
> + .read = aest_iomem_read,
> + .write = aest_iomem_write,
> + },
> + { }
> +};
> +
> +/* In kernel-first mode, kernel will report every CE by default. */
> +static void __init aest_set_ce_threshold(struct aest_node *node)
> +{
> + u64 regs_p, err_fr, err_fr_cec, err_fr_rp, err_misc0, ce_threshold;
> + int i;
> +
> + for_each_implemented_record(i, node) {
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> + regs_p = (u64)&node->interface.regs[i];
> +
> + err_fr = node->access->read(regs_p, ERXFR);
> + err_fr_cec = FIELD_GET(ERR_FR_CEC, err_fr);
> + err_fr_rp = FIELD_GET(ERR_FR_RP, err_fr);
> + err_misc0 = node->access->read(regs_p, ERXMISC0);
> +
> + if (err_fr_cec == ERR_FR_CEC_0B_COUNTER)
> + pr_debug("%s-%d do not support CE threshold!\n",
> + node->name, i);
Quoted from ARM RAS spec:
If the node implements a corrected error counter or counters, then a corrected error event is defined as
follows:
• A corrected error event occurs when a counter overflows and sets a counter overflow flag to 0b1.
Otherwise, a corrected error event occurs when the error record records an error as a Corrected error.
So, if the node does not support CE threshold, it will report every CE and no ce_threshold should be set.
> + else if (err_fr_cec == ERR_FR_CEC_8B_COUNTER &&
> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
> + pr_debug("%s-%d support 8 bit CE threshold!\n",
> + node->name, i);
> + ce_threshold = err_misc0 | ERR_MISC0_8B_CEC;
> + } else if (err_fr_cec == ERR_FR_CEC_16B_COUNTER &&
> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
> + pr_debug("%s-%d support 16 bit CE threshold!\n",
> + node->name, i);
> + ce_threshold = err_misc0 | ERR_MISC0_16B_CEC;
> + } else
> + pr_debug("%s-%d do not support double counter yet!\n",
> + node->name, i);
> +
> + node->access->write(regs_p, ERXMISC0, ce_threshold);
ce_threshold may be uninited in some of above if-else branch.
> + node->interface.ce_threshold[i] = ce_threshold;
> +
> + aest_sync(node);
> + }
> +}
> +
> +static int __init aest_init_interface(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + struct acpi_aest_node_interface *interface;
> + struct resource *res;
> + int size;
> +
> + interface = ACPI_ADD_PTR(struct acpi_aest_node_interface, hdr,
> + hdr->node_interface_offset);
> +
> + if (interface->type >= ACPI_AEST_XFACE_RESERVED) {
> + pr_err("invalid interface type: %d\n", interface->type);
> + return -EINVAL;
> + }
> +
> + node->interface.type = interface->type;
> + node->interface.phy_addr = interface->address;
> + node->interface.record_start = interface->error_record_index;
> + node->interface.record_end = interface->error_record_index +
> + interface->error_record_count;
Why rename the field name here?
> + node->interface.flags = interface->flags;
> + node->interface.record_implemented = interface->error_record_implemented;
> + node->interface.status_reporting = interface->error_status_reporting;
> + node->interface.addressing_mode = interface->addressing_mode;
> + node->access = &aest_access[interface->type];
> +> +
> +
> + /*
> + * Currently SR based handling is done through the architected
> + * discovery exposed through SRs. That may change in the future
> + * if there is supplemental information in the AEST that is
> + * needed.
> + */
> + if (interface->type == ACPI_AEST_NODE_SYSTEM_REGISTER)
> + return 0;
> +
> + res = kzalloc(sizeof(struct resource), GFP_KERNEL);
> + if (!res)
> + return -ENOMEM;
> +
> + size = interface->error_record_count * sizeof(struct ras_ext_regs);
> + res->name = "AEST";
> + res->start = interface->address;
> + res->end = res->start + size;
> + res->flags = IORESOURCE_MEM;
> +
> + if (insert_resource(&iomem_resource, res)) {
> + pr_notice("request region conflict with %s\n",
> + res->name);
> + }
> +
> + node->interface.regs = ioremap(res->start, size);
> + if (!node->interface.regs) {
> + pr_err("Ioremap for %s failed!\n", node->name);
> + kfree(res);
> + return -EINVAL;
> + }
> +
> + node->interface.ce_threshold = kzalloc(sizeof(u64) *
> + interface->error_record_count, GFP_KERNEL);
> + if (!node->interface.ce_threshold)
> + return -ENOMEM;
> +
> + aest_set_ce_threshold(node);
> +
> + return 0;
> +}
> +
> +static int __init aest_init_common(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + int ret;
> +
> + set_aest_node_name(node);
> +
> + ret = aest_init_interface(hdr, node);
> + if (ret) {
> + pr_err("failed to init interface\n");
> + return ret;
> + }
> +
> + return aest_init_interrupts(hdr, node);
> +}
> +
> +static int __init aest_init_node_default(struct acpi_aest_hdr *hdr)
> +{
> + struct aest_node *node;
> + union aest_node_spec *node_spec;
> + int ret;
> +
> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
e.g. If the second node is failed to alloced, where you free the first node?
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = hdr->type;
> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
> + hdr->node_specific_offset);
> +
> + memcpy(&node->spec, node_spec,
> + hdr->node_interface_offset - hdr->node_specific_offset);
> +
> + ret = aest_init_common(hdr, node);
> + if (ret)
> + kfree(node);
> +
> + return ret;
> +}
> +
> +static int __init aest_init_processor_node(struct acpi_aest_hdr *hdr)
> +{
> + struct aest_node *node;
> + union aest_node_spec *node_spec;
> + union aest_node_processor *proc;
> + int ret;
> +
> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = hdr->type;
> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
> + hdr->node_specific_offset);
> +
> + memcpy(&node->spec, node_spec,
> + hdr->node_interface_offset - hdr->node_specific_offset);
> +
> + proc = ACPI_ADD_PTR(union aest_node_processor, node_spec,
> + sizeof(acpi_aest_processor));
> +
> + switch (node->spec.processor.resource_type) {
> + case ACPI_AEST_CACHE_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_cache));
> + break;
> + case ACPI_AEST_TLB_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_tlb));
> + break;
> + case ACPI_AEST_GENERIC_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_generic));
> + break;
> + }
> +
> + ret = aest_init_common(hdr, node);
> + if (ret)
> + kfree(node);
> +
> + return ret;
> +}
> +
> +static int __init aest_init_node(struct acpi_aest_hdr *node)
> +{
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + return aest_init_processor_node(node);
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + case ACPI_AEST_GIC_ERROR_NODE:
> + return aest_init_node_default(node);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static void __init aest_count_ppi(struct acpi_aest_hdr *header)
> +{
> + struct acpi_aest_node_interrupt *interrupt;
> + int i;
> +
> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, header,
> + header->node_interrupt_offset);
> +
> + for (i = 0; i < header->node_interrupt_count; i++, interrupt++) {
> + if (interrupt->gsiv >= 16 && interrupt->gsiv < 32)
> + num_ppi++;
> + }
> +}
> +
> +static int aest_starting_cpu(unsigned int cpu)
> +{
> + int i;
> +
> + for (i = 0; i < num_ppi; i++)
> + enable_percpu_irq(ppi_irqs[i], IRQ_TYPE_NONE);
> +
> + return 0;
> +}
> +
> +static int aest_dying_cpu(unsigned int cpu)
> +{
> + int i;
> +
> + for (i = 0; i < num_ppi; i++)
> + disable_percpu_irq(ppi_irqs[i]);
> +
> + return 0;
> +}
> +
> +int __init acpi_aest_init(void)
> +{
> + struct acpi_aest_hdr *aest_node, *aest_end;
> + struct acpi_table_aest *aest;
> + int i, ret = 0;
> +
> + if (acpi_disabled)
> + return 0;
> +
> + if (!IS_ENABLED(CONFIG_ARM64_RAS_EXTN))
> + return 0;
> +
> + if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_AEST, 0, &aest_table)))
> + return -EINVAL;
> +
> + ret = aest_node_pool_init();
> + if (ret) {
> + pr_err("Failed init aest node pool.\n");
> + goto fail;
> + }
> +
> + INIT_WORK(&aest_work, aest_node_pool_process);
> +
> + aest = (struct acpi_table_aest *)aest_table;
> +
> + /* Get the first AEST node */
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + sizeof(struct acpi_table_header));
> + /* Pointer to the end of the AEST table */
> + aest_end = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + aest_table->length);
> +
> + while (aest_node < aest_end) {
> + if (((u64)aest_node + aest_node->length) > (u64)aest_end) {
> + pr_err("AEST node pointer overflow, bad table.\n");
> + return -EINVAL;
> + }
> +
> + aest_count_ppi(aest_node);
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
> + aest_node->length);
> + }
> +
> + aest_ppi_data = kcalloc(num_ppi, sizeof(struct aest_node_data *),
> + GFP_KERNEL);
> + if (!aest_ppi_data) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + ppi_irqs = kcalloc(num_ppi, sizeof(int), GFP_KERNEL);
> + if (!ppi_irqs) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + for (i = 0; i < num_ppi; i++) {
> + aest_ppi_data[i] = alloc_percpu(struct aest_node);
> + if (!aest_ppi_data[i]) {
> + pr_err("Failed percpu allocation.\n");
> + ret = -ENOMEM;
> + goto fail;
> + }
> + }
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + sizeof(struct acpi_table_header));
> +
> + while (aest_node < aest_end) {
A macro for_each_aest_node() will be more readable.
> + ret = aest_init_node(aest_node);
> + if (ret) {
> + pr_err("failed to init node: %d", ret);
> + goto fail;
> + }
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
> + aest_node->length);
> + }
> +
> +
> +
> + return cpuhp_setup_state(CPUHP_AP_ARM_AEST_STARTING,
> + "drivers/acpi/arm64/aest:starting",
> + aest_starting_cpu, aest_dying_cpu);
> +
> +fail:
> + for (i = 0; i < num_ppi; i++)
> + free_percpu(aest_ppi_data[i]);
You should free the aest_ppi_data[0:i], aest_ppi_data[i:num_ppi] are not alloc_percpu().
> + kfree(aest_ppi_data);
> + return ret;
> +}
> +subsys_initcall(acpi_aest_init);
> diff --git a/include/linux/acpi_aest.h b/include/linux/acpi_aest.h
> new file mode 100644
> index 000000000000..679187505dc6
> --- /dev/null
> +++ b/include/linux/acpi_aest.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef AEST_H
> +#define AEST_H
> +
> +#include <acpi/actbl.h>
> +#include <asm/ras.h>
> +
> +#define AEST_INTERRUPT_MODE BIT(0)
> +
> +#define ACPI_AEST_PROC_FLAG_GLOBAL (1<<0)
> +#define ACPI_AEST_PROC_FLAG_SHARED (1<<1)
> +
> +#define ACPI_AEST_INTERFACE_CLEAR_MISC (1<<0)
> +
> +#define ERXFR 0x0
> +#define ERXCTLR 0x8
> +#define ERXSTATUS 0x10
> +#define ERXADDR 0x18
> +#define ERXMISC0 0x20
> +#define ERXMISC1 0x28
> +#define ERXMISC2 0x30
> +#define ERXMISC3 0x38
> +
> +struct aest_node_interface {
> + u8 type;
> + u64 phy_addr;
> + u16 record_start;
> + u16 record_end;
> + u32 flags;
> + unsigned long record_implemented;
> + unsigned long status_reporting;
> + unsigned long addressing_mode;
> + struct ras_ext_regs *regs;
> + u64 *ce_threshold;
> +};
> +
> +union aest_node_processor {
> + struct acpi_aest_processor_cache cache_data;
> + struct acpi_aest_processor_tlb tlb_data;
> + struct acpi_aest_processor_generic generic_data;
> +};
> +
> +union aest_node_spec {
> + struct acpi_aest_processor processor;
> + struct acpi_aest_memory memory;
> + struct acpi_aest_smmu smmu;
> + struct acpi_aest_vendor vendor;
> + struct acpi_aest_gic gic;
> +};
> +
> +struct aest_access {
> + u64 (*read)(u64 base, u32 offset);
> + void (*write)(u64 base, u32 offset, u64 val);
> +};
> +
> +struct aest_node {
> + char *name;
> + u8 type;
> + struct aest_node_interface interface;
> + union aest_node_spec spec;
> + union aest_node_processor proc;
> + struct aest_access *access;
> +};
> +
> +struct aest_node_llist {
> + struct llist_node llnode;
> + char *node_name;
> + int type;
> + /*
> + * Different nodes have different meanings:
> + * - Processor node : processor number.
> + * - Memory node : SRAT proximity domain.
> + * - SMMU node : IORT proximity domain.
> + * - Vendor node : hardware ID.
> + * - GIC node : interface type.
> + */
> + u32 id0;
> + /*
> + * Different nodes have different meanings:
> + * - Processor node : processor resource type.
> + * - Memory node : Non.
> + * - SMMU node : subcomponent reference.
> + * - Vendor node : Unique ID.
> + * - GIC node : instance identifier.
> + */
> + u32 id1;
> + int index;
> + unsigned long addressing_mode;
> + struct ras_ext_regs *regs;
> +};
> +
> +#endif /* AEST_H */
> diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> index 624d4a38c358..f0dda08dbad2 100644
> --- a/include/linux/cpuhotplug.h
> +++ b/include/linux/cpuhotplug.h
> @@ -186,6 +186,7 @@ enum cpuhp_state {
> CPUHP_AP_CSKY_TIMER_STARTING,
> CPUHP_AP_TI_GP_TIMER_STARTING,
> CPUHP_AP_HYPERV_TIMER_STARTING,
> + CPUHP_AP_ARM_AEST_STARTING,
> /* Must be the last timer callback */
> CPUHP_AP_DUMMY_TIMER_STARTING,
> CPUHP_AP_ARM_XEN_STARTING,
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH v2 1/2] ACPI/AEST: Initial AEST driver
2024-03-21 2:53 ` [PATCH v2 1/2] ACPI/AEST: Initial AEST driver Ruidong Tian
2024-03-21 3:52 ` Baolin Wang
2024-03-27 5:46 ` Shuai Xue
@ 2024-05-10 5:25 ` Tomohiro Misono (Fujitsu)
2 siblings, 0 replies; 7+ messages in thread
From: Tomohiro Misono (Fujitsu) @ 2024-05-10 5:25 UTC (permalink / raw)
To: 'Ruidong Tian', 'catalin.marinas@arm.com',
'will@kernel.org', 'lpieralisi@kernel.org',
'guohanjun@huawei.com', 'sudeep.holla@arm.com',
'xueshuai@linux.alibaba.com',
'baolin.wang@linux.alibaba.com',
'linux-kernel@vger.kernel.org',
'linux-acpi@vger.kernel.org',
'linux-arm-kernel@lists.infradead.org',
'rafael@kernel.org', 'lenb@kernel.org',
'tony.luck@intel.com', 'bp@alien8.de',
'linux-edac@vger.kernel.org'
Cc: 'tianruidond@linux.alibaba.com', 'Tyler Baicar'
Hello, some comments below.
> Subject: [PATCH v2 1/2] ACPI/AEST: Initial AEST driver
>
> From: Tyler Baicar <baicar@os.amperecomputing.com>
>
> Add support for parsing the ARM Error Source Table and basic handling of
> errors reported through both memory mapped and system register interfaces.
>
> Assume system register interfaces are only registered with private
> peripheral interrupts (PPIs); otherwise there is no guarantee the
> core handling the error is the core which took the error and has the
> syndrome info in its system registers.
>
> In kernel-first mode, all configuration is controlled by kernel, include
> CE ce_threshold and interrupt enable/disable.
>
> All detected errors will be processed as follow:
> - CE, DE: use a workqueue to log this hardware errors.
> - UER, UEO: log it and call memory_failure in workquee.
> - UC, UEU: panic in irq context.
>
> Signed-off-by: Tyler Baicar <baicar@os.amperecomputing.com>
> Signed-off-by: Ruidong Tian <tianruidong@linux.alibaba.com>
> ---
> MAINTAINERS | 11 +
> arch/arm64/include/asm/ras.h | 71 +++
> drivers/acpi/arm64/Kconfig | 10 +
> drivers/acpi/arm64/Makefile | 1 +
> drivers/acpi/arm64/aest.c | 834 +++++++++++++++++++++++++++++++++++
> include/linux/acpi_aest.h | 92 ++++
> include/linux/cpuhotplug.h | 1 +
> 7 files changed, 1020 insertions(+)
> create mode 100644 arch/arm64/include/asm/ras.h
> create mode 100644 drivers/acpi/arm64/aest.c
> create mode 100644 include/linux/acpi_aest.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dd5de540ec0b..34900d4bb677 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -330,6 +330,17 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Maintained
> F: drivers/acpi/arm64
>
> +ACPI AEST
> +M: Tyler Baicar <baicar@os.amperecomputing.com>
> +M: Ruidong Tian <tianruidond@linux.alibaba.com>
> +L: linux-acpi@vger.kernel.org
> +L: linux-arm-kernel@lists.infradead.org
> +S: Supported
> +F: arch/arm64/include/asm/ras.h
> +F: drivers/acpi/arm64/aest.c
> +F: include/linux/acpi_aest.h
> +
> +
> ACPI FOR RISC-V (ACPI/riscv)
> M: Sunil V L <sunilvl@ventanamicro.com>
> L: linux-acpi@vger.kernel.org
> diff --git a/arch/arm64/include/asm/ras.h b/arch/arm64/include/asm/ras.h
> new file mode 100644
> index 000000000000..04667f0de30f
> --- /dev/null
> +++ b/arch/arm64/include/asm/ras.h
> @@ -0,0 +1,71 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __ASM_RAS_H
> +#define __ASM_RAS_H
> +
> +#include <linux/types.h>
> +#include <linux/bits.h>
> +
> +/* ERR<n>FR */
> +#define ERR_FR_RP BIT(15)
> +#define ERR_FR_CEC GENMASK_ULL(14, 12)
> +
> +#define ERR_FR_RP_SINGLE_COUNTER 0
> +#define ERR_FR_RP_DOUBLE_COUNTER 1
> +
> +#define ERR_FR_CEC_0B_COUNTER 0
> +#define ERR_FR_CEC_8B_COUNTER BIT(1)
> +#define ERR_FR_CEC_16B_COUNTER BIT(2)
> +
> +/* ERR<n>STATUS */
> +#define ERR_STATUS_AV BIT(31)
> +#define ERR_STATUS_V BIT(30)
> +#define ERR_STATUS_UE BIT(29)
> +#define ERR_STATUS_ER BIT(28)
> +#define ERR_STATUS_OF BIT(27)
> +#define ERR_STATUS_MV BIT(26)
> +#define ERR_STATUS_CE (BIT(25) | BIT(24))
> +#define ERR_STATUS_DE BIT(23)
> +#define ERR_STATUS_PN BIT(22)
> +#define ERR_STATUS_UET (BIT(21) | BIT(20))
> +#define ERR_STATUS_CI BIT(19)
> +#define ERR_STATUS_IERR GENMASK_ULL(15, 8)
> +#define ERR_STATUS_SERR GENMASK_ULL(7, 0)
> +
> +/* These bit is write-one-to-clear */
> +#define ERR_STATUS_W1TC (ERR_STATUS_AV | ERR_STATUS_V | ERR_STATUS_UE | \
> + ERR_STATUS_ER | ERR_STATUS_OF | ERR_STATUS_MV | \
> + ERR_STATUS_CE | ERR_STATUS_DE | ERR_STATUS_PN | \
> + ERR_STATUS_UET | ERR_STATUS_CI)
> +
> +#define ERR_STATUS_UET_UC 0
> +#define ERR_STATUS_UET_UEU 1
> +#define ERR_STATUS_UET_UER 2
> +#define ERR_STATUS_UET_UEO 3
According to the spec, I think UER is 3 and UEO is 2.
> +
> +/* ERR<n>CTLR */
> +#define ERR_CTLR_FI BIT(3)
> +#define ERR_CTLR_UI BIT(2)
> +
> +/* ERR<n>ADDR */
> +#define ERR_ADDR_AI BIT(61)
> +#define ERR_ADDR_PADDR GENMASK_ULL(55, 0)
> +
> +/* ERR<n>MISC0 */
> +
> +/* ERR<n>FR.CEC == 0b010, ERR<n>FR.RP == 0 */
> +#define ERR_MISC0_8B_OF BIT(39)
> +#define ERR_MISC0_8B_CEC GENMASK_ULL(38, 32)
> +
> +/* ERR<n>FR.CEC == 0b100, ERR<n>FR.RP == 0 */
> +#define ERR_MISC0_16B_OF BIT(47)
> +#define ERR_MISC0_16B_CEC GENMASK_ULL(46, 32)
> +
> +struct ras_ext_regs {
> + u64 err_fr;
> + u64 err_ctlr;
> + u64 err_status;
> + u64 err_addr;
> + u64 err_misc[4];
> +};
> +
> +#endif /* __ASM_RAS_H */
> diff --git a/drivers/acpi/arm64/Kconfig b/drivers/acpi/arm64/Kconfig
> index b3ed6212244c..639db671c5cf 100644
> --- a/drivers/acpi/arm64/Kconfig
> +++ b/drivers/acpi/arm64/Kconfig
> @@ -21,3 +21,13 @@ config ACPI_AGDI
>
> config ACPI_APMT
> bool
> +
> +config ACPI_AEST
> + bool "ARM Error Source Table Support"
> +
> + help
> + The Arm Error Source Table (AEST) provides details on ACPI
> + extensions that enable kernel-first handling of errors in a
> + system that supports the Armv8 RAS extensions.
> +
> + If set, the kernel will report and log hardware errors.
> diff --git a/drivers/acpi/arm64/Makefile b/drivers/acpi/arm64/Makefile
> index 143debc1ba4a..b5b740058c46 100644
> --- a/drivers/acpi/arm64/Makefile
> +++ b/drivers/acpi/arm64/Makefile
> @@ -5,3 +5,4 @@ obj-$(CONFIG_ACPI_GTDT) += gtdt.o
> obj-$(CONFIG_ACPI_APMT) += apmt.o
> obj-$(CONFIG_ARM_AMBA) += amba.o
> obj-y += dma.o init.o
> +obj-$(CONFIG_ACPI_AEST) += aest.o
> diff --git a/drivers/acpi/arm64/aest.c b/drivers/acpi/arm64/aest.c
> new file mode 100644
> index 000000000000..ab17aa5f5997
> --- /dev/null
> +++ b/drivers/acpi/arm64/aest.c
> @@ -0,0 +1,834 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ARM Error Source Table Support
> + *
> + * Copyright (c) 2021, Ampere Computing LLC
> + * Copyright (c) 2021-2024, Alibaba Group.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/acpi_aest.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/kernel.h>
> +#include <linux/genalloc.h>
> +#include <linux/llist.h>
> +#include <acpi/actbl.h>
> +#include <asm/ras.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "ACPI AEST: " fmt
> +
> +#define CASE_READ(res, x) \
> + case (x): { \
> + res = read_sysreg_s(SYS_##x##_EL1); \
> + break; \
> + }
> +
> +#define CASE_WRITE(val, x) \
> + case (x): { \
> + write_sysreg_s((val), SYS_##x##_EL1); \
> + break; \
> + }
> +
> +#define for_each_implemented_record(index, node) \
> + for ((index) = node->interface.record_start; \
> + (index) < node->interface.record_end; \
> + (index)++)
> +
> +#define AEST_LOG_PREFIX_BUFFER 64
> +
> +/*
> + * This memory pool is only to be used to save AEST node in AEST irq context.
> + * There can be 500 AEST node at most.
> + */
> +#define AEST_NODE_ALLOCED_MAX 500
> +
> +static struct acpi_table_header *aest_table;
> +
> +static struct aest_node __percpu **aest_ppi_data;
> +
> +static int *ppi_irqs;
> +static u8 num_ppi;
> +static u8 ppi_idx;
> +
> +static struct work_struct aest_work;
> +
> +static struct gen_pool *aest_node_pool;
> +static struct llist_head aest_node_llist;
> +
> +static u64 aest_sysreg_read(u64 __unused, u32 offset)
> +{
> + u64 res;
> +
> + switch (offset) {
> + CASE_READ(res, ERXFR)
> + CASE_READ(res, ERXCTLR)
> + CASE_READ(res, ERXSTATUS)
> + CASE_READ(res, ERXADDR)
> + CASE_READ(res, ERXMISC0)
> + CASE_READ(res, ERXMISC1)
> + CASE_READ(res, ERXMISC2)
> + CASE_READ(res, ERXMISC3)
> + default :
> + res = 0;
> + }
> + return res;
> +}
> +
> +static void aest_sysreg_write(u64 base, u32 offset, u64 val)
> +{
> + switch (offset) {
> + CASE_WRITE(val, ERXFR)
> + CASE_WRITE(val, ERXCTLR)
> + CASE_WRITE(val, ERXSTATUS)
> + CASE_WRITE(val, ERXADDR)
> + CASE_WRITE(val, ERXMISC0)
> + CASE_WRITE(val, ERXMISC1)
> + CASE_WRITE(val, ERXMISC2)
> + CASE_WRITE(val, ERXMISC3)
> + default :
> + return;
> + }
> +}
> +
> +static u64 aest_iomem_read(u64 base, u32 offset)
> +{
> + return readq_relaxed((void *)(base + offset));
> +}
> +
> +static void aest_iomem_write(u64 base, u32 offset, u64 val)
> +{
> + writeq_relaxed(val, (void *)(base + offset));
> +}
> +
> +static void aest_print(struct aest_node_llist *lnode)
> +{
> + static atomic_t seqno = { 0 };
> + unsigned int curr_seqno;
> + char pfx_seq[AEST_LOG_PREFIX_BUFFER];
> + int index;
> + struct ras_ext_regs *regs;
> +
> + curr_seqno = atomic_inc_return(&seqno);
> + snprintf(pfx_seq, sizeof(pfx_seq), "{%u}" HW_ERR, curr_seqno);
> + pr_info("%sHardware error from %s\n", pfx_seq, lnode->node_name);
> +
> + switch (lnode->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + pr_err("%s Error from CPU%d\n", pfx_seq, lnode->id0);
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + pr_err("%s Error from memory at SRAT proximity domain 0x%x\n",
> + pfx_seq, lnode->id0);
> + break;
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + pr_err("%s Error from SMMU IORT node 0x%x subcomponent 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + pr_err("%s Error from vendor hid 0x%x uid 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + case ACPI_AEST_GIC_ERROR_NODE:
> + pr_err("%s Error from GIC type 0x%x instance 0x%x\n",
> + pfx_seq, lnode->id0, lnode->id1);
> + break;
> + default:
> + pr_err("%s Unknown AEST node type\n", pfx_seq);
> + return;
> + }
> +
> + index = lnode->index;
> + regs = lnode->regs;
> +
> + pr_err("%s ERR%uFR: 0x%llx\n", pfx_seq, index, regs->err_fr);
> + pr_err("%s ERR%uCTRL: 0x%llx\n", pfx_seq, index, regs->err_ctlr);
> + pr_err("%s ERR%uSTATUS: 0x%llx\n", pfx_seq, index, regs->err_status);
> + if (regs->err_status & ERR_STATUS_AV)
> + pr_err("%s ERR%uADDR: 0x%llx\n", pfx_seq, index, regs->err_addr);
> +
> + if (regs->err_status & ERR_STATUS_MV) {
> + pr_err("%s ERR%uMISC0: 0x%llx\n", pfx_seq, index, regs->err_misc[0]);
> + pr_err("%s ERR%uMISC1: 0x%llx\n", pfx_seq, index, regs->err_misc[1]);
> + pr_err("%s ERR%uMISC2: 0x%llx\n", pfx_seq, index, regs->err_misc[2]);
> + pr_err("%s ERR%uMISC3: 0x%llx\n", pfx_seq, index, regs->err_misc[3]);
> + }
> +}
> +
> +static void aest_handle_memory_failure(struct aest_node_llist *lnode)
> +{
> + unsigned long pfn;
> + u64 addr;
> +
> + if (test_bit(lnode->index, &lnode->addressing_mode) ||
> + (lnode->regs->err_addr & ERR_ADDR_AI))
> + return;
> +
> + addr = lnode->regs->err_addr & (1UL << CONFIG_ARM64_PA_BITS);
> + pfn = PHYS_PFN(addr);
> +
> + if (!pfn_valid(pfn)) {
> + pr_warn(HW_ERR "Invalid physical address: %#llx\n", addr);
> + return;
> + }
> +
> + memory_failure(pfn, 0);
> +}
> +
> +static void aest_node_pool_process(struct work_struct *__unused)
> +{
> + struct llist_node *head;
> + struct aest_node_llist *lnode, *tmp;
> + u64 status;
> +
> + head = llist_del_all(&aest_node_llist);
> + if (!head)
> + return;
> +
> + head = llist_reverse_order(head);
> + llist_for_each_entry_safe(lnode, tmp, head, llnode) {
> + aest_print(lnode);
> +
> + status = lnode->regs->err_status;
> + if ((status & ERR_STATUS_UE) &&
> + (status & ERR_STATUS_UET) > ERR_STATUS_UET_UEU)
If UET is UC or UEU, wouldn't the kernel already be panicked?
Instead, shouldn't we check if lnode->type is ACPI_AEST_MEMORY_ERROR_NODE?
> + aest_handle_memory_failure(lnode);
> + gen_pool_free(aest_node_pool, (unsigned long)lnode,
> + sizeof(*lnode));
> + }
> +}
> +
> +static int aest_node_gen_pool_add(struct aest_node *node, int index,
> + struct ras_ext_regs *regs)
> +{
> + struct aest_node_llist *list;
> +
> + if (!aest_node_pool)
> + return -EINVAL;
> +
> + list = (void *)gen_pool_alloc(aest_node_pool, sizeof(*list));
> + if (!list)
> + return -ENOMEM;
> +
> + list->type = node->type;
> + list->node_name = node->name;
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + list->id0 = node->spec.processor.processor_id;
> + if (node->spec.processor.flags & (ACPI_AEST_PROC_FLAG_SHARED |
> + ACPI_AEST_PROC_FLAG_GLOBAL))
> + list->id0 = smp_processor_id();
> +
> + list->id1 = node->spec.processor.resource_type;
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + list->id0 = node->spec.memory.srat_proximity_domain;
> + break;
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + list->id0 = node->spec.smmu.iort_node_reference;
> + list->id1 = node->spec.smmu.subcomponent_reference;
> + break;
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + list->id0 = node->spec.vendor.acpi_hid;
> + list->id1 = node->spec.vendor.acpi_uid;
> + break;
> + case ACPI_AEST_GIC_ERROR_NODE:
> + list->id0 = node->spec.gic.interface_type;
> + list->id1 = node->spec.gic.instance_id;
> + break;
> + default:
> + list->id0 = 0;
> + list->id1 = 0;
> + }
> +
> + list->regs = regs;
> + list->index = index;
> + list->addressing_mode = node->interface.addressing_mode;
> + llist_add(&list->llnode, &aest_node_llist);
> +
> + return 0;
> +}
> +
> +static int aest_node_pool_init(void)
> +{
> + unsigned long addr, size;
> + int rc;
> +
> + if (aest_node_pool)
> + return 0;
> +
> + size = ilog2(sizeof(struct aest_node_llist));
> + aest_node_pool = gen_pool_create(size, -1);
> + if (!aest_node_pool)
> + return -ENOMEM;
> +
> + addr = (unsigned long)vmalloc(PAGE_ALIGN(size * AEST_NODE_ALLOCED_MAX));
> + if (!addr)
> + goto err_pool_alloc;
> +
> + rc = gen_pool_add(aest_node_pool, addr, size, -1);
> + if (rc)
> + goto err_pool_add;
> +
> + return 0;
> +
> +err_pool_add:
> + vfree((void *)addr);
> +
> +err_pool_alloc:
> + gen_pool_destroy(aest_node_pool);
> +
> + return -ENOMEM;
> +}
> +
> +static void aest_log(struct aest_node *node, int index, struct ras_ext_regs *regs)
> +{
> + if (!aest_node_gen_pool_add(node, index, regs))
> + schedule_work(&aest_work);
> +}
> +
> +/*
> + * Each PE may has multi error record, you must selects an error record to
> + * be accessed through the Error Record System registers.
> + */
> +static inline void aest_select_record(struct aest_node *node, int i)
> +{
> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER) {
> + write_sysreg_s(i, SYS_ERRSELR_EL1);
> + isb();
> + }
> +}
> +
> +/* Ensure all writes has taken effect. */
> +static inline void aest_sync(struct aest_node *node)
> +{
> + if (node->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER)
> + isb();
> +}
> +
> +static int aest_proc(struct aest_node *node)
> +{
> + struct ras_ext_regs regs = {0};
> + struct aest_access *access;
> + int i, count = 0;
> + u64 regs_p;
> +
> + for_each_implemented_record(i, node) {
> +
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> +
> + access = node->access;
> + regs_p = (u64)&node->interface.regs[i];
> +
> + regs.err_status = access->read(regs_p, ERXSTATUS);
> + if (!(regs.err_status & ERR_STATUS_V))
> + continue;
> +
> + count++;
> +
> + if (regs.err_status & ERR_STATUS_AV)
> + regs.err_addr = access->read(regs_p, ERXADDR);
> +
> + regs.err_fr = access->read(regs_p, ERXFR);
> + regs.err_ctlr = access->read(regs_p, ERXCTLR);
> +
> + if (regs.err_status & ERR_STATUS_MV) {
> + regs.err_misc[0] = access->read(regs_p, ERXMISC0);
> + regs.err_misc[1] = access->read(regs_p, ERXMISC1);
> + regs.err_misc[2] = access->read(regs_p, ERXMISC2);
> + regs.err_misc[3] = access->read(regs_p, ERXMISC3);
> + }
> +
> + if (node->interface.flags & ACPI_AEST_INTERFACE_CLEAR_MISC) {
> + access->write(regs_p, ERXMISC0, 0);
> + access->write(regs_p, ERXMISC1, 0);
> + access->write(regs_p, ERXMISC2, 0);
> + access->write(regs_p, ERXMISC3, 0);
> + } else
> + access->write(regs_p, ERXMISC0,
> + node->interface.ce_threshold[i]);
We don't need this write if the platform does not support CE reporting.
> +
> + aest_log(node, i, ®s);
Logging is asynchronous but should we print something here for UE case
as we are going panic() below?
> +
> + /* panic if unrecoverable and uncontainable error encountered */
> + if ((regs.err_status & ERR_STATUS_UE) &&
> + (regs.err_status & ERR_STATUS_UET) < ERR_STATUS_UET_UER)
Nit: I prefer something like: (status & UET) == UC || (status & UET) == UEU
> + panic("AEST: unrecoverable error encountered");
> +
> + /* Write-one-to-clear the bits we've seen */
> + regs.err_status &= ERR_STATUS_W1TC;
> +
> + /* Multi bit filed need to write all-ones to clear. */
> + if (regs.err_status & ERR_STATUS_CE)
> + regs.err_status |= ERR_STATUS_CE;
> +
> + /* Multi bit filed need to write all-ones to clear. */
> + if (regs.err_status & ERR_STATUS_UET)
> + regs.err_status |= ERR_STATUS_UET;
> +
> + access->write(regs_p, ERXSTATUS, regs.err_status);
> +
> + aest_sync(node);
> + }
> +
> + return count;
> +}
> +
> +static irqreturn_t aest_irq_func(int irq, void *input)
> +{
> + struct aest_node *node = input;
> +
> + if (aest_proc(node))
> + return IRQ_HANDLED;
> +
> + return IRQ_NONE;
> +}
> +
> +static int __init aest_register_gsi(u32 gsi, int trigger, void *data,
> + irq_handler_t aest_irq_func)
> +{
> + int cpu, irq;
> +
> + irq = acpi_register_gsi(NULL, gsi, trigger, ACPI_ACTIVE_HIGH);
> +
> + if (irq == -EINVAL) {
> + pr_err("failed to map AEST GSI %d\n", gsi);
> + return -EINVAL;
> + }
> +
> + if (irq_is_percpu_devid(irq)) {
> + ppi_irqs[ppi_idx] = irq;
> + for_each_possible_cpu(cpu) {
> + memcpy(per_cpu_ptr(aest_ppi_data[ppi_idx], cpu), data,
> + sizeof(struct aest_node));
> + }
> + if (request_percpu_irq(irq, aest_irq_func, "AEST",
> + aest_ppi_data[ppi_idx++])) {
> + pr_err("failed to register AEST IRQ %d\n", irq);
> + return -EINVAL;
> + }
> + } else {
> + if (request_irq(irq, aest_irq_func, IRQF_SHARED, "AEST",
> + data)) {
> + pr_err("failed to register AEST IRQ %d\n", irq);
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init aest_init_interrupts(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + struct acpi_aest_node_interrupt *interrupt;
> + int i, trigger, ret = 0, err_ctlr, regs_p;
> +
> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, hdr,
> + hdr->node_interrupt_offset);
> +
> + for (i = 0; i < hdr->node_interrupt_count; i++, interrupt++) {
> + trigger = (interrupt->flags & AEST_INTERRUPT_MODE) ?
> + ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
> + if (aest_register_gsi(interrupt->gsiv, trigger, node,
> + aest_irq_func))
> + ret = -EINVAL;
> + }
> +
> + /* Ensure RAS interrupt is enabled */
> + for_each_implemented_record(i, node) {
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> +
> + regs_p = (u64)&node->interface.regs[i];
> +
> + err_ctlr = node->access->read(regs_p, ERXCTLR);
> +
> + if (interrupt->type == ACPI_AEST_NODE_FAULT_HANDLING)
> + err_ctlr |= ERR_CTLR_FI;
> + if (interrupt->type == ACPI_AEST_NODE_ERROR_RECOVERY)
> + err_ctlr |= ERR_CTLR_UI;
> +
> + node->access->write(regs_p, ERXCTLR, err_ctlr);
> +
> + aest_sync(node);
> + }
> +
> + return ret;
> +}
> +
> +static void __init set_aest_node_name(struct aest_node *node)
> +{
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + node->name = kasprintf(GFP_KERNEL, "AEST-CPU%d",
> + node->spec.processor.processor_id);
> + break;
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + case ACPI_AEST_GIC_ERROR_NODE:
> + node->name = kasprintf(GFP_KERNEL, "AEST-%llx",
> + node->interface.phy_addr);
> + break;
> + default:
> + node->name = kasprintf(GFP_KERNEL, "AEST-Unkown-Node");
> + }
> +}
> +
> +/* access type is decided by AEST interface type. */
> +static struct aest_access aest_access[] = {
> + [ACPI_AEST_NODE_SYSTEM_REGISTER] = {
> + .read = aest_sysreg_read,
> + .write = aest_sysreg_write,
> + },
> +
> + [ACPI_AEST_NODE_MEMORY_MAPPED] = {
> + .read = aest_iomem_read,
> + .write = aest_iomem_write,
> + },
> + { }
> +};
> +
> +/* In kernel-first mode, kernel will report every CE by default. */
> +static void __init aest_set_ce_threshold(struct aest_node *node)
> +{
> + u64 regs_p, err_fr, err_fr_cec, err_fr_rp, err_misc0, ce_threshold;
> + int i;
> +
> + for_each_implemented_record(i, node) {
> + /* 1b: Error record at i index is not implemented */
> + if (test_bit(i, &node->interface.record_implemented))
> + continue;
> +
> + aest_select_record(node, i);
> + regs_p = (u64)&node->interface.regs[i];
> +
> + err_fr = node->access->read(regs_p, ERXFR);
> + err_fr_cec = FIELD_GET(ERR_FR_CEC, err_fr);
> + err_fr_rp = FIELD_GET(ERR_FR_RP, err_fr);
> + err_misc0 = node->access->read(regs_p, ERXMISC0);
> +
> + if (err_fr_cec == ERR_FR_CEC_0B_COUNTER)
> + pr_debug("%s-%d do not support CE threshold!\n",
> + node->name, i);
> + else if (err_fr_cec == ERR_FR_CEC_8B_COUNTER &&
> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
> + pr_debug("%s-%d support 8 bit CE threshold!\n",
> + node->name, i);
> + ce_threshold = err_misc0 | ERR_MISC0_8B_CEC;
> + } else if (err_fr_cec == ERR_FR_CEC_16B_COUNTER &&
> + err_fr_rp == ERR_FR_RP_SINGLE_COUNTER) {
> + pr_debug("%s-%d support 16 bit CE threshold!\n",
> + node->name, i);
> + ce_threshold = err_misc0 | ERR_MISC0_16B_CEC;
> + } else
> + pr_debug("%s-%d do not support double counter yet!\n",
> + node->name, i);
> +
> + node->access->write(regs_p, ERXMISC0, ce_threshold);
> + node->interface.ce_threshold[i] = ce_threshold;
> +
> + aest_sync(node);
> + }
> +}
> +
> +static int __init aest_init_interface(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + struct acpi_aest_node_interface *interface;
> + struct resource *res;
> + int size;
> +
> + interface = ACPI_ADD_PTR(struct acpi_aest_node_interface, hdr,
> + hdr->node_interface_offset);
> +
> + if (interface->type >= ACPI_AEST_XFACE_RESERVED) {
> + pr_err("invalid interface type: %d\n", interface->type);
> + return -EINVAL;
> + }
> +
> + node->interface.type = interface->type;
> + node->interface.phy_addr = interface->address;
> + node->interface.record_start = interface->error_record_index;
> + node->interface.record_end = interface->error_record_index +
> + interface->error_record_count;
> + node->interface.flags = interface->flags;
> + node->interface.record_implemented = interface->error_record_implemented;
> + node->interface.status_reporting = interface->error_status_reporting;
> + node->interface.addressing_mode = interface->addressing_mode;
> + node->access = &aest_access[interface->type];
> +
> + /*
> + * Currently SR based handling is done through the architected
> + * discovery exposed through SRs. That may change in the future
> + * if there is supplemental information in the AEST that is
> + * needed.
> + */
> + if (interface->type == ACPI_AEST_NODE_SYSTEM_REGISTER)
> + return 0;
> +
> + res = kzalloc(sizeof(struct resource), GFP_KERNEL);
> + if (!res)
> + return -ENOMEM;
> +
> + size = interface->error_record_count * sizeof(struct ras_ext_regs);
> + res->name = "AEST";
> + res->start = interface->address;
> + res->end = res->start + size;
> + res->flags = IORESOURCE_MEM;
> +
> + if (insert_resource(&iomem_resource, res)) {
> + pr_notice("request region conflict with %s\n",
> + res->name);
> + }
> +
> + node->interface.regs = ioremap(res->start, size);
> + if (!node->interface.regs) {
> + pr_err("Ioremap for %s failed!\n", node->name);
> + kfree(res);
> + return -EINVAL;
> + }
> +
> + node->interface.ce_threshold = kzalloc(sizeof(u64) *
> + interface->error_record_count, GFP_KERNEL);
> + if (!node->interface.ce_threshold)
> + return -ENOMEM;
As mentioned above, CE reporting is indicated in MISC<n>FR.CE.
All CE related handlings should be put under if statement.
> +
> + aest_set_ce_threshold(node);
> +
> + return 0;
> +}
> +
> +static int __init aest_init_common(struct acpi_aest_hdr *hdr,
> + struct aest_node *node)
> +{
> + int ret;
> +
> + set_aest_node_name(node);
> +
> + ret = aest_init_interface(hdr, node);
> + if (ret) {
> + pr_err("failed to init interface\n");
> + return ret;
> + }
> +
> + return aest_init_interrupts(hdr, node);
> +}
> +
> +static int __init aest_init_node_default(struct acpi_aest_hdr *hdr)
> +{
> + struct aest_node *node;
> + union aest_node_spec *node_spec;
> + int ret;
> +
> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = hdr->type;
> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
> + hdr->node_specific_offset);
> +
> + memcpy(&node->spec, node_spec,
> + hdr->node_interface_offset - hdr->node_specific_offset);
> +
> + ret = aest_init_common(hdr, node);
> + if (ret)
> + kfree(node);
> +
> + return ret;
> +}
> +
> +static int __init aest_init_processor_node(struct acpi_aest_hdr *hdr)
> +{
> + struct aest_node *node;
> + union aest_node_spec *node_spec;
> + union aest_node_processor *proc;
> + int ret;
> +
> + node = kzalloc(sizeof(struct aest_node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = hdr->type;
> + node_spec = ACPI_ADD_PTR(union aest_node_spec, hdr,
> + hdr->node_specific_offset);
> +
> + memcpy(&node->spec, node_spec,
> + hdr->node_interface_offset - hdr->node_specific_offset);
> +
> + proc = ACPI_ADD_PTR(union aest_node_processor, node_spec,
> + sizeof(acpi_aest_processor));
> +
> + switch (node->spec.processor.resource_type) {
> + case ACPI_AEST_CACHE_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_cache));
> + break;
> + case ACPI_AEST_TLB_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_tlb));
> + break;
> + case ACPI_AEST_GENERIC_RESOURCE:
> + memcpy(&node->proc, proc,
> + sizeof(struct acpi_aest_processor_generic));
> + break;
> + }
> +
> + ret = aest_init_common(hdr, node);
> + if (ret)
> + kfree(node);
> +
> + return ret;
> +}
> +
> +static int __init aest_init_node(struct acpi_aest_hdr *node)
> +{
> + switch (node->type) {
> + case ACPI_AEST_PROCESSOR_ERROR_NODE:
> + return aest_init_processor_node(node);
> + case ACPI_AEST_MEMORY_ERROR_NODE:
> + case ACPI_AEST_VENDOR_ERROR_NODE:
> + case ACPI_AEST_SMMU_ERROR_NODE:
> + case ACPI_AEST_GIC_ERROR_NODE:
> + return aest_init_node_default(node);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static void __init aest_count_ppi(struct acpi_aest_hdr *header)
> +{
> + struct acpi_aest_node_interrupt *interrupt;
> + int i;
> +
> + interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, header,
> + header->node_interrupt_offset);
> +
> + for (i = 0; i < header->node_interrupt_count; i++, interrupt++) {
> + if (interrupt->gsiv >= 16 && interrupt->gsiv < 32)
> + num_ppi++;
> + }
> +}
> +
> +static int aest_starting_cpu(unsigned int cpu)
> +{
> + int i;
> +
> + for (i = 0; i < num_ppi; i++)
> + enable_percpu_irq(ppi_irqs[i], IRQ_TYPE_NONE);
> +
> + return 0;
> +}
> +
> +static int aest_dying_cpu(unsigned int cpu)
> +{
> + int i;
> +
> + for (i = 0; i < num_ppi; i++)
> + disable_percpu_irq(ppi_irqs[i]);
> +
> + return 0;
> +}
> +
> +int __init acpi_aest_init(void)
> +{
> + struct acpi_aest_hdr *aest_node, *aest_end;
> + struct acpi_table_aest *aest;
> + int i, ret = 0;
> +
> + if (acpi_disabled)
> + return 0;
> +
> + if (!IS_ENABLED(CONFIG_ARM64_RAS_EXTN))
> + return 0;
> +
> + if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_AEST, 0, &aest_table)))
> + return -EINVAL;
> +
> + ret = aest_node_pool_init();
> + if (ret) {
> + pr_err("Failed init aest node pool.\n");
> + goto fail;
> + }
> +
> + INIT_WORK(&aest_work, aest_node_pool_process);
> +
> + aest = (struct acpi_table_aest *)aest_table;
> +
> + /* Get the first AEST node */
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + sizeof(struct acpi_table_header));
> + /* Pointer to the end of the AEST table */
> + aest_end = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + aest_table->length);
> +
> + while (aest_node < aest_end) {
> + if (((u64)aest_node + aest_node->length) > (u64)aest_end) {
> + pr_err("AEST node pointer overflow, bad table.\n");
> + return -EINVAL;
> + }
> +
> + aest_count_ppi(aest_node);
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
> + aest_node->length);
> + }
> +
> + aest_ppi_data = kcalloc(num_ppi, sizeof(struct aest_node_data *),
> + GFP_KERNEL);
> + if (!aest_ppi_data) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + ppi_irqs = kcalloc(num_ppi, sizeof(int), GFP_KERNEL);
> + if (!ppi_irqs) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + for (i = 0; i < num_ppi; i++) {
> + aest_ppi_data[i] = alloc_percpu(struct aest_node);
> + if (!aest_ppi_data[i]) {
> + pr_err("Failed percpu allocation.\n");
> + ret = -ENOMEM;
> + goto fail;
> + }
> + }
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest,
> + sizeof(struct acpi_table_header));
> +
> + while (aest_node < aest_end) {
> + ret = aest_init_node(aest_node);
> + if (ret) {
> + pr_err("failed to init node: %d", ret);
> + goto fail;
> + }
> +
> + aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
> + aest_node->length);
> + }
> +
> +
> +
> + return cpuhp_setup_state(CPUHP_AP_ARM_AEST_STARTING,
> + "drivers/acpi/arm64/aest:starting",
> + aest_starting_cpu, aest_dying_cpu);
> +
> +fail:
> + for (i = 0; i < num_ppi; i++)
> + free_percpu(aest_ppi_data[i]);
> + kfree(aest_ppi_data);
> + return ret;
> +}
> +subsys_initcall(acpi_aest_init);
> diff --git a/include/linux/acpi_aest.h b/include/linux/acpi_aest.h
> new file mode 100644
> index 000000000000..679187505dc6
> --- /dev/null
> +++ b/include/linux/acpi_aest.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef AEST_H
> +#define AEST_H
> +
> +#include <acpi/actbl.h>
> +#include <asm/ras.h>
> +
> +#define AEST_INTERRUPT_MODE BIT(0)
> +
> +#define ACPI_AEST_PROC_FLAG_GLOBAL (1<<0)
> +#define ACPI_AEST_PROC_FLAG_SHARED (1<<1)
> +
> +#define ACPI_AEST_INTERFACE_CLEAR_MISC (1<<0)
> +
> +#define ERXFR 0x0
> +#define ERXCTLR 0x8
> +#define ERXSTATUS 0x10
> +#define ERXADDR 0x18
> +#define ERXMISC0 0x20
> +#define ERXMISC1 0x28
> +#define ERXMISC2 0x30
> +#define ERXMISC3 0x38
> +
> +struct aest_node_interface {
> + u8 type;
> + u64 phy_addr;
> + u16 record_start;
> + u16 record_end;
According to the spec, these two should be u32?
> + u32 flags;
> + unsigned long record_implemented;
> + unsigned long status_reporting;
> + unsigned long addressing_mode;
... and these three should be u64?
> + struct ras_ext_regs *regs;
> + u64 *ce_threshold;
> +};
> +
> +union aest_node_processor {
> + struct acpi_aest_processor_cache cache_data;
> + struct acpi_aest_processor_tlb tlb_data;
> + struct acpi_aest_processor_generic generic_data;
> +};
> +
> +union aest_node_spec {
> + struct acpi_aest_processor processor;
> + struct acpi_aest_memory memory;
> + struct acpi_aest_smmu smmu;
> + struct acpi_aest_vendor vendor;
> + struct acpi_aest_gic gic;
> +};
> +
> +struct aest_access {
> + u64 (*read)(u64 base, u32 offset);
> + void (*write)(u64 base, u32 offset, u64 val);
> +};
> +
> +struct aest_node {
> + char *name;
> + u8 type;
> + struct aest_node_interface interface;
> + union aest_node_spec spec;
> + union aest_node_processor proc;
> + struct aest_access *access;
> +};
> +
> +struct aest_node_llist {
> + struct llist_node llnode;
> + char *node_name;
> + int type;
I think 'type' should be u32
> + /*
> + * Different nodes have different meanings:
> + * - Processor node : processor number.
> + * - Memory node : SRAT proximity domain.
> + * - SMMU node : IORT proximity domain.
> + * - Vendor node : hardware ID.
> + * - GIC node : interface type.
> + */
> + u32 id0;
> + /*
> + * Different nodes have different meanings:
> + * - Processor node : processor resource type.
> + * - Memory node : Non.
> + * - SMMU node : subcomponent reference.
> + * - Vendor node : Unique ID.
> + * - GIC node : instance identifier.
> + */
> + u32 id1;
> + int index;
This too should be u32.
Best Regards,
Tomohiro Misono
> + unsigned long addressing_mode;
> + struct ras_ext_regs *regs;
> +};
> +
> +#endif /* AEST_H */
> diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> index 624d4a38c358..f0dda08dbad2 100644
> --- a/include/linux/cpuhotplug.h
> +++ b/include/linux/cpuhotplug.h
> @@ -186,6 +186,7 @@ enum cpuhp_state {
> CPUHP_AP_CSKY_TIMER_STARTING,
> CPUHP_AP_TI_GP_TIMER_STARTING,
> CPUHP_AP_HYPERV_TIMER_STARTING,
> + CPUHP_AP_ARM_AEST_STARTING,
> /* Must be the last timer callback */
> CPUHP_AP_DUMMY_TIMER_STARTING,
> CPUHP_AP_ARM_XEN_STARTING,
> --
> 2.33.1
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply [flat|nested] 7+ messages in thread