diff --git a/MAINTAINERS b/MAINTAINERS
index 5250298d2817..aa0483726606 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -382,6 +382,7 @@ ACPI FOR ARM64 (ACPI/arm64)
 M:	Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
 M:	Hanjun Guo <guohanjun@huawei.com>
 M:	Sudeep Holla <sudeep.holla@arm.com>
+R:	Tyler Baicar <baicar@os.amperecomputing.com>
 L:	linux-acpi@vger.kernel.org
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
diff --git a/arch/arm64/include/asm/ras.h b/arch/arm64/include/asm/ras.h
new file mode 100644
index 000000000000..e88fa93e5f1c
--- /dev/null
+++ b/arch/arm64/include/asm/ras.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_RAS_H
+#define __ASM_RAS_H
+
+#include <linux/types.h>
+#include <linux/bits.h>
+
+#define ERR_STATUS_AV		BIT(31)
+#define ERR_STATUS_V		BIT(30)
+#define ERR_STATUS_UE		BIT(29)
+#define ERR_STATUS_MV		BIT(26)
+#define ERR_STATUS_CE_MASK	(BIT(25) | BIT(24))
+#define ERR_STATUS_DE		BIT(23)
+#define ERR_STATUS_UET_MASK	(BIT(21) | BIT(20))
+#define ERR_STATUS_IERR_SHIFT	8
+#define ERR_STATUS_IERR_MASK	0xff
+#define ERR_STATUS_SERR_SHIFT	0
+#define ERR_STATUS_SERR_MASK	0xff
+#define ERR_STATUS_W1TC_MASK	0xfff80000
+
+#define ERRIDR_NUM_MASK		0xffff
+
+#define ERRGSR_OFFSET		0xe00
+#define ERRDEVARCH_OFFSET	0xfbc
+
+#define ERRDEVARCH_REV_SHIFT	0x16
+#define ERRDEVARCH_REV_MASK	0xf
+
+#define RAS_REV_v1_1		0x1
+
+struct ras_ext_regs {
+	u64 err_fr;
+	u64 err_ctlr;
+	u64 err_status;
+	u64 err_addr;
+	u64 err_misc0;
+	u64 err_misc1;
+	u64 err_misc2;
+	u64 err_misc3;
+};
+
+#ifdef CONFIG_ARM64_RAS_EXTN
+void arch_arm_ras_print_error(struct ras_ext_regs *regs, unsigned int i, bool misc23_present);
+u64 arch_arm_ras_get_status_clear_value(u64 err_status);
+void arch_arm_ras_report_error(u64 implemented, bool clear_misc);
+#else
+void arch_arm_ras_print_error(struct ras_ext_regs *regs, unsigned int i, bool misc23_present) { }
+u64 arch_arm_ras_get_status_clear_value(u64 err_status) { return 0; }
+void arch_arm_ras_report_error(u64 implemented, bool clear_misc) { }
+#endif
+
+#endif	/* __ASM_RAS_H */
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 16b3f1a1d468..6bbed061d835 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -230,6 +230,8 @@
 #define SYS_ERXADDR_EL1			sys_reg(3, 0, 5, 4, 3)
 #define SYS_ERXMISC0_EL1		sys_reg(3, 0, 5, 5, 0)
 #define SYS_ERXMISC1_EL1		sys_reg(3, 0, 5, 5, 1)
+#define SYS_ERXMISC2_EL1		sys_reg(3, 0, 5, 5, 2)
+#define SYS_ERXMISC3_EL1		sys_reg(3, 0, 5, 5, 3)
 #define SYS_TFSR_EL1			sys_reg(3, 0, 5, 6, 0)
 #define SYS_TFSRE0_EL1			sys_reg(3, 0, 5, 6, 1)
 
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 88b3e2a21408..fe73844494ba 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -73,6 +73,7 @@ obj-$(CONFIG_ARM64_PTR_AUTH)		+= pointer_auth.o
 obj-$(CONFIG_ARM64_MTE)			+= mte.o
 obj-y					+= vdso-wrap.o
 obj-$(CONFIG_COMPAT_VDSO)		+= vdso32-wrap.o
+obj-$(CONFIG_ARM64_RAS_EXTN)		+= ras.o
 
 obj-y					+= probes/
 head-y					:= head.o
diff --git a/arch/arm64/kernel/ras.c b/arch/arm64/kernel/ras.c
new file mode 100644
index 000000000000..31e2036a4c70
--- /dev/null
+++ b/arch/arm64/kernel/ras.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/kernel.h>
+#include <linux/cpu.h>
+
+#include <asm/cpufeature.h>
+#include <asm/ras.h>
+
+static bool ras_extn_v1p1(void)
+{
+	unsigned long fld, reg = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
+
+	fld = cpuid_feature_extract_unsigned_field(reg, ID_AA64PFR0_RAS_SHIFT);
+
+	return fld >= ID_AA64PFR0_RAS_V1P1;
+}
+
+u64 arch_arm_ras_get_status_clear_value(u64 err_status)
+{
+	/* Write-one-to-clear the bits we've seen */
+	err_status &= ERR_STATUS_W1TC_MASK;
+
+	/* If CE field is non-zero, all bits must be written to properly clear */
+	if (err_status & ERR_STATUS_CE_MASK)
+		err_status |= ERR_STATUS_CE_MASK;
+
+	/* If UET field is non-zero, all bits must be written to properly clear */
+	if (err_status & ERR_STATUS_UET_MASK)
+		err_status |= ERR_STATUS_UET_MASK;
+
+	return err_status;
+}
+
+void arch_arm_ras_print_error(struct ras_ext_regs *regs, unsigned int i, bool misc23_present)
+{
+	pr_err(" ERR%uSTATUS: 0x%llx\n", i, regs->err_status);
+	if (regs->err_status & ERR_STATUS_AV)
+		pr_err(" ERR%uADDR: 0x%llx\n", i, regs->err_addr);
+
+	if (regs->err_status & ERR_STATUS_MV) {
+		pr_err(" ERR%uMISC0: 0x%llx\n", i, regs->err_misc0);
+		pr_err(" ERR%uMISC1: 0x%llx\n", i, regs->err_misc1);
+
+		if (misc23_present) {
+			pr_err(" ERR%uMISC2: 0x%llx\n", i, regs->err_misc2);
+			pr_err(" ERR%uMISC3: 0x%llx\n", i, regs->err_misc3);
+		}
+	}
+}
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ARM RAS: " fmt
+
+void arch_arm_ras_report_error(u64 implemented, bool clear_misc)
+{
+	struct ras_ext_regs regs = {0};
+	unsigned int i, cpu_num;
+	bool misc23_present;
+	bool fatal = false;
+	u64 num_records;
+
+	if (!this_cpu_has_cap(ARM64_HAS_RAS_EXTN))
+		return;
+
+	cpu_num = get_cpu();
+	num_records = read_sysreg_s(SYS_ERRIDR_EL1) & ERRIDR_NUM_MASK;
+
+	for (i = 0; i < num_records; i++) {
+		if (!(implemented & BIT(i)))
+			continue;
+
+		write_sysreg_s(i, SYS_ERRSELR_EL1);
+		isb();
+		regs.err_status = read_sysreg_s(SYS_ERXSTATUS_EL1);
+
+		if (!(regs.err_status & ERR_STATUS_V))
+			continue;
+
+		pr_err("error from processor 0x%x\n", cpu_num);
+
+		if (regs.err_status & ERR_STATUS_AV)
+			regs.err_addr = read_sysreg_s(SYS_ERXADDR_EL1);
+
+		misc23_present = ras_extn_v1p1();
+
+		if (regs.err_status & ERR_STATUS_MV) {
+			regs.err_misc0 = read_sysreg_s(SYS_ERXMISC0_EL1);
+			regs.err_misc1 = read_sysreg_s(SYS_ERXMISC1_EL1);
+
+			if (misc23_present) {
+				regs.err_misc2 = read_sysreg_s(SYS_ERXMISC2_EL1);
+				regs.err_misc3 = read_sysreg_s(SYS_ERXMISC3_EL1);
+			}
+		}
+
+		arch_arm_ras_print_error(&regs, i, misc23_present);
+
+		/*
+		 * In the future, we will treat UER conditions as potentially
+		 * recoverable.
+		 */
+		if (regs.err_status & ERR_STATUS_UE)
+			fatal = true;
+
+		regs.err_status = arch_arm_ras_get_status_clear_value(regs.err_status);
+		write_sysreg_s(regs.err_status, SYS_ERXSTATUS_EL1);
+
+		if (clear_misc) {
+			write_sysreg_s(0x0, SYS_ERXMISC0_EL1);
+			write_sysreg_s(0x0, SYS_ERXMISC1_EL1);
+
+			if (misc23_present) {
+				write_sysreg_s(0x0, SYS_ERXMISC2_EL1);
+				write_sysreg_s(0x0, SYS_ERXMISC3_EL1);
+			}
+		}
+
+		isb();
+	}
+
+	if (fatal)
+		panic("ARM RAS: uncorrectable error encountered");
+
+	put_cpu();
+}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index e3ec1a44f94d..dc15e9896db4 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1573,6 +1573,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ERXADDR_EL1), trap_raz_wi },
 	{ SYS_DESC(SYS_ERXMISC0_EL1), trap_raz_wi },
 	{ SYS_DESC(SYS_ERXMISC1_EL1), trap_raz_wi },
+	{ SYS_DESC(SYS_ERXMISC2_EL1), trap_raz_wi },
+	{ SYS_DESC(SYS_ERXMISC3_EL1), trap_raz_wi },
 
 	MTE_REG(TFSR_EL1),
 	MTE_REG(TFSRE0_EL1),
diff --git a/drivers/acpi/arm64/Kconfig b/drivers/acpi/arm64/Kconfig
index 6dba187f4f2e..8d5cf99976c8 100644
--- a/drivers/acpi/arm64/Kconfig
+++ b/drivers/acpi/arm64/Kconfig
@@ -8,3 +8,6 @@ config ACPI_IORT
 
 config ACPI_GTDT
 	bool
+
+config ACPI_AEST
+	bool "ARM Error Source Table Support"
diff --git a/drivers/acpi/arm64/Makefile b/drivers/acpi/arm64/Makefile
index 66acbe77f46e..8f60a9fb6ab1 100644
--- a/drivers/acpi/arm64/Makefile
+++ b/drivers/acpi/arm64/Makefile
@@ -2,3 +2,4 @@
 obj-$(CONFIG_ACPI_IORT) 	+= iort.o
 obj-$(CONFIG_ACPI_GTDT) 	+= gtdt.o
 obj-y				+= dma.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..2df4f2377e51
--- /dev/null
+++ b/drivers/acpi/arm64/aest.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM Error Source Table Support
+ *
+ * Copyright (c) 2021, Ampere Computing LLC
+ */
+
+#include <linux/acpi.h>
+#include <linux/acpi_aest.h>
+#include <linux/cpuhotplug.h>
+#include <linux/kernel.h>
+
+#include <acpi/actbl.h>
+
+#include <asm/ras.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ACPI AEST: " fmt
+
+static struct acpi_table_header *aest_table;
+
+static struct aest_node_data __percpu **ppi_data;
+static int ppi_irqs[AEST_MAX_PPI];
+static u8 num_ppi;
+static u8 ppi_idx;
+
+static bool aest_mmio_ras_misc23_present(u64 base_addr)
+{
+	u32 val;
+
+	val = readl((void *) (base_addr + ERRDEVARCH_OFFSET));
+	val <<= ERRDEVARCH_REV_SHIFT;
+	val &= ERRDEVARCH_REV_MASK;
+
+	return val >= RAS_REV_v1_1;
+}
+
+static void aest_print(struct aest_node_data *data, struct ras_ext_regs regs,
+		       int index, bool misc23_present)
+{
+	/* No more than 2 corrected messages every 5 seconds */
+	static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2);
+
+	if (regs.err_status & ERR_STATUS_UE ||
+	    regs.err_status & ERR_STATUS_DE ||
+	    __ratelimit(&ratelimit_corrected)) {
+		switch (data->node_type) {
+		case ACPI_AEST_PROCESSOR_ERROR_NODE:
+			if (!(data->data.processor.flags & AEST_PROC_GLOBAL) &&
+			    !(data->data.processor.flags & AEST_PROC_SHARED))
+				pr_err("error from processor 0x%x\n",
+				       data->data.processor.processor_id);
+			break;
+		case ACPI_AEST_MEMORY_ERROR_NODE:
+			pr_err("error from memory at SRAT proximity domain 0x%x\n",
+			       data->data.memory.srat_proximity_domain);
+			break;
+		case ACPI_AEST_SMMU_ERROR_NODE:
+			pr_err("error from SMMU IORT node 0x%x subcomponent 0x%x\n",
+			       data->data.smmu.iort_node_reference,
+			       data->data.smmu.subcomponent_reference);
+			break;
+		case ACPI_AEST_VENDOR_ERROR_NODE:
+			pr_err("error from vendor hid 0x%x uid 0x%x\n",
+			       data->data.vendor.acpi_hid, data->data.vendor.acpi_uid);
+			break;
+		case ACPI_AEST_GIC_ERROR_NODE:
+			pr_err("error from GIC type 0x%x instance 0x%x\n",
+			       data->data.gic.interface_type, data->data.gic.instance_id);
+		}
+
+		arch_arm_ras_print_error(&regs, index, misc23_present);
+	}
+}
+
+static void aest_proc(struct aest_node_data *data)
+{
+	struct ras_ext_regs *regs_p, regs = {0};
+	bool misc23_present;
+	bool fatal = false;
+	u64 errgsr = 0;
+	int i;
+
+	/*
+	 * 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 (data->interface.type == ACPI_AEST_NODE_SYSTEM_REGISTER) {
+		arch_arm_ras_report_error(data->interface.implemented,
+					  data->interface.flags & AEST_INTERFACE_CLEAR_MISC);
+		return;
+	}
+
+	regs_p = data->interface.regs;
+	errgsr = readq((void *) (((u64) regs_p) + ERRGSR_OFFSET));
+
+	for (i = data->interface.start; i < data->interface.end; i++) {
+		if (!(data->interface.implemented & BIT(i)))
+			continue;
+
+		if (!(data->interface.status_reporting & BIT(i)) && !(errgsr & BIT(i)))
+			continue;
+
+		regs.err_status = readq(&regs_p[i].err_status);
+		if (!(regs.err_status & ERR_STATUS_V))
+			continue;
+
+		if (regs.err_status & ERR_STATUS_AV)
+			regs.err_addr = readq(&regs_p[i].err_addr);
+
+		regs.err_fr = readq(&regs_p[i].err_fr);
+		regs.err_ctlr = readq(&regs_p[i].err_ctlr);
+
+		if (regs.err_status & ERR_STATUS_MV) {
+			misc23_present = aest_mmio_ras_misc23_present((u64) regs_p);
+			regs.err_misc0 = readq(&regs_p[i].err_misc0);
+			regs.err_misc1 = readq(&regs_p[i].err_misc1);
+
+			if (misc23_present) {
+				regs.err_misc2 = readq(&regs_p[i].err_misc2);
+				regs.err_misc3 = readq(&regs_p[i].err_misc3);
+			}
+		}
+
+		aest_print(data, regs, i, misc23_present);
+
+		if (regs.err_status & ERR_STATUS_UE)
+			fatal = true;
+
+		regs.err_status = arch_arm_ras_get_status_clear_value(regs.err_status);
+		writeq(regs.err_status, &regs_p[i].err_status);
+
+		if (data->interface.flags & AEST_INTERFACE_CLEAR_MISC) {
+			writeq(0x0, &regs_p[i].err_misc0);
+			writeq(0x0, &regs_p[i].err_misc1);
+
+			if (misc23_present) {
+				writeq(0x0, &regs_p[i].err_misc2);
+				writeq(0x0, &regs_p[i].err_misc3);
+			}
+		}
+	}
+
+	if (fatal)
+		panic("AEST: uncorrectable error encountered");
+}
+
+static irqreturn_t aest_irq_func(int irq, void *input)
+{
+	struct aest_node_data *data = input;
+
+	aest_proc(data);
+
+	return IRQ_HANDLED;
+}
+
+static int __init aest_register_gsi(u32 gsi, int trigger, void *data)
+{
+	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 (gsi < 16) {
+		pr_err("invalid GSI %d\n", gsi);
+		return -EINVAL;
+	} else if (gsi < 32) {
+		if (ppi_idx >= AEST_MAX_PPI) {
+			pr_err("Unable to register PPI %d\n", gsi);
+			return -EINVAL;
+		}
+		ppi_irqs[ppi_idx] = irq;
+		enable_percpu_irq(irq, IRQ_TYPE_NONE);
+		for_each_possible_cpu(cpu) {
+			memcpy(per_cpu_ptr(ppi_data[ppi_idx], cpu), data,
+			       sizeof(struct aest_node_data));
+		}
+		if (request_percpu_irq(irq, aest_irq_func, "AEST",
+				       ppi_data[ppi_idx++])) {
+			pr_err("failed to register AEST IRQ %d\n", irq);
+			return -EINVAL;
+		}
+	} else if (gsi < 1020) {
+		if (request_irq(irq, aest_irq_func, IRQF_SHARED, "AEST",
+				data)) {
+			pr_err("failed to register AEST IRQ %d\n", irq);
+			return -EINVAL;
+		}
+	} else {
+		pr_err("invalid GSI %d\n", gsi);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int __init aest_init_interrupts(struct acpi_aest_hdr *node,
+				       struct aest_node_data *data)
+{
+	struct acpi_aest_node_interrupt *interrupt;
+	int i, trigger, ret = 0;
+
+	interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, node,
+				 node->node_interrupt_offset);
+
+	for (i = 0; i < node->node_interrupt_count; i++, interrupt++) {
+		trigger = (interrupt->flags & AEST_INTERRUPT_MODE) ?
+			  ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
+		if (aest_register_gsi(interrupt->gsiv, trigger, data))
+			ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int __init aest_init_interface(struct acpi_aest_hdr *node,
+				       struct aest_node_data *data)
+{
+	struct acpi_aest_node_interface *interface;
+	struct resource *res;
+	int size;
+
+	interface = ACPI_ADD_PTR(struct acpi_aest_node_interface, node,
+				 node->node_interface_offset);
+
+	if (interface->type >= ACPI_AEST_XFACE_RESERVED) {
+		pr_err("invalid interface type: %d\n", interface->type);
+		return -EINVAL;
+	}
+
+	data->interface.type = interface->type;
+	data->interface.start = interface->error_record_index;
+	data->interface.end = interface->error_record_index + interface->error_record_count;
+	data->interface.flags = interface->flags;
+	data->interface.implemented = interface->error_record_implemented;
+	data->interface.status_reporting = interface->error_status_reporting;
+
+	/*
+	 * 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 (request_resource_conflict(&iomem_resource, res)) {
+		pr_err("unable to request region starting at 0x%llx\n",
+			res->start);
+		kfree(res);
+		return -EEXIST;
+	}
+
+	data->interface.regs = ioremap(interface->address, size);
+	if (data->interface.regs == NULL) {
+		kfree(res);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int __init aest_init_node(struct acpi_aest_hdr *node)
+{
+	union acpi_aest_processor_data *proc_data;
+	union aest_node_spec *node_spec;
+	struct aest_node_data *data;
+	int ret;
+
+	data = kzalloc(sizeof(struct aest_node_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->node_type = node->type;
+
+	node_spec = ACPI_ADD_PTR(union aest_node_spec, node, node->node_specific_offset);
+
+	switch (node->type) {
+	case ACPI_AEST_PROCESSOR_ERROR_NODE:
+		memcpy(&data->data, node_spec, sizeof(struct acpi_aest_processor));
+		break;
+	case ACPI_AEST_MEMORY_ERROR_NODE:
+		memcpy(&data->data, node_spec, sizeof(struct acpi_aest_memory));
+		break;
+	case ACPI_AEST_SMMU_ERROR_NODE:
+		memcpy(&data->data, node_spec, sizeof(struct acpi_aest_smmu));
+		break;
+	case ACPI_AEST_VENDOR_ERROR_NODE:
+		memcpy(&data->data, node_spec, sizeof(struct acpi_aest_vendor));
+		break;
+	case ACPI_AEST_GIC_ERROR_NODE:
+		memcpy(&data->data, node_spec, sizeof(struct acpi_aest_gic));
+		break;
+	default:
+		kfree(data);
+		return -EINVAL;
+	}
+
+	if (node->type == ACPI_AEST_PROCESSOR_ERROR_NODE) {
+		proc_data = ACPI_ADD_PTR(union acpi_aest_processor_data, node_spec,
+					 sizeof(acpi_aest_processor));
+
+		switch (data->data.processor.resource_type) {
+		case ACPI_AEST_CACHE_RESOURCE:
+			memcpy(&data->proc_data, proc_data,
+			       sizeof(struct acpi_aest_processor_cache));
+			break;
+		case ACPI_AEST_TLB_RESOURCE:
+			memcpy(&data->proc_data, proc_data,
+			       sizeof(struct acpi_aest_processor_tlb));
+			break;
+		case ACPI_AEST_GENERIC_RESOURCE:
+			memcpy(&data->proc_data, proc_data,
+			       sizeof(struct acpi_aest_processor_generic));
+			break;
+		}
+	}
+
+	ret = aest_init_interface(node, data);
+	if (ret) {
+		kfree(data);
+		return ret;
+	}
+
+	return aest_init_interrupts(node, data);
+}
+
+static void aest_count_ppi(struct acpi_aest_hdr *node)
+{
+	struct acpi_aest_node_interrupt *interrupt;
+	int i;
+
+	interrupt = ACPI_ADD_PTR(struct acpi_aest_node_interrupt, node,
+				 node->node_interrupt_offset);
+
+	for (i = 0; i < node->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)
+{
+	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;
+
+	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);
+	}
+
+	if (num_ppi > AEST_MAX_PPI) {
+		pr_err("Limiting PPI support to %d PPIs\n", AEST_MAX_PPI);
+		num_ppi = AEST_MAX_PPI;
+	}
+
+	ppi_data = kcalloc(num_ppi, sizeof(struct aest_node_data *),
+			   GFP_KERNEL);
+
+	for (i = 0; i < num_ppi; i++) {
+		ppi_data[i] = alloc_percpu(struct aest_node_data);
+		if (!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);
+
+		aest_node = ACPI_ADD_PTR(struct acpi_aest_hdr, aest_node,
+					 aest_node->length);
+	}
+
+	cpuhp_setup_state(CPUHP_AP_ARM_AEST_STARTING,
+			  "drivers/acpi/arm64/aest:starting",
+			  aest_starting_cpu, aest_dying_cpu);
+
+	return 0;
+
+fail:
+	for (i = 0; i < num_ppi; i++)
+		free_percpu(ppi_data[i]);
+	kfree(ppi_data);
+	return ret;
+}
+
+early_initcall(acpi_aest_init);
diff --git a/include/linux/acpi_aest.h b/include/linux/acpi_aest.h
new file mode 100644
index 000000000000..492503f54ebc
--- /dev/null
+++ b/include/linux/acpi_aest.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef AEST_H
+#define AEST_H
+
+#include <acpi/actbl.h>
+
+#define ACPI_SIG_AEST			"AEST"	/* ARM Error Source Table */
+
+#define AEST_INTERRUPT_MODE		BIT(0)
+
+#define AEST_MAX_PPI			4
+
+#define AEST_PROC_GLOBAL		BIT(0)
+#define AEST_PROC_SHARED		BIT(1)
+
+#define AEST_INTERFACE_SHARED		BIT(0)
+#define AEST_INTERFACE_CLEAR_MISC	BIT(1)
+
+struct aest_interface_data {
+	u8 type;
+	u16 start;
+	u16 end;
+	u32 flags;
+	u64 implemented;
+	u64 status_reporting;
+	struct ras_ext_regs *regs;
+};
+
+union acpi_aest_processor_data {
+	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_node_data {
+	u8 node_type;
+	struct aest_interface_data interface;
+	union aest_node_spec data;
+	union acpi_aest_processor_data proc_data;
+};
+
+#endif /* AEST_H */
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
index 773c83730906..9d30e4c00a52 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -186,6 +186,7 @@ enum cpuhp_state {
 	CPUHP_AP_KVM_ARM_VGIC_INIT_STARTING,
 	CPUHP_AP_KVM_ARM_VGIC_STARTING,
 	CPUHP_AP_KVM_ARM_TIMER_STARTING,
+	CPUHP_AP_ARM_AEST_STARTING,
 	/* Must be the last timer callback */
 	CPUHP_AP_DUMMY_TIMER_STARTING,
 	CPUHP_AP_ARM_XEN_STARTING,
