diff mbox series

[v2,1/2] ACPI/AEST: Initial AEST driver

Message ID 20240321025317.114621-2-tianruidong@linux.alibaba.com (mailing list archive)
State Handled Elsewhere, archived
Headers show
Series ARM Error Source Table V1 Support | expand

Commit Message

Ruidong Tian March 21, 2024, 2:53 a.m. UTC
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

Comments

Baolin Wang March 21, 2024, 3:52 a.m. UTC | #1
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, &regs);
> +
> +		/* 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,
Shuai Xue March 27, 2024, 5:46 a.m. UTC | #2
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, &regs);

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,
Ruidong Tian April 9, 2024, 7:37 a.m. UTC | #3
在 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, &regs);
>> +
>> +        /* 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.
Tomohiro Misono (Fujitsu) May 10, 2024, 5:25 a.m. UTC | #4
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, &regs);

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
diff mbox series

Patch

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, &regs);
+
+		/* 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;
+	}
+
+	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;
+
+	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;
+	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,