* [PATCH 00/12] ras: share firmware-first estatus handling
@ 2025-12-17 11:28 Ahmed Tiba
2025-12-17 11:28 ` [PATCH 01/12] ras: add estatus core interfaces Ahmed Tiba
` (12 more replies)
0 siblings, 13 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Platforms that rely on firmware-first RAS today only get the full Linux
handling pipeline when the records arrive through ACPI/APEI GHES. This
series lifts the generic parts of GHES into a reusable estatus core, wires
GHES up to that core, and adds a DeviceTree-facing provider so non-ACPI
systems can route CPER records through the same logic. The final patches
document the binding and the admin-guide flow.
The end result is a single estatus implementation that covers vendor record
notifier support, memory error queueing, IRQ/NMI handling and the CXL/PCIe.
GHES and DT users now simply provide transport-specific ops.
This is based on v6.19-rc1
Ahmed Tiba (12):
ras: add estatus core interfaces
ras: add estatus core implementation
ras: add estatus vendor handling and processing
ras: add estatus queuing and IRQ/NMI handling
ras: flesh out estatus processing core
efi/cper: adopt estatus iteration helpers
ghes: prepare estatus hooks for shared handling
ghes: add estatus provider ops
ghes: route error handling through shared estatus core
dt-bindings: ras: document estatus provider
ras: add DeviceTree estatus provider driver
doc: ras: describe firmware-first estatus flow
Documentation/admin-guide/RAS/main.rst | 24 +
.../devicetree/bindings/ras/arm,ras-ffh.yaml | 95 ++
MAINTAINERS | 8 +
arch/arm64/include/asm/fixmap.h | 5 +
drivers/acpi/apei/Kconfig | 1 +
drivers/acpi/apei/ghes.c | 1292 +++--------------
drivers/firmware/efi/Kconfig | 11 +
drivers/firmware/efi/Makefile | 1 +
drivers/firmware/efi/cper.c | 29 +-
drivers/firmware/efi/estatus.c | 1056 ++++++++++++++
drivers/ras/Kconfig | 14 +
drivers/ras/Makefile | 1 +
drivers/ras/estatus-dt.c | 318 ++++
include/acpi/ghes.h | 58 +-
include/linux/estatus.h | 267 ++++
15 files changed, 2001 insertions(+), 1179 deletions(-)
create mode 100644 Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
create mode 100644 drivers/firmware/efi/estatus.c
create mode 100644 drivers/ras/estatus-dt.c
create mode 100644 include/linux/estatus.h
--
2.43.0
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH 01/12] ras: add estatus core interfaces
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 02/12] ras: add estatus core implementation Ahmed Tiba
` (11 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Introduce CONFIG_RAS_ESTATUS_CORE and the public header that exposes
the generic error-status abstractions. Nothing uses the option yet, but
the definitions are shared by both GHES and the forthcoming DeviceTree
provider, so land them ahead of the core implementation.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
MAINTAINERS | 5 +
drivers/firmware/efi/Kconfig | 11 ++
include/linux/estatus.h | 267 +++++++++++++++++++++++++++++++++++
3 files changed, 283 insertions(+)
create mode 100644 include/linux/estatus.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 5b11839cba9d..501b6d300aa5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21757,6 +21757,11 @@ M: Alexandre Bounine <alex.bou9@gmail.com>
S: Maintained
F: drivers/rapidio/
+RAS ERROR STATUS
+M: Ahmed Tiba <ahmed.tiba@arm.com>
+S: Maintained
+F: include/linux/estatus.h
+
RAS INFRASTRUCTURE
M: Tony Luck <tony.luck@intel.com>
M: Borislav Petkov <bp@alien8.de>
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 29e0729299f5..d348ceb81cfb 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -329,6 +329,17 @@ config UEFI_CPER_X86
depends on UEFI_CPER && X86
default y
+config RAS_ESTATUS_CORE
+ bool "Firmware-first estatus processing core"
+ depends on UEFI_CPER
+ select GENERIC_ALLOCATOR
+ help
+ Provide the shared Common Platform Error Record (CPER) handling core
+ that firmware-first error sources reuse to read, cache, log, and
+ dispatch Generic Hardware Error Status Blocks. This gets selected
+ automatically by providers such as ACPI APEI GHES or the DeviceTree
+ estatus driver.
+
config TEE_STMM_EFI
tristate "TEE-based EFI runtime variable service driver"
depends on EFI && OPTEE
diff --git a/include/linux/estatus.h b/include/linux/estatus.h
new file mode 100644
index 000000000000..002a9533c85a
--- /dev/null
+++ b/include/linux/estatus.h
@@ -0,0 +1,267 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Firmware-first RAS: Generic Error Status Core
+ *
+ * Copyright (C) 2025 ARM Ltd.
+ * Author: Ahmed Tiba <ahmed.tiba@arm.com>
+ */
+
+#ifndef __LINUX_ESTATUS_H
+#define __LINUX_ESTATUS_H
+
+/* "estatus" abbreviates "error status" (CPER status blocks). */
+
+/*
+ * "estatus" is a contraction of "error status". The naming mirrors the ACPI
+ * Generic Error Status Block (HEST/CPER) terminology while staying agnostic of
+ * the transport (ACPI, DeviceTree, etc.).
+ */
+
+#include <linux/irq_work.h>
+#include <linux/kconfig.h>
+#include <linux/cper.h>
+#include <asm/fixmap.h>
+
+#if IS_ENABLED(CONFIG_ACPI)
+#include <linux/acpi.h>
+#include <acpi/actbl1.h>
+#define estatus_generic_status struct acpi_hest_generic_status
+#define estatus_generic_data struct acpi_hest_generic_data
+#define estatus_generic_data_v300 struct acpi_hest_generic_data_v300
+#else
+struct estatus_generic_status {
+ u32 block_status;
+ u32 raw_data_offset;
+ u32 raw_data_length;
+ u32 data_length;
+ u32 error_severity;
+} __packed;
+
+struct estatus_generic_data {
+ u8 section_type[16];
+ u32 error_severity;
+ u16 revision;
+ u8 validation_bits;
+ u8 flags;
+ u32 error_data_length;
+ u8 fru_id[16];
+ u8 fru_text[20];
+} __packed;
+
+struct estatus_generic_data_v300 {
+ u8 section_type[16];
+ u32 error_severity;
+ u16 revision;
+ u8 validation_bits;
+ u8 flags;
+ u32 error_data_length;
+ u8 fru_id[16];
+ u8 fru_text[20];
+ u64 time_stamp;
+} __packed;
+
+#define estatus_generic_status struct estatus_generic_status
+#define estatus_generic_data struct estatus_generic_data
+#define estatus_generic_data_v300 struct estatus_generic_data_v300
+
+#define acpi_hest_generic_status estatus_generic_status
+#define acpi_hest_generic_data estatus_generic_data
+#define acpi_hest_generic_data_v300 estatus_generic_data_v300
+#endif
+
+struct estatus_source;
+
+#if IS_ENABLED(CONFIG_ACPI_APEI_GHES)
+#include <acpi/apei.h>
+#endif
+
+void estatus_report_mem_error(int sev, struct cper_sec_mem_err *mem_err);
+
+enum estatus_notify_mode {
+ ESTATUS_NOTIFY_ASYNC,
+ ESTATUS_NOTIFY_SEA,
+};
+
+struct estatus_ops {
+ int (*get_phys)(struct estatus_source *source, phys_addr_t *addr);
+ int (*read)(struct estatus_source *source, phys_addr_t addr,
+ void *buf, size_t len, enum fixed_addresses fixmap_idx);
+ int (*write)(struct estatus_source *source, phys_addr_t addr,
+ const void *buf, size_t len, enum fixed_addresses fixmap_idx);
+ void (*ack)(struct estatus_source *source);
+ size_t (*get_max_len)(struct estatus_source *source);
+ enum estatus_notify_mode (*get_notify_mode)(struct estatus_source *source);
+ const char *(*get_name)(struct estatus_source *source);
+};
+
+struct estatus_source {
+ const struct estatus_ops *ops;
+ void *priv;
+ estatus_generic_status *estatus;
+ enum fixed_addresses fixmap_idx;
+};
+
+struct estatus_node {
+ struct llist_node llnode;
+ struct estatus_source *source;
+};
+
+struct estatus_cache {
+ u32 estatus_len;
+ atomic_t count;
+ struct estatus_source *source;
+ unsigned long long time_in;
+ struct rcu_head rcu;
+};
+
+enum {
+ ESTATUS_SEV_NO = 0x0,
+ ESTATUS_SEV_CORRECTED = 0x1,
+ ESTATUS_SEV_RECOVERABLE = 0x2,
+ ESTATUS_SEV_PANIC = 0x3,
+};
+
+int estatus_proc(struct estatus_source *ghes);
+int estatus_in_nmi_queue_one_entry(struct estatus_source *ghes,
+ enum fixed_addresses fixmap_idx);
+void estatus_proc_in_irq(struct irq_work *irq_work);
+
+/**
+ * estatus_register_vendor_record_notifier - register a notifier for vendor
+ * records that the kernel would otherwise ignore.
+ * @nb: pointer to the notifier_block structure of the event handler.
+ *
+ * return 0 : SUCCESS, non-zero : FAIL
+ */
+int estatus_register_vendor_record_notifier(struct notifier_block *nb);
+
+/**
+ * estatus_unregister_vendor_record_notifier - unregister the previously
+ * registered vendor record notifier.
+ * @nb: pointer to the notifier_block structure of the vendor record handler.
+ */
+void estatus_unregister_vendor_record_notifier(struct notifier_block *nb);
+
+int estatus_pool_init(unsigned int num_ghes);
+
+struct notifier_block;
+void estatus_register_report_chain(struct notifier_block *nb);
+void estatus_unregister_report_chain(struct notifier_block *nb);
+
+static inline int estatus_get_version(estatus_generic_data *gdata)
+{
+ return gdata->revision >> 8;
+}
+
+static inline void *estatus_get_payload(estatus_generic_data *gdata)
+{
+ if (estatus_get_version(gdata) >= 3)
+ return (void *)(((estatus_generic_data_v300 *)(gdata)) + 1);
+
+ return gdata + 1;
+}
+
+static inline int estatus_get_error_length(estatus_generic_data *gdata)
+{
+ return gdata->error_data_length;
+}
+
+static inline int estatus_get_size(estatus_generic_data *gdata)
+{
+ if (estatus_get_version(gdata) >= 3)
+ return sizeof(estatus_generic_data_v300);
+
+ return sizeof(estatus_generic_data);
+}
+
+static inline int estatus_get_record_size(estatus_generic_data *gdata)
+{
+ return (estatus_get_size(gdata) + estatus_get_error_length(gdata));
+}
+
+static inline void *estatus_get_next(estatus_generic_data *gdata)
+{
+ return (void *)(gdata) + estatus_get_record_size(gdata);
+}
+
+static inline estatus_generic_data *
+estatus_first_section(estatus_generic_status *estatus)
+{
+ return (estatus_generic_data *)(estatus + 1);
+}
+
+static inline bool
+estatus_section_valid(estatus_generic_status *estatus,
+ estatus_generic_data *section)
+{
+ return (void *)section - (void *)(estatus + 1) < estatus->data_length;
+}
+
+struct estatus_section_iter {
+ estatus_generic_status *estatus;
+ estatus_generic_data *section;
+ bool started;
+};
+
+static inline estatus_generic_data *
+estatus_section_iter_next(struct estatus_section_iter *iter,
+ estatus_generic_status *estatus)
+{
+ if (!iter->started) {
+ iter->estatus = estatus;
+ iter->section = estatus_first_section(estatus);
+ iter->started = true;
+ } else if (iter->section) {
+ iter->section = estatus_get_next(iter->section);
+ }
+
+ if (!iter->section)
+ return NULL;
+
+ if (!estatus_section_valid(iter->estatus, iter->section)) {
+ iter->section = NULL;
+ return NULL;
+ }
+
+ return iter->section;
+}
+
+#define estatus_for_each_section(_estatus, _section) \
+ for (struct estatus_section_iter __estatus_iter = {0}; \
+ ((_section) = estatus_section_iter_next(&__estatus_iter, (_estatus))); \
+ )
+
+static inline int acpi_hest_get_version(struct acpi_hest_generic_data *gdata)
+{
+ return estatus_get_version(gdata);
+}
+
+static inline void *acpi_hest_get_payload(struct acpi_hest_generic_data *gdata)
+{
+ return estatus_get_payload(gdata);
+}
+
+static inline int acpi_hest_get_error_length(struct acpi_hest_generic_data *gdata)
+{
+ return estatus_get_error_length(gdata);
+}
+
+static inline int acpi_hest_get_size(struct acpi_hest_generic_data *gdata)
+{
+ return estatus_get_size(gdata);
+}
+
+static inline int acpi_hest_get_record_size(struct acpi_hest_generic_data *gdata)
+{
+ return estatus_get_record_size(gdata);
+}
+
+static inline void *acpi_hest_get_next(struct acpi_hest_generic_data *gdata)
+{
+ return estatus_get_next(gdata);
+}
+
+#define apei_estatus_for_each_section(estatus, section) \
+ estatus_for_each_section(estatus, section)
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 02/12] ras: add estatus core implementation
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
2025-12-17 11:28 ` [PATCH 01/12] ras: add estatus core interfaces Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-18 15:42 ` Mauro Carvalho Chehab
2025-12-21 19:31 ` kernel test robot
2025-12-17 11:28 ` [PATCH 03/12] ras: add estatus vendor handling and processing Ahmed Tiba
` (10 subsequent siblings)
12 siblings, 2 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Add estatus.c, hook it into the EFI Makefile, and register
the MAINTAINERS entry for the new code. The implementation provides the
memory-pool helpers, notifier plumbing, and utility functions that the
GHES and DeviceTree providers will reuse in later commits.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
MAINTAINERS | 1 +
drivers/firmware/efi/Makefile | 1 +
drivers/firmware/efi/estatus.c | 560 +++++++++++++++++++++++++++++++++
3 files changed, 562 insertions(+)
create mode 100644 drivers/firmware/efi/estatus.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 501b6d300aa5..67d79d4e612d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21760,6 +21760,7 @@ F: drivers/rapidio/
RAS ERROR STATUS
M: Ahmed Tiba <ahmed.tiba@arm.com>
S: Maintained
+F: drivers/firmware/efi/estatus.c
F: include/linux/estatus.h
RAS INFRASTRUCTURE
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index 8efbcf699e4f..03708d915bcf 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o
obj-$(CONFIG_EFI_ESRT) += esrt.o
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
obj-$(CONFIG_UEFI_CPER) += cper.o cper_cxl.o
+obj-$(CONFIG_RAS_ESTATUS_CORE) += estatus.o
obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
subdir-$(CONFIG_EFI_STUB) += libstub
obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o
diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
new file mode 100644
index 000000000000..8dae5c73ce27
--- /dev/null
+++ b/drivers/firmware/efi/estatus.c
@@ -0,0 +1,560 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Firmware-first RAS: Generic Error Status Core
+ *
+ * Copyright (C) 2025 ARM Ltd.
+ * Author: Ahmed Tiba <ahmed.tiba@arm.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/cper.h>
+#include <linux/ratelimit.h>
+#include <linux/vmalloc.h>
+#include <linux/llist.h>
+#include <linux/genalloc.h>
+#include <linux/pci.h>
+#include <linux/pfn.h>
+#include <linux/aer.h>
+#include <linux/nmi.h>
+#include <linux/sched/clock.h>
+#include <linux/uuid.h>
+#include <linux/kconfig.h>
+#include <linux/ras.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/workqueue.h>
+#include <linux/task_work.h>
+#include <ras/ras_event.h>
+
+#include <linux/estatus.h>
+#include <asm/fixmap.h>
+
+void estatus_pool_region_free(unsigned long addr, u32 size);
+
+static void estatus_log_hw_error(char level, const char *seq_tag,
+ const char *name)
+{
+ switch (level) {
+ case '0':
+ pr_emerg("%sHardware error from %s\n", seq_tag, name);
+ break;
+ case '1':
+ pr_alert("%sHardware error from %s\n", seq_tag, name);
+ break;
+ case '2':
+ pr_crit("%sHardware error from %s\n", seq_tag, name);
+ break;
+ case '3':
+ pr_err("%sHardware error from %s\n", seq_tag, name);
+ break;
+ case '4':
+ pr_warn("%sHardware error from %s\n", seq_tag, name);
+ break;
+ case '5':
+ pr_notice("%sHardware error from %s\n", seq_tag, name);
+ break;
+ case '6':
+ pr_info("%sHardware error from %s\n", seq_tag, name);
+ break;
+ default:
+ pr_debug("%sHardware error from %s\n", seq_tag, name);
+ break;
+ }
+}
+
+static inline u32 estatus_len(struct acpi_hest_generic_status *estatus)
+{
+ if (estatus->raw_data_length)
+ return estatus->raw_data_offset + estatus->raw_data_length;
+
+ return sizeof(*estatus) + estatus->data_length;
+}
+
+#define ESTATUS_PFX "ESTATUS: "
+
+#define ESTATUS_ESOURCE_PREALLOC_MAX_SIZE_SIZE 65536
+
+#define ESTATUS_POOL_MIN_ALLOC_ORDER 3
+
+/* This is just an estimation for memory pool allocation */
+#define ESTATUS_CACHE_AVG_SIZE 512
+
+#define ESTATUS_CACHES_SIZE 4
+
+#define ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL
+/* Prevent too many caches are allocated because of RCU */
+#define ESTATUS_CACHE_ALLOCED_MAX (ESTATUS_CACHES_SIZE * 3 / 2)
+
+#define ESTATUS_CACHE_LEN(estatus_len) \
+ (sizeof(struct estatus_cache) + (estatus_len))
+#define ESTATUS_FROM_CACHE(cache) \
+ ((struct acpi_hest_generic_status *) \
+ ((struct estatus_cache *)(cache) + 1))
+
+#define ESTATUS_NODE_LEN(estatus_len) \
+ (sizeof(struct estatus_node) + (estatus_len))
+#define ESTATUS_FROM_NODE(node) \
+ ((struct acpi_hest_generic_status *) \
+ ((struct estatus_node *)(node) + 1))
+
+#define ESTATUS_VENDOR_ENTRY_LEN(gdata_len) \
+ (sizeof(struct estatus_vendor_record_entry) + (gdata_len))
+#define ESTATUS_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \
+ ((struct acpi_hest_generic_data *) \
+ ((struct estatus_vendor_record_entry *)(vendor_entry) + 1))
+
+static ATOMIC_NOTIFIER_HEAD(estatus_report_chain);
+
+struct estatus_vendor_record_entry {
+ struct work_struct work;
+ int error_severity;
+ char vendor_record[];
+};
+
+static struct estatus_cache __rcu *estatus_caches[ESTATUS_CACHES_SIZE];
+static atomic_t estatus_cache_alloced;
+
+static int estatus_panic_timeout __read_mostly = 30;
+
+static struct gen_pool *estatus_pool;
+static DEFINE_MUTEX(estatus_pool_mutex);
+
+static inline const char *estatus_source_name(struct estatus_source *source)
+{
+ if (source->ops && source->ops->get_name)
+ return source->ops->get_name(source);
+
+ return "unknown";
+}
+
+static inline size_t estatus_source_max_len(struct estatus_source *source)
+{
+ if (source->ops && source->ops->get_max_len)
+ return source->ops->get_max_len(source);
+
+ return 0;
+}
+
+static inline enum estatus_notify_mode
+estatus_source_notify_mode(struct estatus_source *source)
+{
+ if (source->ops && source->ops->get_notify_mode)
+ return source->ops->get_notify_mode(source);
+
+ return ESTATUS_NOTIFY_ASYNC;
+}
+
+static inline int estatus_source_get_phys(struct estatus_source *source,
+ phys_addr_t *addr)
+{
+ if (!source->ops || !source->ops->get_phys)
+ return -EOPNOTSUPP;
+
+ return source->ops->get_phys(source, addr);
+}
+
+static inline int estatus_source_read(struct estatus_source *source,
+ phys_addr_t addr, void *buf, size_t len,
+ enum fixed_addresses fixmap_idx)
+{
+ if (!source->ops || !source->ops->read)
+ return -EOPNOTSUPP;
+
+ return source->ops->read(source, addr, buf, len, fixmap_idx);
+}
+
+static inline int estatus_source_write(struct estatus_source *source,
+ phys_addr_t addr, const void *buf,
+ size_t len,
+ enum fixed_addresses fixmap_idx)
+{
+ if (!source->ops || !source->ops->write)
+ return -EOPNOTSUPP;
+
+ return source->ops->write(source, addr, buf, len, fixmap_idx);
+}
+
+static inline void estatus_source_ack(struct estatus_source *source)
+{
+ if (source->ops && source->ops->ack)
+ source->ops->ack(source);
+}
+
+int estatus_pool_init(unsigned int num_ghes)
+{
+ unsigned long addr, len;
+ int rc = 0;
+
+ mutex_lock(&estatus_pool_mutex);
+ if (estatus_pool)
+ goto out_unlock;
+
+ estatus_pool = gen_pool_create(ESTATUS_POOL_MIN_ALLOC_ORDER, -1);
+ if (!estatus_pool) {
+ rc = -ENOMEM;
+ goto out_unlock;
+ }
+
+ if (!num_ghes)
+ num_ghes = 1;
+
+ len = ESTATUS_CACHE_AVG_SIZE * ESTATUS_CACHE_ALLOCED_MAX;
+ len += (num_ghes * ESTATUS_ESOURCE_PREALLOC_MAX_SIZE_SIZE);
+
+ addr = (unsigned long)vmalloc(PAGE_ALIGN(len));
+ if (!addr) {
+ rc = -ENOMEM;
+ goto err_pool_alloc;
+ }
+
+ rc = gen_pool_add(estatus_pool, addr, PAGE_ALIGN(len), -1);
+ if (rc)
+ goto err_pool_add;
+
+out_unlock:
+ mutex_unlock(&estatus_pool_mutex);
+ return rc;
+
+err_pool_add:
+ vfree((void *)addr);
+err_pool_alloc:
+ gen_pool_destroy(estatus_pool);
+ estatus_pool = NULL;
+ goto out_unlock;
+}
+
+/**
+ * estatus_pool_region_free - free previously allocated memory
+ * from the estatus_pool.
+ * @addr: address of memory to free.
+ * @size: size of memory to free.
+ *
+ * Returns none.
+ */
+void estatus_pool_region_free(unsigned long addr, u32 size)
+{
+ gen_pool_free(estatus_pool, addr, size);
+}
+EXPORT_SYMBOL_GPL(estatus_pool_region_free);
+
+/* Check the top-level record header has an appropriate size. */
+static int __estatus_check_estatus(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus)
+{
+ u32 len = estatus_len(estatus);
+ size_t max_len = estatus_source_max_len(source);
+
+ if (len < sizeof(*estatus)) {
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Truncated error status block!\n");
+ return -EIO;
+ }
+
+ if (max_len && len > max_len) {
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Invalid error status block length!\n");
+ return -EIO;
+ }
+
+ if (cper_estatus_check_header(estatus)) {
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Invalid CPER header!\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/* Read the CPER block, returning its address, and header in estatus. */
+static int __estatus_peek_estatus(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus,
+ phys_addr_t *buf_paddr,
+ enum fixed_addresses fixmap_idx)
+{
+ int rc;
+
+ rc = estatus_source_get_phys(source, buf_paddr);
+ if (rc) {
+ *buf_paddr = 0;
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX
+ "Failed to get error status block address for provider %s: %d\n",
+ estatus_source_name(source), rc);
+ return rc;
+ }
+
+ if (!*buf_paddr)
+ return -ENOENT;
+
+ rc = estatus_source_read(source, *buf_paddr, estatus,
+ sizeof(*estatus), fixmap_idx);
+ if (rc)
+ return rc;
+
+ if (!estatus->block_status) {
+ *buf_paddr = 0;
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static int __estatus_read_estatus(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus,
+ phys_addr_t buf_paddr,
+ enum fixed_addresses fixmap_idx,
+ size_t buf_len)
+{
+ int rc;
+
+ rc = estatus_source_read(source, buf_paddr, estatus, buf_len,
+ fixmap_idx);
+ if (rc)
+ return rc;
+
+ if (cper_estatus_check(estatus)) {
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX
+ "Failed to read error status block for provider %s!\n",
+ estatus_source_name(source));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int estatus_read_estatus(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus,
+ phys_addr_t *buf_paddr,
+ enum fixed_addresses fixmap_idx)
+{
+ int rc;
+
+ rc = __estatus_peek_estatus(source, estatus, buf_paddr, fixmap_idx);
+ if (rc)
+ return rc;
+
+ rc = __estatus_check_estatus(source, estatus);
+ if (rc)
+ return rc;
+
+ return __estatus_read_estatus(source, estatus, *buf_paddr,
+ fixmap_idx, estatus_len(estatus));
+}
+
+static void estatus_clear_estatus(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus,
+ phys_addr_t buf_paddr,
+ enum fixed_addresses fixmap_idx)
+{
+ int rc;
+
+ estatus->block_status = 0;
+
+ if (!buf_paddr)
+ return;
+
+ rc = estatus_source_write(source, buf_paddr, estatus,
+ sizeof(estatus->block_status), fixmap_idx);
+ if (rc)
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX
+ "Failed to clear error status block for provider %s: %d\n",
+ estatus_source_name(source), rc);
+
+ estatus_source_ack(source);
+}
+
+static inline int estatus_severity(int severity)
+{
+ switch (severity) {
+ case CPER_SEV_INFORMATIONAL:
+ return ESTATUS_SEV_NO;
+ case CPER_SEV_CORRECTED:
+ return ESTATUS_SEV_CORRECTED;
+ case CPER_SEV_RECOVERABLE:
+ return ESTATUS_SEV_RECOVERABLE;
+ case CPER_SEV_FATAL:
+ return ESTATUS_SEV_PANIC;
+ default:
+ /* Unknown, go panic */
+ return ESTATUS_SEV_PANIC;
+ }
+}
+
+static void __estatus_print_estatus(const char *pfx,
+ struct estatus_source *source,
+ const struct acpi_hest_generic_status *estatus)
+{
+ static atomic_t seqno;
+ unsigned int curr_seqno;
+ char pfx_seq[64];
+ char seq_tag[64];
+ const char *name = estatus_source_name(source);
+ const char *level = pfx;
+ char level_char = '4';
+
+ if (!level) {
+ if (estatus_severity(estatus->error_severity) <=
+ ESTATUS_SEV_CORRECTED)
+ level = KERN_WARNING;
+ else
+ level = KERN_ERR;
+ }
+
+ if (level[0] == KERN_SOH_ASCII && level[1])
+ level_char = level[1];
+ else if (estatus_severity(estatus->error_severity) > ESTATUS_SEV_CORRECTED)
+ level_char = '3';
+
+ curr_seqno = atomic_inc_return(&seqno);
+ snprintf(seq_tag, sizeof(seq_tag), "{%u}" HW_ERR, curr_seqno);
+ snprintf(pfx_seq, sizeof(pfx_seq), "%s%s", level, seq_tag);
+ estatus_log_hw_error(level_char, seq_tag, name);
+ cper_estatus_print(pfx_seq, estatus);
+}
+
+static int estatus_print_estatus(const char *pfx,
+ struct estatus_source *source,
+ const struct acpi_hest_generic_status *estatus)
+{
+ /* Not more than 2 messages every 5 seconds */
+ static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5 * HZ, 2);
+ static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5 * HZ, 2);
+ struct ratelimit_state *ratelimit;
+
+ if (estatus_severity(estatus->error_severity) <= ESTATUS_SEV_CORRECTED)
+ ratelimit = &ratelimit_corrected;
+ else
+ ratelimit = &ratelimit_uncorrected;
+ if (__ratelimit(ratelimit)) {
+ __estatus_print_estatus(pfx, source, estatus);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * GHES error status reporting throttle, to report more kinds of
+ * errors, instead of just most frequently occurred errors.
+ */
+static int estatus_cached(struct acpi_hest_generic_status *estatus)
+{
+ u32 len;
+ int i, cached = 0;
+ unsigned long long now;
+ struct estatus_cache *cache;
+ struct acpi_hest_generic_status *cache_estatus;
+
+ len = estatus_len(estatus);
+ rcu_read_lock();
+ for (i = 0; i < ESTATUS_CACHES_SIZE; i++) {
+ cache = rcu_dereference(estatus_caches[i]);
+ if (!cache)
+ continue;
+ if (len != cache->estatus_len)
+ continue;
+ cache_estatus = ESTATUS_FROM_CACHE(cache);
+ if (memcmp(estatus, cache_estatus, len))
+ continue;
+ atomic_inc(&cache->count);
+ now = sched_clock();
+ if (now - cache->time_in < ESTATUS_IN_CACHE_MAX_NSEC)
+ cached = 1;
+ break;
+ }
+ rcu_read_unlock();
+ return cached;
+}
+
+static struct estatus_cache *estatus_cache_alloc(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus)
+{
+ int alloced;
+ u32 len, cache_len;
+ struct estatus_cache *cache;
+ struct acpi_hest_generic_status *cache_estatus;
+
+ alloced = atomic_add_return(1, &estatus_cache_alloced);
+ if (alloced > ESTATUS_CACHE_ALLOCED_MAX) {
+ atomic_dec(&estatus_cache_alloced);
+ return NULL;
+ }
+ len = estatus_len(estatus);
+ cache_len = ESTATUS_CACHE_LEN(len);
+ cache = (void *)gen_pool_alloc(estatus_pool, cache_len);
+ if (!cache) {
+ atomic_dec(&estatus_cache_alloced);
+ return NULL;
+ }
+ cache_estatus = ESTATUS_FROM_CACHE(cache);
+ memcpy(cache_estatus, estatus, len);
+ cache->estatus_len = len;
+ atomic_set(&cache->count, 0);
+ cache->source = source;
+ cache->time_in = sched_clock();
+ return cache;
+}
+
+static void estatus_cache_rcu_free(struct rcu_head *head)
+{
+ struct estatus_cache *cache;
+ u32 len;
+
+ cache = container_of(head, struct estatus_cache, rcu);
+ len = estatus_len(ESTATUS_FROM_CACHE(cache));
+ len = ESTATUS_CACHE_LEN(len);
+ gen_pool_free(estatus_pool, (unsigned long)cache, len);
+ atomic_dec(&estatus_cache_alloced);
+}
+
+static void estatus_cache_add(struct estatus_source *source,
+ struct acpi_hest_generic_status *estatus)
+{
+ unsigned long long now, duration, period, max_period = 0;
+ struct estatus_cache *cache, *new_cache;
+ struct estatus_cache __rcu *victim;
+ int i, slot = -1, count;
+
+ new_cache = estatus_cache_alloc(source, estatus);
+ if (!new_cache)
+ return;
+
+ rcu_read_lock();
+ now = sched_clock();
+ for (i = 0; i < ESTATUS_CACHES_SIZE; i++) {
+ cache = rcu_dereference(estatus_caches[i]);
+ if (!cache) {
+ slot = i;
+ break;
+ }
+ duration = now - cache->time_in;
+ if (duration >= ESTATUS_IN_CACHE_MAX_NSEC) {
+ slot = i;
+ break;
+ }
+ count = atomic_read(&cache->count);
+ period = duration;
+ do_div(period, (count + 1));
+ if (period > max_period) {
+ max_period = period;
+ slot = i;
+ }
+ }
+ rcu_read_unlock();
+
+ if (slot != -1) {
+ /*
+ * Use release semantics to ensure that estatus_cached()
+ * running on another CPU will see the updated cache fields if
+ * it can see the new value of the pointer.
+ */
+ victim = xchg_release(&estatus_caches[slot],
+ RCU_INITIALIZER(new_cache));
+
+ /*
+ * At this point, victim may point to a cached item different
+ * from the one based on which we selected the slot. Instead of
+ * going to the loop again to pick another slot, let's just
+ * drop the other item anyway: this may cause a false cache
+ * miss later on, but that won't cause any problems.
+ */
+ if (victim)
+ call_rcu(&unrcu_pointer(victim)->rcu,
+ estatus_cache_rcu_free);
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
2025-12-17 11:28 ` [PATCH 01/12] ras: add estatus core interfaces Ahmed Tiba
2025-12-17 11:28 ` [PATCH 02/12] ras: add estatus core implementation Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-18 16:04 ` Mauro Carvalho Chehab
2025-12-21 23:39 ` kernel test robot
2025-12-17 11:28 ` [PATCH 04/12] ras: add estatus queuing and IRQ/NMI handling Ahmed Tiba
` (9 subsequent siblings)
12 siblings, 2 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Teach the estatus core how to walk CPER records and expose the vendor
record notification path. This adds the section iteration helpers,
the logging helpers that mirror the GHES behaviour, and the deferred
work used to hand vendor GUIDs to interested drivers. No users switch
over yet; this simply moves the common logic out of GHES so the next
patches can wire it up.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/firmware/efi/estatus.c | 415 +++++++++++++++++++++++++++++++++
1 file changed, 415 insertions(+)
diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
index 8dae5c73ce27..5a848d1b218e 100644
--- a/drivers/firmware/efi/estatus.c
+++ b/drivers/firmware/efi/estatus.c
@@ -17,6 +17,7 @@
#include <linux/aer.h>
#include <linux/nmi.h>
#include <linux/sched/clock.h>
+#include <linux/sched/signal.h>
#include <linux/uuid.h>
#include <linux/kconfig.h>
#include <linux/ras.h>
@@ -119,6 +120,14 @@ static int estatus_panic_timeout __read_mostly = 30;
static struct gen_pool *estatus_pool;
static DEFINE_MUTEX(estatus_pool_mutex);
+static enum fixed_addresses estatus_source_fixmap(struct estatus_source *source)
+{
+ if (WARN_ON_ONCE(!source->fixmap_idx))
+ return FIX_HOLE;
+
+ return source->fixmap_idx;
+}
+
static inline const char *estatus_source_name(struct estatus_source *source)
{
if (source->ops && source->ops->get_name)
@@ -558,3 +567,409 @@ static void estatus_cache_add(struct estatus_source *source,
estatus_cache_rcu_free);
}
}
+
+struct estatus_task_work {
+ struct callback_head twork;
+ u64 pfn;
+ int flags;
+};
+
+static void estatus_memory_failure_cb(struct callback_head *twork)
+{
+ struct estatus_task_work *twcb = container_of(twork, struct estatus_task_work, twork);
+ int ret;
+
+ ret = memory_failure(twcb->pfn, twcb->flags);
+ gen_pool_free(estatus_pool, (unsigned long)twcb, sizeof(*twcb));
+
+ if (!ret || ret == -EHWPOISON || ret == -EOPNOTSUPP)
+ return;
+
+ pr_err(HW_ERR ESTATUS_PFX
+ "%#llx: Sending SIGBUS to %s:%d due to hardware memory corruption\n",
+ twcb->pfn, current->comm, task_pid_nr(current));
+ force_sig(SIGBUS);
+}
+
+static bool estatus_do_memory_failure(u64 physical_addr, int flags)
+{
+ struct estatus_task_work *twcb;
+ unsigned long pfn;
+
+ if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
+ return false;
+
+ pfn = PHYS_PFN(physical_addr);
+ if (!pfn_valid(pfn) && !arch_is_platform_page(physical_addr)) {
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX
+ "Invalid address in generic error data: %#llx\n",
+ physical_addr);
+ return false;
+ }
+
+ if (flags == MF_ACTION_REQUIRED && current->mm) {
+ twcb = (void *)gen_pool_alloc(estatus_pool, sizeof(*twcb));
+ if (!twcb)
+ return false;
+
+ twcb->pfn = pfn;
+ twcb->flags = flags;
+ init_task_work(&twcb->twork, estatus_memory_failure_cb);
+ task_work_add(current, &twcb->twork, TWA_RESUME);
+ return true;
+ }
+
+ memory_failure_queue(pfn, flags);
+ return true;
+}
+
+static bool estatus_handle_memory_failure(estatus_generic_data *gdata, int sev, bool sync)
+{
+ int flags = -1;
+ int sec_sev = estatus_severity(gdata->error_severity);
+ struct cper_sec_mem_err *mem_err = estatus_get_payload(gdata);
+
+ if (!(mem_err->validation_bits & CPER_MEM_VALID_PA))
+ return false;
+
+ /* iff following two events can be handled properly by now */
+ if (sec_sev == ESTATUS_SEV_CORRECTED &&
+ (gdata->flags & CPER_SEC_ERROR_THRESHOLD_EXCEEDED))
+ flags = MF_SOFT_OFFLINE;
+ if (sev == ESTATUS_SEV_RECOVERABLE && sec_sev == ESTATUS_SEV_RECOVERABLE)
+ flags = sync ? MF_ACTION_REQUIRED : 0;
+
+ if (flags != -1)
+ return estatus_do_memory_failure(mem_err->physical_addr, flags);
+
+ return false;
+}
+
+static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
+{
+ struct cper_sec_proc_arm *err = estatus_get_payload(gdata);
+ int flags = sync ? MF_ACTION_REQUIRED : 0;
+ bool queued = false;
+ int sec_sev, i;
+ char *p;
+
+ log_arm_hw_error(err);
+
+ sec_sev = estatus_severity(gdata->error_severity);
+ if (sev != ESTATUS_SEV_RECOVERABLE || sec_sev != ESTATUS_SEV_RECOVERABLE)
+ return false;
+
+ p = (char *)(err + 1);
+ for (i = 0; i < err->err_info_num; i++) {
+ struct cper_arm_err_info *err_info = (struct cper_arm_err_info *)p;
+ bool is_cache = (err_info->type == CPER_ARM_CACHE_ERROR);
+ bool has_pa = (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR);
+ const char *error_type = "unknown error";
+
+ /*
+ * The field (err_info->error_info & BIT(26)) is fixed to set to
+ * 1 in some old firmware of HiSilicon Kunpeng920. We assume that
+ * firmware won't mix corrected errors in an uncorrected section,
+ * and don't filter out 'corrected' error here.
+ */
+ if (is_cache && has_pa) {
+ queued = estatus_do_memory_failure(err_info->physical_fault_addr, flags);
+ p += err_info->length;
+ continue;
+ }
+
+ if (err_info->type < ARRAY_SIZE(cper_proc_error_type_strs))
+ error_type = cper_proc_error_type_strs[err_info->type];
+
+ pr_warn_ratelimited(FW_WARN ESTATUS_PFX
+ "Unhandled processor error type: %s\n",
+ error_type);
+ p += err_info->length;
+ }
+
+ return queued;
+}
+
+/*
+ * PCIe AER errors need to be sent to the AER driver for reporting and
+ * recovery. The ESTATUS severities map to the following AER severities and
+ * require the following handling:
+ *
+ * ESTATUS_SEV_CORRECTABLE -> AER_CORRECTABLE
+ * These need to be reported by the AER driver but no recovery is
+ * necessary.
+ * ESTATUS_SEV_RECOVERABLE -> AER_NONFATAL
+ * ESTATUS_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL
+ * These both need to be reported and recovered from by the AER driver.
+ * ESTATUS_SEV_PANIC does not make it to this handling since the kernel must
+ * panic.
+ */
+static void estatus_handle_aer(estatus_generic_data *gdata)
+{
+#ifdef CONFIG_ACPI_APEI_PCIEAER
+ struct cper_sec_pcie *pcie_err = estatus_get_payload(gdata);
+
+ if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID &&
+ pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) {
+ unsigned int devfn;
+ int aer_severity;
+ u8 *aer_info;
+
+ devfn = PCI_DEVFN(pcie_err->device_id.device,
+ pcie_err->device_id.function);
+ aer_severity = cper_severity_to_aer(gdata->error_severity);
+
+ /*
+ * If firmware reset the component to contain
+ * the error, we must reinitialize it before
+ * use, so treat it as a fatal AER error.
+ */
+ if (gdata->flags & CPER_SEC_RESET)
+ aer_severity = AER_FATAL;
+
+ aer_info = (void *)gen_pool_alloc(estatus_pool,
+ sizeof(struct aer_capability_regs));
+ if (!aer_info)
+ return;
+ memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs));
+
+ aer_recover_queue(pcie_err->device_id.segment,
+ pcie_err->device_id.bus,
+ devfn, aer_severity,
+ (struct aer_capability_regs *)
+ aer_info);
+ }
+#endif
+}
+
+static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list);
+
+int estatus_register_vendor_record_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&vendor_record_notify_list, nb);
+}
+EXPORT_SYMBOL_GPL(estatus_register_vendor_record_notifier);
+
+void estatus_unregister_vendor_record_notifier(struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&vendor_record_notify_list, nb);
+}
+EXPORT_SYMBOL_GPL(estatus_unregister_vendor_record_notifier);
+
+static void estatus_vendor_record_work_func(struct work_struct *work)
+{
+ struct estatus_vendor_record_entry *entry;
+ estatus_generic_data *gdata;
+ u32 len;
+
+ entry = container_of(work, struct estatus_vendor_record_entry, work);
+ gdata = ESTATUS_GDATA_FROM_VENDOR_ENTRY(entry);
+
+ blocking_notifier_call_chain(&vendor_record_notify_list,
+ entry->error_severity, gdata);
+
+ len = ESTATUS_VENDOR_ENTRY_LEN(estatus_get_record_size(gdata));
+ gen_pool_free(estatus_pool, (unsigned long)entry, len);
+}
+
+static void estatus_defer_non_standard_event(estatus_generic_data *gdata, int sev)
+{
+ estatus_generic_data *copied_gdata;
+ struct estatus_vendor_record_entry *entry;
+ u32 len;
+
+ len = ESTATUS_VENDOR_ENTRY_LEN(estatus_get_record_size(gdata));
+ entry = (void *)gen_pool_alloc(estatus_pool, len);
+ if (!entry)
+ return;
+
+ copied_gdata = ESTATUS_GDATA_FROM_VENDOR_ENTRY(entry);
+ memcpy(copied_gdata, gdata, estatus_get_record_size(gdata));
+ entry->error_severity = sev;
+
+ INIT_WORK(&entry->work, estatus_vendor_record_work_func);
+ schedule_work(&entry->work);
+}
+
+/*
+ * A platform may describe one error source for the handling of synchronous
+ * errors (e.g. MCE or SEA), or for handling asynchronous errors (e.g. SCI
+ * or External Interrupt). On x86, the HEST notifications are always
+ * asynchronous, so only SEA on ARM is delivered as a synchronous
+ * notification.
+ */
+static inline bool estatus_is_sync_notify(struct estatus_source *source)
+{
+ return estatus_source_notify_mode(source) == ESTATUS_NOTIFY_SEA;
+}
+
+static void estatus_do_proc(struct estatus_source *source, const estatus_generic_status *estatus)
+{
+ int sev, sec_sev;
+ estatus_generic_data *gdata;
+ guid_t *sec_type;
+ const guid_t *fru_id = &guid_null;
+ char *fru_text = "";
+ bool queued = false;
+ bool sync = estatus_is_sync_notify(source);
+
+ sev = estatus_severity(estatus->error_severity);
+ estatus_for_each_section(estatus, gdata) {
+ sec_type = (guid_t *)gdata->section_type;
+ sec_sev = estatus_severity(gdata->error_severity);
+ if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
+ fru_id = (guid_t *)gdata->fru_id;
+
+ if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
+ fru_text = gdata->fru_text;
+
+ if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
+ struct cper_sec_mem_err *mem_err = estatus_get_payload(gdata);
+
+ atomic_notifier_call_chain(&estatus_report_chain, sev, mem_err);
+
+ estatus_report_mem_error(sev, mem_err);
+ queued = estatus_handle_memory_failure(gdata, sev, sync);
+ } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
+ estatus_handle_aer(gdata);
+ } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) {
+ queued = estatus_handle_arm_hw_error(gdata, sev, sync);
+ } else {
+ void *err = estatus_get_payload(gdata);
+
+ estatus_defer_non_standard_event(gdata, sev);
+ log_non_standard_event(sec_type, fru_id, fru_text,
+ sec_sev, err,
+ gdata->error_data_length);
+ }
+ }
+
+ if (sync && !queued) {
+ pr_err(HW_ERR ESTATUS_PFX
+ "%s: synchronous unrecoverable error (SIGBUS)\n",
+ estatus_source_name(source));
+ force_sig(SIGBUS);
+ }
+}
+
+static void __estatus_panic(struct estatus_source *source, estatus_generic_status *estatus,
+ phys_addr_t buf_paddr, enum fixed_addresses fixmap_idx)
+{
+ const char *msg = ESTATUS_PFX "Fatal hardware error";
+
+ __estatus_print_estatus(KERN_EMERG, source, estatus);
+
+ add_taint(TAINT_MACHINE_CHECK, LOCKDEP_STILL_OK);
+
+ estatus_clear_estatus(source, estatus, buf_paddr, fixmap_idx);
+
+ if (!panic_timeout)
+ pr_emerg("%s but panic disabled\n", msg);
+
+ panic(msg);
+}
+
+int estatus_proc(struct estatus_source *source)
+{
+ estatus_generic_status *estatus = source->estatus;
+ phys_addr_t buf_paddr;
+ enum fixed_addresses fixmap_idx = estatus_source_fixmap(source);
+ int rc;
+
+ rc = estatus_read_estatus(source, estatus, &buf_paddr, fixmap_idx);
+ if (rc)
+ goto out;
+
+ if (estatus_severity(estatus->error_severity) >= ESTATUS_SEV_PANIC)
+ __estatus_panic(source, estatus, buf_paddr, fixmap_idx);
+
+ if (!estatus_cached(estatus)) {
+ if (estatus_print_estatus(NULL, source, estatus))
+ estatus_cache_add(source, estatus);
+ }
+ estatus_do_proc(source, estatus);
+
+out:
+ estatus_clear_estatus(source, estatus, buf_paddr, fixmap_idx);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(estatus_proc);
+
+/*
+ * Handlers for CPER records may not be NMI safe. For example,
+ * memory_failure_queue() takes spinlocks and calls schedule_work_on().
+ * In any NMI-like handler, memory from estatus_pool is used to save
+ * estatus, and added to the estatus_llist. irq_work_queue() causes
+ * estatus_proc_in_irq() to run in IRQ context where each estatus in
+ * estatus_llist is processed.
+ *
+ * Memory from the estatus_pool is also used with the estatus_cache
+ * to suppress frequent messages.
+ */
+static struct llist_head estatus_llist;
+
+void estatus_proc_in_irq(struct irq_work *irq_work)
+{
+ struct llist_node *llnode, *next;
+ struct estatus_node *estatus_node;
+ struct estatus_source *source;
+ estatus_generic_status *estatus;
+ u32 len, node_len;
+
+ llnode = llist_del_all(&estatus_llist);
+ /*
+ * Because the time order of estatus in list is reversed,
+ * revert it back to proper order.
+ */
+ llnode = llist_reverse_order(llnode);
+ while (llnode) {
+ next = llnode->next;
+ estatus_node = llist_entry(llnode, struct estatus_node,
+ llnode);
+ source = estatus_node->source;
+ estatus = ESTATUS_FROM_NODE(estatus_node);
+ len = estatus_len(estatus);
+ node_len = ESTATUS_NODE_LEN(len);
+ estatus_do_proc(source, estatus);
+ if (!estatus_cached(estatus)) {
+ if (estatus_print_estatus(NULL, source, estatus))
+ estatus_cache_add(source, estatus);
+ }
+ gen_pool_free(estatus_pool,
+ (unsigned long)estatus_node, node_len);
+
+ llnode = next;
+ }
+}
+EXPORT_SYMBOL_GPL(estatus_proc_in_irq);
+
+static void estatus_print_queued_estatus(void)
+{
+ struct llist_node *llnode;
+ struct estatus_node *estatus_node;
+ struct estatus_source *source;
+ estatus_generic_status *estatus;
+
+ llnode = llist_del_all(&estatus_llist);
+ /*
+ * Because the time order of estatus in list is reversed,
+ * revert it back to proper order.
+ */
+ llnode = llist_reverse_order(llnode);
+ while (llnode) {
+ estatus_node = llist_entry(llnode, struct estatus_node,
+ llnode);
+ estatus = ESTATUS_FROM_NODE(estatus_node);
+ source = estatus_node->source;
+ estatus_print_estatus(NULL, source, estatus);
+ llnode = llnode->next;
+ }
+}
+
+void estatus_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
+{
+#if IS_ENABLED(CONFIG_ACPI_APEI_GHES)
+ arch_apei_report_mem_error(sev, mem_err);
+#endif
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 04/12] ras: add estatus queuing and IRQ/NMI handling
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (2 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 03/12] ras: add estatus vendor handling and processing Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 05/12] ras: flesh out estatus processing core Ahmed Tiba
` (8 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Hook the estatus core into the IRQ-work and NMI-safe queues that used to
live in GHES. The new code records CPER payloads into a per-source node,
ships them via irq_work so heavy processing happens out of the NMI path,
and adds the task_work hook used by memory-failure. Behaviour remains the
same as before, but the logic now lives alongside the rest of the estatus
infrastructure so both GHES and DeviceTree providers can reuse it.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/firmware/efi/estatus.c | 78 ++++++++++++++++++++++++++++++++++
include/linux/estatus.h | 3 +-
2 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
index 5a848d1b218e..8b62b23e2e93 100644
--- a/drivers/firmware/efi/estatus.c
+++ b/drivers/firmware/efi/estatus.c
@@ -973,3 +973,81 @@ void estatus_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
arch_apei_report_mem_error(sev, mem_err);
#endif
}
+
+int estatus_in_nmi_queue_one_entry(struct estatus_source *source, enum fixed_addresses fixmap_idx)
+{
+ estatus_generic_status *estatus, tmp_header;
+ struct estatus_node *estatus_node;
+ u32 len, node_len;
+ phys_addr_t buf_paddr;
+ int sev, rc;
+
+ if (!IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG))
+ return -EOPNOTSUPP;
+
+ rc = __estatus_peek_estatus(source, &tmp_header, &buf_paddr,
+ fixmap_idx);
+ if (rc) {
+ estatus_clear_estatus(source, &tmp_header, buf_paddr,
+ fixmap_idx);
+ return rc;
+ }
+
+ rc = __estatus_check_estatus(source, &tmp_header);
+ if (rc) {
+ estatus_clear_estatus(source, &tmp_header, buf_paddr,
+ fixmap_idx);
+ return rc;
+ }
+
+ len = estatus_len(&tmp_header);
+ node_len = ESTATUS_NODE_LEN(len);
+ estatus_node = (void *)gen_pool_alloc(estatus_pool, node_len);
+ if (!estatus_node)
+ return -ENOMEM;
+
+ estatus_node->source = source;
+ estatus = ESTATUS_FROM_NODE(estatus_node);
+
+ if (__estatus_read_estatus(source, estatus, buf_paddr, fixmap_idx,
+ len)) {
+ estatus_clear_estatus(source, estatus, buf_paddr, fixmap_idx);
+ rc = -ENOENT;
+ goto no_work;
+ }
+
+ sev = estatus_severity(estatus->error_severity);
+ if (sev >= ESTATUS_SEV_PANIC) {
+ estatus_print_queued_estatus();
+ __estatus_panic(source, estatus, buf_paddr, fixmap_idx);
+ }
+
+ estatus_clear_estatus(source, &tmp_header, buf_paddr, fixmap_idx);
+
+ /* This error has been reported before, don't process it again. */
+ if (estatus_cached(estatus))
+ goto no_work;
+
+ llist_add(&estatus_node->llnode, &estatus_llist);
+
+ return rc;
+
+no_work:
+ gen_pool_free(estatus_pool, (unsigned long)estatus_node,
+ node_len);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(estatus_in_nmi_queue_one_entry);
+
+void estatus_register_report_chain(struct notifier_block *nb)
+{
+ atomic_notifier_chain_register(&estatus_report_chain, nb);
+}
+EXPORT_SYMBOL_GPL(estatus_register_report_chain);
+
+void estatus_unregister_report_chain(struct notifier_block *nb)
+{
+ atomic_notifier_chain_unregister(&estatus_report_chain, nb);
+}
+EXPORT_SYMBOL_GPL(estatus_unregister_report_chain);
diff --git a/include/linux/estatus.h b/include/linux/estatus.h
index 002a9533c85a..506a74ad60b9 100644
--- a/include/linux/estatus.h
+++ b/include/linux/estatus.h
@@ -122,8 +122,7 @@ enum {
};
int estatus_proc(struct estatus_source *ghes);
-int estatus_in_nmi_queue_one_entry(struct estatus_source *ghes,
- enum fixed_addresses fixmap_idx);
+int estatus_in_nmi_queue_one_entry(struct estatus_source *ghes, enum fixed_addresses fixmap_idx);
void estatus_proc_in_irq(struct irq_work *irq_work);
/**
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 05/12] ras: flesh out estatus processing core
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (3 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 04/12] ras: add estatus queuing and IRQ/NMI handling Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 06/12] efi/cper: adopt estatus iteration helpers Ahmed Tiba
` (7 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Complete the estatus core by adding the cache throttling logic, vendor
record notifier plumbing, IRQ/NMI queue handling and the processing
paths that walk CPER records. Landing the full implementation ahead of
the GHES conversion keeps the follow-on patches focused on their call
sites while leaving behaviour unchanged for existing users.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/firmware/efi/estatus.c | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
index 8b62b23e2e93..8043d68f907b 100644
--- a/drivers/firmware/efi/estatus.c
+++ b/drivers/firmware/efi/estatus.c
@@ -32,6 +32,7 @@
void estatus_pool_region_free(unsigned long addr, u32 size);
+/* Emit a printk at the exact level encoded in the HW_ERR tag we build. */
static void estatus_log_hw_error(char level, const char *seq_tag,
const char *name)
{
@@ -704,7 +705,7 @@ static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bo
* ESTATUS_SEV_PANIC does not make it to this handling since the kernel must
* panic.
*/
-static void estatus_handle_aer(estatus_generic_data *gdata)
+static void estatus_handle_aer(struct acpi_hest_generic_data *gdata)
{
#ifdef CONFIG_ACPI_APEI_PCIEAER
struct cper_sec_pcie *pcie_err = estatus_get_payload(gdata);
@@ -759,7 +760,7 @@ EXPORT_SYMBOL_GPL(estatus_unregister_vendor_record_notifier);
static void estatus_vendor_record_work_func(struct work_struct *work)
{
struct estatus_vendor_record_entry *entry;
- estatus_generic_data *gdata;
+ struct acpi_hest_generic_data *gdata;
u32 len;
entry = container_of(work, struct estatus_vendor_record_entry, work);
@@ -774,7 +775,7 @@ static void estatus_vendor_record_work_func(struct work_struct *work)
static void estatus_defer_non_standard_event(estatus_generic_data *gdata, int sev)
{
- estatus_generic_data *copied_gdata;
+ struct acpi_hest_generic_data *copied_gdata;
struct estatus_vendor_record_entry *entry;
u32 len;
@@ -806,7 +807,7 @@ static inline bool estatus_is_sync_notify(struct estatus_source *source)
static void estatus_do_proc(struct estatus_source *source, const estatus_generic_status *estatus)
{
int sev, sec_sev;
- estatus_generic_data *gdata;
+ struct acpi_hest_generic_data *gdata;
guid_t *sec_type;
const guid_t *fru_id = &guid_null;
char *fru_text = "";
@@ -871,7 +872,7 @@ static void __estatus_panic(struct estatus_source *source, estatus_generic_statu
int estatus_proc(struct estatus_source *source)
{
- estatus_generic_status *estatus = source->estatus;
+ struct acpi_hest_generic_status *estatus = source->estatus;
phys_addr_t buf_paddr;
enum fixed_addresses fixmap_idx = estatus_source_fixmap(source);
int rc;
@@ -913,7 +914,7 @@ void estatus_proc_in_irq(struct irq_work *irq_work)
{
struct llist_node *llnode, *next;
struct estatus_node *estatus_node;
- struct estatus_source *source;
+ struct acpi_hest_generic_status *source;
estatus_generic_status *estatus;
u32 len, node_len;
@@ -927,7 +928,7 @@ void estatus_proc_in_irq(struct irq_work *irq_work)
next = llnode->next;
estatus_node = llist_entry(llnode, struct estatus_node,
llnode);
- source = estatus_node->source;
+ struct estatus_source *source = estatus_node->source;
estatus = ESTATUS_FROM_NODE(estatus_node);
len = estatus_len(estatus);
node_len = ESTATUS_NODE_LEN(len);
@@ -949,7 +950,7 @@ static void estatus_print_queued_estatus(void)
struct llist_node *llnode;
struct estatus_node *estatus_node;
struct estatus_source *source;
- estatus_generic_status *estatus;
+ struct acpi_hest_generic_status *estatus;
llnode = llist_del_all(&estatus_llist);
/*
@@ -976,7 +977,7 @@ void estatus_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
int estatus_in_nmi_queue_one_entry(struct estatus_source *source, enum fixed_addresses fixmap_idx)
{
- estatus_generic_status *estatus, tmp_header;
+ struct acpi_hest_generic_status *estatus, tmp_header;
struct estatus_node *estatus_node;
u32 len, node_len;
phys_addr_t buf_paddr;
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 06/12] efi/cper: adopt estatus iteration helpers
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (4 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 05/12] ras: flesh out estatus processing core Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 07/12] ghes: prepare estatus hooks for shared handling Ahmed Tiba
` (6 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Switch the CPER printer and validator to the estatus_*() accessor
wrappers introduced by the new core. This is a mechanical change that
replaces the local acpi_hest_*() helpers with their aliases and keeps
the section iteration code shared ahead of the GHES conversion.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/firmware/efi/cper.c | 29 +++++++++++++-------------
include/acpi/ghes.h | 41 ++-----------------------------------
2 files changed, 17 insertions(+), 53 deletions(-)
diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c
index 0232bd040f61..07119940486a 100644
--- a/drivers/firmware/efi/cper.c
+++ b/drivers/firmware/efi/cper.c
@@ -26,6 +26,7 @@
#include <acpi/ghes.h>
#include <ras/ras_event.h>
#include <cxl/event.h>
+#include <linux/estatus.h>
/*
* CPER record ID need to be unique even after reboot, because record
@@ -525,7 +526,7 @@ static void cper_print_fw_err(const char *pfx,
struct acpi_hest_generic_data *gdata,
const struct cper_sec_fw_err_rec_ref *fw_err)
{
- void *buf = acpi_hest_get_payload(gdata);
+ void *buf = estatus_get_payload(gdata);
u32 offset, length = gdata->error_data_length;
printk("%s""Firmware Error Record Type: %s\n", pfx,
@@ -607,7 +608,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
__u16 severity;
char newpfx[64];
- if (acpi_hest_get_version(gdata) >= 3)
+ if (estatus_get_version(gdata) >= 3)
cper_print_tstamp(pfx, (struct acpi_hest_generic_data_v300 *)gdata);
severity = gdata->error_severity;
@@ -628,7 +629,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
}
if (guid_equal(sec_type, &CPER_SEC_PROC_GENERIC)) {
- struct cper_sec_proc_generic *proc_err = acpi_hest_get_payload(gdata);
+ struct cper_sec_proc_generic *proc_err = estatus_get_payload(gdata);
printk("%s""section_type: general processor error\n", newpfx);
if (gdata->error_data_length >= sizeof(*proc_err))
@@ -636,7 +637,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
else
goto err_section_too_small;
} else if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
- struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
+ struct cper_sec_mem_err *mem_err = estatus_get_payload(gdata);
printk("%s""section_type: memory error\n", newpfx);
if (gdata->error_data_length >=
@@ -646,7 +647,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
else
goto err_section_too_small;
} else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
- struct cper_sec_pcie *pcie = acpi_hest_get_payload(gdata);
+ struct cper_sec_pcie *pcie = estatus_get_payload(gdata);
printk("%s""section_type: PCIe error\n", newpfx);
if (gdata->error_data_length >= sizeof(*pcie))
@@ -655,7 +656,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
goto err_section_too_small;
#if defined(CONFIG_ARM64) || defined(CONFIG_ARM)
} else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) {
- struct cper_sec_proc_arm *arm_err = acpi_hest_get_payload(gdata);
+ struct cper_sec_proc_arm *arm_err = estatus_get_payload(gdata);
printk("%ssection_type: ARM processor error\n", newpfx);
if (gdata->error_data_length >= sizeof(*arm_err))
@@ -665,7 +666,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
#endif
#if defined(CONFIG_UEFI_CPER_X86)
} else if (guid_equal(sec_type, &CPER_SEC_PROC_IA)) {
- struct cper_sec_proc_ia *ia_err = acpi_hest_get_payload(gdata);
+ struct cper_sec_proc_ia *ia_err = estatus_get_payload(gdata);
printk("%ssection_type: IA32/X64 processor error\n", newpfx);
if (gdata->error_data_length >= sizeof(*ia_err))
@@ -674,7 +675,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
goto err_section_too_small;
#endif
} else if (guid_equal(sec_type, &CPER_SEC_FW_ERR_REC_REF)) {
- struct cper_sec_fw_err_rec_ref *fw_err = acpi_hest_get_payload(gdata);
+ struct cper_sec_fw_err_rec_ref *fw_err = estatus_get_payload(gdata);
printk("%ssection_type: Firmware Error Record Reference\n",
newpfx);
@@ -684,7 +685,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
else
goto err_section_too_small;
} else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) {
- struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata);
+ struct cxl_cper_sec_prot_err *prot_err = estatus_get_payload(gdata);
printk("%ssection_type: CXL Protocol Error\n", newpfx);
if (gdata->error_data_length >= sizeof(*prot_err))
@@ -692,7 +693,7 @@ cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata
else
goto err_section_too_small;
} else {
- const void *err = acpi_hest_get_payload(gdata);
+ const void *err = estatus_get_payload(gdata);
printk("%ssection type: unknown, %pUl\n", newpfx, sec_type);
printk("%ssection length: %#x\n", newpfx,
@@ -723,7 +724,7 @@ void cper_estatus_print(const char *pfx,
printk("%s""event severity: %s\n", pfx, cper_severity_str(severity));
snprintf(newpfx, sizeof(newpfx), "%s ", pfx);
- apei_estatus_for_each_section(estatus, gdata) {
+ estatus_for_each_section(estatus, gdata) {
cper_estatus_print_section(newpfx, gdata, sec_no);
sec_no++;
}
@@ -755,11 +756,11 @@ int cper_estatus_check(const struct acpi_hest_generic_status *estatus)
data_len = estatus->data_length;
- apei_estatus_for_each_section(estatus, gdata) {
- if (acpi_hest_get_size(gdata) > data_len)
+ estatus_for_each_section(estatus, gdata) {
+ if (estatus_get_size(gdata) > data_len)
return -EINVAL;
- record_size = acpi_hest_get_record_size(gdata);
+ record_size = estatus_get_record_size(gdata);
if (record_size > data_len)
return -EINVAL;
diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h
index ebd21b05fe6e..022c0325f1e0 100644
--- a/include/acpi/ghes.h
+++ b/include/acpi/ghes.h
@@ -4,6 +4,7 @@
#include <acpi/apei.h>
#include <acpi/hed.h>
+#include <linux/estatus.h>
/*
* One struct ghes is created for each generic hardware error source.
@@ -80,46 +81,8 @@ static inline void ghes_estatus_pool_region_free(unsigned long addr, u32 size) {
int ghes_estatus_pool_init(unsigned int num_ghes);
-static inline int acpi_hest_get_version(struct acpi_hest_generic_data *gdata)
-{
- return gdata->revision >> 8;
-}
-
-static inline void *acpi_hest_get_payload(struct acpi_hest_generic_data *gdata)
-{
- if (acpi_hest_get_version(gdata) >= 3)
- return (void *)(((struct acpi_hest_generic_data_v300 *)(gdata)) + 1);
-
- return gdata + 1;
-}
-
-static inline int acpi_hest_get_error_length(struct acpi_hest_generic_data *gdata)
-{
- return ((struct acpi_hest_generic_data *)(gdata))->error_data_length;
-}
-
-static inline int acpi_hest_get_size(struct acpi_hest_generic_data *gdata)
-{
- if (acpi_hest_get_version(gdata) >= 3)
- return sizeof(struct acpi_hest_generic_data_v300);
-
- return sizeof(struct acpi_hest_generic_data);
-}
-
-static inline int acpi_hest_get_record_size(struct acpi_hest_generic_data *gdata)
-{
- return (acpi_hest_get_size(gdata) + acpi_hest_get_error_length(gdata));
-}
-
-static inline void *acpi_hest_get_next(struct acpi_hest_generic_data *gdata)
-{
- return (void *)(gdata) + acpi_hest_get_record_size(gdata);
-}
-
#define apei_estatus_for_each_section(estatus, section) \
- for (section = (struct acpi_hest_generic_data *)(estatus + 1); \
- (void *)section - (void *)(estatus + 1) < estatus->data_length; \
- section = acpi_hest_get_next(section))
+ estatus_for_each_section(estatus, section)
#ifdef CONFIG_ACPI_APEI_SEA
int ghes_notify_sea(void);
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 07/12] ghes: prepare estatus hooks for shared handling
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (5 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 06/12] efi/cper: adopt estatus iteration helpers Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 08/12] ghes: add estatus provider ops Ahmed Tiba
` (5 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Introduce struct estatus_source inside struct ghes and export thin
wrappers around the existing GHES estatus helpers. These helpers are
not ACPI-specific, so moving them behind a common interface is the
first step toward reusing the logic for other firmware providers.
Behaviour stays the same for GHES in this patch.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/acpi/apei/Kconfig | 1 +
drivers/firmware/efi/estatus.c | 3 +--
include/acpi/ghes.h | 17 +++++++++++++----
3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig
index 070c07d68dfb..90cb8360988b 100644
--- a/drivers/acpi/apei/Kconfig
+++ b/drivers/acpi/apei/Kconfig
@@ -24,6 +24,7 @@ config ACPI_APEI_GHES
select IRQ_WORK
select GENERIC_ALLOCATOR
select ARM_SDE_INTERFACE if ARM64
+ select RAS_ESTATUS_CORE
help
Generic Hardware Error Source provides a way to report
platform hardware errors (such as that from chipset). It
diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
index 8043d68f907b..259122730303 100644
--- a/drivers/firmware/efi/estatus.c
+++ b/drivers/firmware/efi/estatus.c
@@ -654,9 +654,8 @@ static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bo
int sec_sev, i;
char *p;
- log_arm_hw_error(err);
-
sec_sev = estatus_severity(gdata->error_severity);
+ log_arm_hw_error(err, sec_sev);
if (sev != ESTATUS_SEV_RECOVERABLE || sec_sev != ESTATUS_SEV_RECOVERABLE)
return false;
diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h
index 022c0325f1e0..7dc6acde3e2e 100644
--- a/include/acpi/ghes.h
+++ b/include/acpi/ghes.h
@@ -5,6 +5,7 @@
#include <acpi/apei.h>
#include <acpi/hed.h>
#include <linux/estatus.h>
+#include <linux/notifier.h>
/*
* One struct ghes is created for each generic hardware error source.
@@ -22,6 +23,7 @@ struct ghes {
struct acpi_hest_generic_v2 *generic_v2;
};
struct acpi_hest_generic_status *estatus;
+ struct estatus_source estatus_src;
unsigned long flags;
union {
struct list_head list;
@@ -30,6 +32,7 @@ struct ghes {
};
struct device *dev;
struct list_head elist;
+ char name[32];
};
struct ghes_estatus_node {
@@ -47,10 +50,10 @@ struct ghes_estatus_cache {
};
enum {
- GHES_SEV_NO = 0x0,
- GHES_SEV_CORRECTED = 0x1,
- GHES_SEV_RECOVERABLE = 0x2,
- GHES_SEV_PANIC = 0x3,
+ GHES_SEV_NO = ESTATUS_SEV_NO,
+ GHES_SEV_CORRECTED = ESTATUS_SEV_CORRECTED,
+ GHES_SEV_RECOVERABLE = ESTATUS_SEV_RECOVERABLE,
+ GHES_SEV_PANIC = ESTATUS_SEV_PANIC,
};
#ifdef CONFIG_ACPI_APEI_GHES
@@ -73,6 +76,12 @@ void ghes_unregister_vendor_record_notifier(struct notifier_block *nb);
struct list_head *ghes_get_devices(void);
void ghes_estatus_pool_region_free(unsigned long addr, u32 size);
+int ghes_register_vendor_record_notifier(struct notifier_block *nb);
+void ghes_unregister_vendor_record_notifier(struct notifier_block *nb);
+void ghes_register_report_chain(struct notifier_block *nb);
+void ghes_unregister_report_chain(struct notifier_block *nb);
+
+void estatus_pool_region_free(unsigned long addr, u32 size);
#else
static inline struct list_head *ghes_get_devices(void) { return NULL; }
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 08/12] ghes: add estatus provider ops
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (6 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 07/12] ghes: prepare estatus hooks for shared handling Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 09/12] ghes: route error handling through shared estatus core Ahmed Tiba
` (4 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Add the estatus_ops implementation that lets GHES present its CPER buffer
through the new core. The helper plugs the existing fixmap read/write code
into the generic callbacks, wires up the acknowledgment path for GHESv2
sources, and exposes the source name/notification attributes. No behaviour
changes yet—the next patch switches the GHES paths over to these hooks.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/acpi/apei/ghes.c | 157 +++++++++++++++++++++++++++++++++++++++
1 file changed, 157 insertions(+)
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 0dc767392a6c..5932542ea942 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -53,6 +53,7 @@
#include <asm/tlbflush.h>
#include <cxl/event.h>
#include <ras/ras_event.h>
+#include <linux/estatus.h>
#include "apei-internal.h"
@@ -250,6 +251,8 @@ static void unmap_gen_v2(struct ghes *ghes)
apei_unmap_generic_address(&ghes->generic_v2->read_ack_register);
}
+static const struct estatus_ops ghes_estatus_ops;
+
static void ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
{
int rc;
@@ -276,6 +279,8 @@ static struct ghes *ghes_new(struct acpi_hest_generic *generic)
return ERR_PTR(-ENOMEM);
ghes->generic = generic;
+ scnprintf(ghes->name, sizeof(ghes->name), "GHES source %d",
+ generic->header.source_id);
if (is_hest_type_generic_v2(ghes)) {
rc = map_gen_v2(ghes);
if (rc)
@@ -299,6 +304,12 @@ static struct ghes *ghes_new(struct acpi_hest_generic *generic)
goto err_unmap_status_addr;
}
+ ghes->estatus_src.ops = &ghes_estatus_ops;
+ ghes->estatus_src.priv = ghes;
+ ghes->estatus_src.estatus = ghes->estatus;
+ ghes->estatus_src.fixmap_idx = FIX_APEI_GHES_IRQ;
+ INIT_LIST_HEAD(&ghes->elist);
+
return ghes;
err_unmap_status_addr:
@@ -1844,3 +1855,149 @@ void ghes_unregister_report_chain(struct notifier_block *nb)
atomic_notifier_chain_unregister(&ghes_report_chain, nb);
}
EXPORT_SYMBOL_GPL(ghes_unregister_report_chain);
+
+#define GHES_ESTATUS_RW_FROM_PHYS 1
+#define GHES_ESTATUS_RW_TO_PHYS 0
+
+static void __iomem *ghes_estatus_map(struct ghes *ghes, u64 pfn,
+ enum fixed_addresses fixmap_idx)
+{
+ phys_addr_t paddr = PFN_PHYS(pfn);
+ pgprot_t prot = arch_apei_get_mem_attribute(paddr);
+
+ __set_fixmap(fixmap_idx, paddr, prot);
+
+ return (void __iomem *)__fix_to_virt(fixmap_idx);
+}
+
+static void ghes_estatus_unmap(void __iomem *vaddr,
+ enum fixed_addresses fixmap_idx)
+{
+ int _idx = virt_to_fix((unsigned long)vaddr);
+
+ WARN_ON_ONCE(fixmap_idx != _idx);
+ clear_fixmap(fixmap_idx);
+}
+
+static void ghes_estatus_copy_buffer(struct ghes *ghes, void *buffer,
+ phys_addr_t paddr, size_t len,
+ int from_phys,
+ enum fixed_addresses fixmap_idx)
+{
+ void __iomem *vaddr;
+ u64 offset;
+ u32 chunk;
+
+ while (len > 0) {
+ offset = paddr - (paddr & PAGE_MASK);
+ vaddr = ghes_estatus_map(ghes, PHYS_PFN(paddr), fixmap_idx);
+ chunk = PAGE_SIZE - offset;
+ chunk = min_t(u32, chunk, len);
+ if (from_phys == GHES_ESTATUS_RW_FROM_PHYS)
+ memcpy_fromio(buffer, vaddr + offset, chunk);
+ else
+ memcpy_toio(vaddr + offset, buffer, chunk);
+
+ len -= chunk;
+ paddr += chunk;
+ buffer += chunk;
+ ghes_estatus_unmap(vaddr, fixmap_idx);
+ }
+}
+
+static int ghes_estatus_get_phys(struct estatus_source *source,
+ phys_addr_t *addr)
+{
+ struct ghes *ghes = source->priv;
+ u64 value = 0;
+ int rc;
+
+ rc = apei_read(&value, &ghes->generic->error_status_address);
+ if (rc)
+ return rc;
+
+ *addr = value;
+ return 0;
+}
+
+static int ghes_estatus_read(struct estatus_source *source, phys_addr_t addr,
+ void *buf, size_t len,
+ enum fixed_addresses fixmap_idx)
+{
+ struct ghes *ghes = source->priv;
+
+ ghes_estatus_copy_buffer(ghes, buf, addr, len,
+ GHES_ESTATUS_RW_FROM_PHYS, fixmap_idx);
+ return 0;
+}
+
+static int ghes_estatus_write(struct estatus_source *source, phys_addr_t addr,
+ const void *buf, size_t len,
+ enum fixed_addresses fixmap_idx)
+{
+ struct ghes *ghes = source->priv;
+
+ ghes_estatus_copy_buffer(ghes, (void *)buf, addr, len,
+ GHES_ESTATUS_RW_TO_PHYS, fixmap_idx);
+ return 0;
+}
+
+static void ghes_estatus_ack(struct estatus_source *source)
+{
+ struct ghes *ghes = source->priv;
+ struct acpi_hest_generic_v2 *gv2;
+ u64 val = 0;
+ int rc;
+
+ if (ghes->generic->header.type != ACPI_HEST_TYPE_GENERIC_ERROR_V2)
+ return;
+
+ gv2 = ghes->generic_v2;
+ rc = apei_read(&val, &gv2->read_ack_register);
+ if (rc)
+ return;
+
+ val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset;
+ val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset;
+
+ apei_write(val, &gv2->read_ack_register);
+}
+
+static size_t ghes_estatus_get_max_len(struct estatus_source *source)
+{
+ struct ghes *ghes = source->priv;
+
+ return ghes->generic->error_block_length;
+}
+
+static enum estatus_notify_mode
+ghes_estatus_get_notify_mode(struct estatus_source *source)
+{
+ struct ghes *ghes = source->priv;
+ u8 notify_type = ghes->generic->notify.type;
+
+ if (notify_type == ACPI_HEST_NOTIFY_SEA)
+ return ESTATUS_NOTIFY_SEA;
+
+ return ESTATUS_NOTIFY_ASYNC;
+}
+
+static const char *ghes_estatus_get_name(struct estatus_source *source)
+{
+ struct ghes *ghes = source->priv;
+
+ if (ghes->dev)
+ return dev_name(ghes->dev);
+
+ return ghes->name;
+}
+
+static const struct estatus_ops ghes_estatus_ops = {
+ .get_phys = ghes_estatus_get_phys,
+ .read = ghes_estatus_read,
+ .write = ghes_estatus_write,
+ .ack = ghes_estatus_ack,
+ .get_max_len = ghes_estatus_get_max_len,
+ .get_notify_mode = ghes_estatus_get_notify_mode,
+ .get_name = ghes_estatus_get_name,
+};
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 09/12] ghes: route error handling through shared estatus core
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (7 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 08/12] ghes: add estatus provider ops Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 10/12] dt-bindings: ras: document estatus provider Ahmed Tiba
` (3 subsequent siblings)
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Switch GHES over to the shared estatus. GHES call the common helpers.
This shrinks ghes.c considerably
while keeping runtime behaviour identical, and it allows other
firmware-first providers to reuse exactly the same processing logic.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
drivers/acpi/apei/ghes.c | 1137 +-------------------------------
drivers/firmware/efi/estatus.c | 55 +-
2 files changed, 43 insertions(+), 1149 deletions(-)
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 5932542ea942..8a9fb9d4ffda 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -60,36 +60,6 @@
#define GHES_PFX "GHES: "
#define GHES_ESTATUS_MAX_SIZE 65536
-#define GHES_ESOURCE_PREALLOC_MAX_SIZE 65536
-
-#define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER 3
-
-/* This is just an estimation for memory pool allocation */
-#define GHES_ESTATUS_CACHE_AVG_SIZE 512
-
-#define GHES_ESTATUS_CACHES_SIZE 4
-
-#define GHES_ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL
-/* Prevent too many caches are allocated because of RCU */
-#define GHES_ESTATUS_CACHE_ALLOCED_MAX (GHES_ESTATUS_CACHES_SIZE * 3 / 2)
-
-#define GHES_ESTATUS_CACHE_LEN(estatus_len) \
- (sizeof(struct ghes_estatus_cache) + (estatus_len))
-#define GHES_ESTATUS_FROM_CACHE(estatus_cache) \
- ((struct acpi_hest_generic_status *) \
- ((struct ghes_estatus_cache *)(estatus_cache) + 1))
-
-#define GHES_ESTATUS_NODE_LEN(estatus_len) \
- (sizeof(struct ghes_estatus_node) + (estatus_len))
-#define GHES_ESTATUS_FROM_NODE(estatus_node) \
- ((struct acpi_hest_generic_status *) \
- ((struct ghes_estatus_node *)(estatus_node) + 1))
-
-#define GHES_VENDOR_ENTRY_LEN(gdata_len) \
- (sizeof(struct ghes_vendor_record_entry) + (gdata_len))
-#define GHES_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \
- ((struct acpi_hest_generic_data *) \
- ((struct ghes_vendor_record_entry *)(vendor_entry) + 1))
/*
* NMI-like notifications vary by architecture, before the compiler can prune
@@ -100,27 +70,11 @@
#define FIX_APEI_GHES_SDEI_CRITICAL __end_of_fixed_addresses
#endif
-static ATOMIC_NOTIFIER_HEAD(ghes_report_chain);
-
static inline bool is_hest_type_generic_v2(struct ghes *ghes)
{
return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;
}
-/*
- * A platform may describe one error source for the handling of synchronous
- * errors (e.g. MCE or SEA), or for handling asynchronous errors (e.g. SCI
- * or External Interrupt). On x86, the HEST notifications are always
- * asynchronous, so only SEA on ARM is delivered as a synchronous
- * notification.
- */
-static inline bool is_hest_sync_notify(struct ghes *ghes)
-{
- u8 notify_type = ghes->generic->notify.type;
-
- return notify_type == ACPI_HEST_NOTIFY_SEA;
-}
-
/*
* This driver isn't really modular, however for the time being,
* continuing to use module_param is the easiest way to remain
@@ -165,67 +119,11 @@ static DEFINE_MUTEX(ghes_devs_mutex);
*/
static DEFINE_SPINLOCK(ghes_notify_lock_irq);
-struct ghes_vendor_record_entry {
- struct work_struct work;
- int error_severity;
- char vendor_record[];
-};
-
-static struct gen_pool *ghes_estatus_pool;
-
-static struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
-static atomic_t ghes_estatus_cache_alloced;
-
-static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
-{
- phys_addr_t paddr;
- pgprot_t prot;
-
- paddr = PFN_PHYS(pfn);
- prot = arch_apei_get_mem_attribute(paddr);
- __set_fixmap(fixmap_idx, paddr, prot);
-
- return (void __iomem *) __fix_to_virt(fixmap_idx);
-}
-
-static void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx)
-{
- int _idx = virt_to_fix((unsigned long)vaddr);
-
- WARN_ON_ONCE(fixmap_idx != _idx);
- clear_fixmap(fixmap_idx);
-}
-
int ghes_estatus_pool_init(unsigned int num_ghes)
{
- unsigned long addr, len;
- int rc;
-
- ghes_estatus_pool = gen_pool_create(GHES_ESTATUS_POOL_MIN_ALLOC_ORDER, -1);
- if (!ghes_estatus_pool)
- return -ENOMEM;
-
- len = GHES_ESTATUS_CACHE_AVG_SIZE * GHES_ESTATUS_CACHE_ALLOCED_MAX;
- len += (num_ghes * GHES_ESOURCE_PREALLOC_MAX_SIZE);
-
- addr = (unsigned long)vmalloc(PAGE_ALIGN(len));
- if (!addr)
- goto err_pool_alloc;
-
- rc = gen_pool_add(ghes_estatus_pool, addr, PAGE_ALIGN(len), -1);
- if (rc)
- goto err_pool_add;
-
- return 0;
-
-err_pool_add:
- vfree((void *)addr);
-
-err_pool_alloc:
- gen_pool_destroy(ghes_estatus_pool);
-
- return -ENOMEM;
+ return estatus_pool_init(num_ghes);
}
+EXPORT_SYMBOL_GPL(ghes_estatus_pool_init);
/**
* ghes_estatus_pool_region_free - free previously allocated memory
@@ -237,7 +135,7 @@ int ghes_estatus_pool_init(unsigned int num_ghes)
*/
void ghes_estatus_pool_region_free(unsigned long addr, u32 size)
{
- gen_pool_free(ghes_estatus_pool, addr, size);
+ estatus_pool_region_free(addr, size);
}
EXPORT_SYMBOL_GPL(ghes_estatus_pool_region_free);
@@ -253,21 +151,6 @@ static void unmap_gen_v2(struct ghes *ghes)
static const struct estatus_ops ghes_estatus_ops;
-static void ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
-{
- int rc;
- u64 val = 0;
-
- rc = apei_read(&val, &gv2->read_ack_register);
- if (rc)
- return;
-
- val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset;
- val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset;
-
- apei_write(val, &gv2->read_ack_register);
-}
-
static struct ghes *ghes_new(struct acpi_hest_generic *generic)
{
struct ghes *ghes;
@@ -329,876 +212,18 @@ static void ghes_fini(struct ghes *ghes)
if (is_hest_type_generic_v2(ghes))
unmap_gen_v2(ghes);
}
-
-static inline int ghes_severity(int severity)
-{
- switch (severity) {
- case CPER_SEV_INFORMATIONAL:
- return GHES_SEV_NO;
- case CPER_SEV_CORRECTED:
- return GHES_SEV_CORRECTED;
- case CPER_SEV_RECOVERABLE:
- return GHES_SEV_RECOVERABLE;
- case CPER_SEV_FATAL:
- return GHES_SEV_PANIC;
- default:
- /* Unknown, go panic */
- return GHES_SEV_PANIC;
- }
-}
-
-static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
- int from_phys,
- enum fixed_addresses fixmap_idx)
-{
- void __iomem *vaddr;
- u64 offset;
- u32 trunk;
-
- while (len > 0) {
- offset = paddr - (paddr & PAGE_MASK);
- vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx);
- trunk = PAGE_SIZE - offset;
- trunk = min(trunk, len);
- if (from_phys)
- memcpy_fromio(buffer, vaddr + offset, trunk);
- else
- memcpy_toio(vaddr + offset, buffer, trunk);
- len -= trunk;
- paddr += trunk;
- buffer += trunk;
- ghes_unmap(vaddr, fixmap_idx);
- }
-}
-
-/* Check the top-level record header has an appropriate size. */
-static int __ghes_check_estatus(struct ghes *ghes,
- struct acpi_hest_generic_status *estatus)
-{
- u32 len = cper_estatus_len(estatus);
-
- if (len < sizeof(*estatus)) {
- pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status block!\n");
- return -EIO;
- }
-
- if (len > ghes->generic->error_block_length) {
- pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status block length!\n");
- return -EIO;
- }
-
- if (cper_estatus_check_header(estatus)) {
- pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n");
- return -EIO;
- }
-
- return 0;
-}
-
-/* Read the CPER block, returning its address, and header in estatus. */
-static int __ghes_peek_estatus(struct ghes *ghes,
- struct acpi_hest_generic_status *estatus,
- u64 *buf_paddr, enum fixed_addresses fixmap_idx)
-{
- struct acpi_hest_generic *g = ghes->generic;
- int rc;
-
- rc = apei_read(buf_paddr, &g->error_status_address);
- if (rc) {
- *buf_paddr = 0;
- pr_warn_ratelimited(FW_WARN GHES_PFX
-"Failed to read error status block address for hardware error source: %d.\n",
- g->header.source_id);
- return -EIO;
- }
- if (!*buf_paddr)
- return -ENOENT;
-
- ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1,
- fixmap_idx);
- if (!estatus->block_status) {
- *buf_paddr = 0;
- return -ENOENT;
- }
-
- return 0;
-}
-
-static int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
- u64 buf_paddr, enum fixed_addresses fixmap_idx,
- size_t buf_len)
-{
- ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx);
- if (cper_estatus_check(estatus)) {
- pr_warn_ratelimited(FW_WARN GHES_PFX
- "Failed to read error status block!\n");
- return -EIO;
- }
-
- return 0;
-}
-
-static int ghes_read_estatus(struct ghes *ghes,
- struct acpi_hest_generic_status *estatus,
- u64 *buf_paddr, enum fixed_addresses fixmap_idx)
-{
- int rc;
-
- rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx);
- if (rc)
- return rc;
-
- rc = __ghes_check_estatus(ghes, estatus);
- if (rc)
- return rc;
-
- return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx,
- cper_estatus_len(estatus));
-}
-
-static void ghes_clear_estatus(struct ghes *ghes,
- struct acpi_hest_generic_status *estatus,
- u64 buf_paddr, enum fixed_addresses fixmap_idx)
-{
- estatus->block_status = 0;
-
- if (!buf_paddr)
- return;
-
- ghes_copy_tofrom_phys(estatus, buf_paddr,
- sizeof(estatus->block_status), 0,
- fixmap_idx);
-
- /*
- * GHESv2 type HEST entries introduce support for error acknowledgment,
- * so only acknowledge the error if this support is present.
- */
- if (is_hest_type_generic_v2(ghes))
- ghes_ack_error(ghes->generic_v2);
-}
-
-/**
- * struct ghes_task_work - for synchronous RAS event
- *
- * @twork: callback_head for task work
- * @pfn: page frame number of corrupted page
- * @flags: work control flags
- *
- * Structure to pass task work to be handled before
- * returning to user-space via task_work_add().
- */
-struct ghes_task_work {
- struct callback_head twork;
- u64 pfn;
- int flags;
-};
-
-static void memory_failure_cb(struct callback_head *twork)
-{
- struct ghes_task_work *twcb = container_of(twork, struct ghes_task_work, twork);
- int ret;
-
- ret = memory_failure(twcb->pfn, twcb->flags);
- gen_pool_free(ghes_estatus_pool, (unsigned long)twcb, sizeof(*twcb));
-
- if (!ret || ret == -EHWPOISON || ret == -EOPNOTSUPP)
- return;
-
- pr_err("%#llx: Sending SIGBUS to %s:%d due to hardware memory corruption\n",
- twcb->pfn, current->comm, task_pid_nr(current));
- force_sig(SIGBUS);
-}
-
-static bool ghes_do_memory_failure(u64 physical_addr, int flags)
-{
- struct ghes_task_work *twcb;
- unsigned long pfn;
-
- if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
- return false;
-
- pfn = PHYS_PFN(physical_addr);
-
- if (flags == MF_ACTION_REQUIRED && current->mm) {
- twcb = (void *)gen_pool_alloc(ghes_estatus_pool, sizeof(*twcb));
- if (!twcb)
- return false;
-
- twcb->pfn = pfn;
- twcb->flags = flags;
- init_task_work(&twcb->twork, memory_failure_cb);
- task_work_add(current, &twcb->twork, TWA_RESUME);
- return true;
- }
-
- memory_failure_queue(pfn, flags);
- return true;
-}
-
-static bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata,
- int sev, bool sync)
-{
- int flags = -1;
- int sec_sev = ghes_severity(gdata->error_severity);
- struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
-
- if (!(mem_err->validation_bits & CPER_MEM_VALID_PA))
- return false;
-
- /* iff following two events can be handled properly by now */
- if (sec_sev == GHES_SEV_CORRECTED &&
- (gdata->flags & CPER_SEC_ERROR_THRESHOLD_EXCEEDED))
- flags = MF_SOFT_OFFLINE;
- if (sev == GHES_SEV_RECOVERABLE && sec_sev == GHES_SEV_RECOVERABLE)
- flags = sync ? MF_ACTION_REQUIRED : 0;
-
- if (flags != -1)
- return ghes_do_memory_failure(mem_err->physical_addr, flags);
-
- return false;
-}
-
-static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata,
- int sev, bool sync)
-{
- struct cper_sec_proc_arm *err = acpi_hest_get_payload(gdata);
- int flags = sync ? MF_ACTION_REQUIRED : 0;
- char error_type[120];
- bool queued = false;
- int sec_sev, i;
- char *p;
-
- sec_sev = ghes_severity(gdata->error_severity);
- log_arm_hw_error(err, sec_sev);
- if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE)
- return false;
-
- p = (char *)(err + 1);
- for (i = 0; i < err->err_info_num; i++) {
- struct cper_arm_err_info *err_info = (struct cper_arm_err_info *)p;
- bool is_cache = err_info->type & CPER_ARM_CACHE_ERROR;
- bool has_pa = (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR);
-
- /*
- * The field (err_info->error_info & BIT(26)) is fixed to set to
- * 1 in some old firmware of HiSilicon Kunpeng920. We assume that
- * firmware won't mix corrected errors in an uncorrected section,
- * and don't filter out 'corrected' error here.
- */
- if (is_cache && has_pa) {
- queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags);
- p += err_info->length;
- continue;
- }
-
- cper_bits_to_str(error_type, sizeof(error_type),
- FIELD_GET(CPER_ARM_ERR_TYPE_MASK, err_info->type),
- cper_proc_error_type_strs,
- ARRAY_SIZE(cper_proc_error_type_strs));
-
- pr_warn_ratelimited(FW_WARN GHES_PFX
- "Unhandled processor error type 0x%02x: %s%s\n",
- err_info->type, error_type,
- (err_info->type & ~CPER_ARM_ERR_TYPE_MASK) ? " with reserved bit(s)" : "");
- p += err_info->length;
- }
-
- return queued;
-}
-
-/*
- * PCIe AER errors need to be sent to the AER driver for reporting and
- * recovery. The GHES severities map to the following AER severities and
- * require the following handling:
- *
- * GHES_SEV_CORRECTABLE -> AER_CORRECTABLE
- * These need to be reported by the AER driver but no recovery is
- * necessary.
- * GHES_SEV_RECOVERABLE -> AER_NONFATAL
- * GHES_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL
- * These both need to be reported and recovered from by the AER driver.
- * GHES_SEV_PANIC does not make it to this handling since the kernel must
- * panic.
- */
-static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
-{
-#ifdef CONFIG_ACPI_APEI_PCIEAER
- struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata);
-
- if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID &&
- pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) {
- unsigned int devfn;
- int aer_severity;
- u8 *aer_info;
-
- devfn = PCI_DEVFN(pcie_err->device_id.device,
- pcie_err->device_id.function);
- aer_severity = cper_severity_to_aer(gdata->error_severity);
-
- /*
- * If firmware reset the component to contain
- * the error, we must reinitialize it before
- * use, so treat it as a fatal AER error.
- */
- if (gdata->flags & CPER_SEC_RESET)
- aer_severity = AER_FATAL;
-
- aer_info = (void *)gen_pool_alloc(ghes_estatus_pool,
- sizeof(struct aer_capability_regs));
- if (!aer_info)
- return;
- memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs));
-
- aer_recover_queue(pcie_err->device_id.segment,
- pcie_err->device_id.bus,
- devfn, aer_severity,
- (struct aer_capability_regs *)
- aer_info);
- }
-#endif
-}
-
-static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list);
-
int ghes_register_vendor_record_notifier(struct notifier_block *nb)
{
- return blocking_notifier_chain_register(&vendor_record_notify_list, nb);
+ return estatus_register_vendor_record_notifier(nb);
}
EXPORT_SYMBOL_GPL(ghes_register_vendor_record_notifier);
void ghes_unregister_vendor_record_notifier(struct notifier_block *nb)
{
- blocking_notifier_chain_unregister(&vendor_record_notify_list, nb);
+ estatus_unregister_vendor_record_notifier(nb);
}
EXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier);
-static void ghes_vendor_record_work_func(struct work_struct *work)
-{
- struct ghes_vendor_record_entry *entry;
- struct acpi_hest_generic_data *gdata;
- u32 len;
-
- entry = container_of(work, struct ghes_vendor_record_entry, work);
- gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry);
-
- blocking_notifier_call_chain(&vendor_record_notify_list,
- entry->error_severity, gdata);
-
- len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata));
- gen_pool_free(ghes_estatus_pool, (unsigned long)entry, len);
-}
-
-static void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
- int sev)
-{
- struct acpi_hest_generic_data *copied_gdata;
- struct ghes_vendor_record_entry *entry;
- u32 len;
-
- len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata));
- entry = (void *)gen_pool_alloc(ghes_estatus_pool, len);
- if (!entry)
- return;
-
- copied_gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry);
- memcpy(copied_gdata, gdata, acpi_hest_get_record_size(gdata));
- entry->error_severity = sev;
-
- INIT_WORK(&entry->work, ghes_vendor_record_work_func);
- schedule_work(&entry->work);
-}
-
-/* Room for 8 entries */
-#define CXL_CPER_PROT_ERR_FIFO_DEPTH 8
-static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data,
- CXL_CPER_PROT_ERR_FIFO_DEPTH);
-
-/* Synchronize schedule_work() with cxl_cper_prot_err_work changes */
-static DEFINE_SPINLOCK(cxl_cper_prot_err_work_lock);
-struct work_struct *cxl_cper_prot_err_work;
-
-static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err,
- int severity)
-{
-#ifdef CONFIG_ACPI_APEI_PCIEAER
- struct cxl_cper_prot_err_work_data wd;
- u8 *dvsec_start, *cap_start;
-
- if (!(prot_err->valid_bits & PROT_ERR_VALID_AGENT_ADDRESS)) {
- pr_err_ratelimited("CXL CPER invalid agent type\n");
- return;
- }
-
- if (!(prot_err->valid_bits & PROT_ERR_VALID_ERROR_LOG)) {
- pr_err_ratelimited("CXL CPER invalid protocol error log\n");
- return;
- }
-
- if (prot_err->err_len != sizeof(struct cxl_ras_capability_regs)) {
- pr_err_ratelimited("CXL CPER invalid RAS Cap size (%u)\n",
- prot_err->err_len);
- return;
- }
-
- if (!(prot_err->valid_bits & PROT_ERR_VALID_SERIAL_NUMBER))
- pr_warn(FW_WARN "CXL CPER no device serial number\n");
-
- guard(spinlock_irqsave)(&cxl_cper_prot_err_work_lock);
-
- if (!cxl_cper_prot_err_work)
- return;
-
- switch (prot_err->agent_type) {
- case RCD:
- case DEVICE:
- case LD:
- case FMLD:
- case RP:
- case DSP:
- case USP:
- memcpy(&wd.prot_err, prot_err, sizeof(wd.prot_err));
-
- dvsec_start = (u8 *)(prot_err + 1);
- cap_start = dvsec_start + prot_err->dvsec_len;
-
- memcpy(&wd.ras_cap, cap_start, sizeof(wd.ras_cap));
- wd.severity = cper_severity_to_aer(severity);
- break;
- default:
- pr_err_ratelimited("CXL CPER invalid agent type: %d\n",
- prot_err->agent_type);
- return;
- }
-
- if (!kfifo_put(&cxl_cper_prot_err_fifo, wd)) {
- pr_err_ratelimited("CXL CPER kfifo overflow\n");
- return;
- }
-
- schedule_work(cxl_cper_prot_err_work);
-#endif
-}
-
-int cxl_cper_register_prot_err_work(struct work_struct *work)
-{
- if (cxl_cper_prot_err_work)
- return -EINVAL;
-
- guard(spinlock)(&cxl_cper_prot_err_work_lock);
- cxl_cper_prot_err_work = work;
- return 0;
-}
-EXPORT_SYMBOL_NS_GPL(cxl_cper_register_prot_err_work, "CXL");
-
-int cxl_cper_unregister_prot_err_work(struct work_struct *work)
-{
- if (cxl_cper_prot_err_work != work)
- return -EINVAL;
-
- guard(spinlock)(&cxl_cper_prot_err_work_lock);
- cxl_cper_prot_err_work = NULL;
- return 0;
-}
-EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_prot_err_work, "CXL");
-
-int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd)
-{
- return kfifo_get(&cxl_cper_prot_err_fifo, wd);
-}
-EXPORT_SYMBOL_NS_GPL(cxl_cper_prot_err_kfifo_get, "CXL");
-
-/* Room for 8 entries for each of the 4 event log queues */
-#define CXL_CPER_FIFO_DEPTH 32
-DEFINE_KFIFO(cxl_cper_fifo, struct cxl_cper_work_data, CXL_CPER_FIFO_DEPTH);
-
-/* Synchronize schedule_work() with cxl_cper_work changes */
-static DEFINE_SPINLOCK(cxl_cper_work_lock);
-struct work_struct *cxl_cper_work;
-
-static void cxl_cper_post_event(enum cxl_event_type event_type,
- struct cxl_cper_event_rec *rec)
-{
- struct cxl_cper_work_data wd;
-
- if (rec->hdr.length <= sizeof(rec->hdr) ||
- rec->hdr.length > sizeof(*rec)) {
- pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n",
- rec->hdr.length);
- return;
- }
-
- if (!(rec->hdr.validation_bits & CPER_CXL_COMP_EVENT_LOG_VALID)) {
- pr_err(FW_WARN "CXL CPER invalid event\n");
- return;
- }
-
- guard(spinlock_irqsave)(&cxl_cper_work_lock);
-
- if (!cxl_cper_work)
- return;
-
- wd.event_type = event_type;
- memcpy(&wd.rec, rec, sizeof(wd.rec));
-
- if (!kfifo_put(&cxl_cper_fifo, wd)) {
- pr_err_ratelimited("CXL CPER kfifo overflow\n");
- return;
- }
-
- schedule_work(cxl_cper_work);
-}
-
-int cxl_cper_register_work(struct work_struct *work)
-{
- if (cxl_cper_work)
- return -EINVAL;
-
- guard(spinlock)(&cxl_cper_work_lock);
- cxl_cper_work = work;
- return 0;
-}
-EXPORT_SYMBOL_NS_GPL(cxl_cper_register_work, "CXL");
-
-int cxl_cper_unregister_work(struct work_struct *work)
-{
- if (cxl_cper_work != work)
- return -EINVAL;
-
- guard(spinlock)(&cxl_cper_work_lock);
- cxl_cper_work = NULL;
- return 0;
-}
-EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_work, "CXL");
-
-int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd)
-{
- return kfifo_get(&cxl_cper_fifo, wd);
-}
-EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL");
-
-static void ghes_log_hwerr(int sev, guid_t *sec_type)
-{
- if (sev != CPER_SEV_RECOVERABLE)
- return;
-
- if (guid_equal(sec_type, &CPER_SEC_PROC_ARM) ||
- guid_equal(sec_type, &CPER_SEC_PROC_GENERIC) ||
- guid_equal(sec_type, &CPER_SEC_PROC_IA)) {
- hwerr_log_error_type(HWERR_RECOV_CPU);
- return;
- }
-
- if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR) ||
- guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID) ||
- guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID) ||
- guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) {
- hwerr_log_error_type(HWERR_RECOV_CXL);
- return;
- }
-
- if (guid_equal(sec_type, &CPER_SEC_PCIE) ||
- guid_equal(sec_type, &CPER_SEC_PCI_X_BUS)) {
- hwerr_log_error_type(HWERR_RECOV_PCI);
- return;
- }
-
- if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
- hwerr_log_error_type(HWERR_RECOV_MEMORY);
- return;
- }
-
- hwerr_log_error_type(HWERR_RECOV_OTHERS);
-}
-
-static void ghes_do_proc(struct ghes *ghes,
- const struct acpi_hest_generic_status *estatus)
-{
- int sev, sec_sev;
- struct acpi_hest_generic_data *gdata;
- guid_t *sec_type;
- const guid_t *fru_id = &guid_null;
- char *fru_text = "";
- bool queued = false;
- bool sync = is_hest_sync_notify(ghes);
-
- sev = ghes_severity(estatus->error_severity);
- apei_estatus_for_each_section(estatus, gdata) {
- sec_type = (guid_t *)gdata->section_type;
- sec_sev = ghes_severity(gdata->error_severity);
- if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
- fru_id = (guid_t *)gdata->fru_id;
-
- if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
- fru_text = gdata->fru_text;
-
- ghes_log_hwerr(sev, sec_type);
- if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
- struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
-
- atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err);
-
- arch_apei_report_mem_error(sev, mem_err);
- queued = ghes_handle_memory_failure(gdata, sev, sync);
- } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
- ghes_handle_aer(gdata);
- } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) {
- queued = ghes_handle_arm_hw_error(gdata, sev, sync);
- } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) {
- struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata);
-
- cxl_cper_post_prot_err(prot_err, gdata->error_severity);
- } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) {
- struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata);
-
- cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec);
- } else if (guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID)) {
- struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata);
-
- cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec);
- } else if (guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) {
- struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata);
-
- cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec);
- } else {
- void *err = acpi_hest_get_payload(gdata);
-
- ghes_defer_non_standard_event(gdata, sev);
- log_non_standard_event(sec_type, fru_id, fru_text,
- sec_sev, err,
- gdata->error_data_length);
- }
- }
-
- /*
- * If no memory failure work is queued for abnormal synchronous
- * errors, do a force kill.
- */
- if (sync && !queued) {
- dev_err(ghes->dev,
- HW_ERR GHES_PFX "%s:%d: synchronous unrecoverable error (SIGBUS)\n",
- current->comm, task_pid_nr(current));
- force_sig(SIGBUS);
- }
-}
-
-static void __ghes_print_estatus(const char *pfx,
- const struct acpi_hest_generic *generic,
- const struct acpi_hest_generic_status *estatus)
-{
- static atomic_t seqno;
- unsigned int curr_seqno;
- char pfx_seq[64];
-
- if (pfx == NULL) {
- if (ghes_severity(estatus->error_severity) <=
- GHES_SEV_CORRECTED)
- pfx = KERN_WARNING;
- else
- pfx = KERN_ERR;
- }
- curr_seqno = atomic_inc_return(&seqno);
- snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno);
- printk("%s""Hardware error from APEI Generic Hardware Error Source: %d\n",
- pfx_seq, generic->header.source_id);
- cper_estatus_print(pfx_seq, estatus);
-}
-
-static int ghes_print_estatus(const char *pfx,
- const struct acpi_hest_generic *generic,
- const struct acpi_hest_generic_status *estatus)
-{
- /* Not more than 2 messages every 5 seconds */
- static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2);
- static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5*HZ, 2);
- struct ratelimit_state *ratelimit;
-
- if (ghes_severity(estatus->error_severity) <= GHES_SEV_CORRECTED)
- ratelimit = &ratelimit_corrected;
- else
- ratelimit = &ratelimit_uncorrected;
- if (__ratelimit(ratelimit)) {
- __ghes_print_estatus(pfx, generic, estatus);
- return 1;
- }
- return 0;
-}
-
-/*
- * GHES error status reporting throttle, to report more kinds of
- * errors, instead of just most frequently occurred errors.
- */
-static int ghes_estatus_cached(struct acpi_hest_generic_status *estatus)
-{
- u32 len;
- int i, cached = 0;
- unsigned long long now;
- struct ghes_estatus_cache *cache;
- struct acpi_hest_generic_status *cache_estatus;
-
- len = cper_estatus_len(estatus);
- rcu_read_lock();
- for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
- cache = rcu_dereference(ghes_estatus_caches[i]);
- if (cache == NULL)
- continue;
- if (len != cache->estatus_len)
- continue;
- cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
- if (memcmp(estatus, cache_estatus, len))
- continue;
- atomic_inc(&cache->count);
- now = sched_clock();
- if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC)
- cached = 1;
- break;
- }
- rcu_read_unlock();
- return cached;
-}
-
-static struct ghes_estatus_cache *ghes_estatus_cache_alloc(
- struct acpi_hest_generic *generic,
- struct acpi_hest_generic_status *estatus)
-{
- int alloced;
- u32 len, cache_len;
- struct ghes_estatus_cache *cache;
- struct acpi_hest_generic_status *cache_estatus;
-
- alloced = atomic_add_return(1, &ghes_estatus_cache_alloced);
- if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) {
- atomic_dec(&ghes_estatus_cache_alloced);
- return NULL;
- }
- len = cper_estatus_len(estatus);
- cache_len = GHES_ESTATUS_CACHE_LEN(len);
- cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len);
- if (!cache) {
- atomic_dec(&ghes_estatus_cache_alloced);
- return NULL;
- }
- cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
- memcpy(cache_estatus, estatus, len);
- cache->estatus_len = len;
- atomic_set(&cache->count, 0);
- cache->generic = generic;
- cache->time_in = sched_clock();
- return cache;
-}
-
-static void ghes_estatus_cache_rcu_free(struct rcu_head *head)
-{
- struct ghes_estatus_cache *cache;
- u32 len;
-
- cache = container_of(head, struct ghes_estatus_cache, rcu);
- len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache));
- len = GHES_ESTATUS_CACHE_LEN(len);
- gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len);
- atomic_dec(&ghes_estatus_cache_alloced);
-}
-
-static void
-ghes_estatus_cache_add(struct acpi_hest_generic *generic,
- struct acpi_hest_generic_status *estatus)
-{
- unsigned long long now, duration, period, max_period = 0;
- struct ghes_estatus_cache *cache, *new_cache;
- struct ghes_estatus_cache __rcu *victim;
- int i, slot = -1, count;
-
- new_cache = ghes_estatus_cache_alloc(generic, estatus);
- if (!new_cache)
- return;
-
- rcu_read_lock();
- now = sched_clock();
- for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
- cache = rcu_dereference(ghes_estatus_caches[i]);
- if (cache == NULL) {
- slot = i;
- break;
- }
- duration = now - cache->time_in;
- if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) {
- slot = i;
- break;
- }
- count = atomic_read(&cache->count);
- period = duration;
- do_div(period, (count + 1));
- if (period > max_period) {
- max_period = period;
- slot = i;
- }
- }
- rcu_read_unlock();
-
- if (slot != -1) {
- /*
- * Use release semantics to ensure that ghes_estatus_cached()
- * running on another CPU will see the updated cache fields if
- * it can see the new value of the pointer.
- */
- victim = xchg_release(&ghes_estatus_caches[slot],
- RCU_INITIALIZER(new_cache));
-
- /*
- * At this point, victim may point to a cached item different
- * from the one based on which we selected the slot. Instead of
- * going to the loop again to pick another slot, let's just
- * drop the other item anyway: this may cause a false cache
- * miss later on, but that won't cause any problems.
- */
- if (victim)
- call_rcu(&unrcu_pointer(victim)->rcu,
- ghes_estatus_cache_rcu_free);
- }
-}
-
-static void __ghes_panic(struct ghes *ghes,
- struct acpi_hest_generic_status *estatus,
- u64 buf_paddr, enum fixed_addresses fixmap_idx)
-{
- const char *msg = GHES_PFX "Fatal hardware error";
-
- __ghes_print_estatus(KERN_EMERG, ghes->generic, estatus);
-
- add_taint(TAINT_MACHINE_CHECK, LOCKDEP_STILL_OK);
-
- ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
-
- if (!panic_timeout)
- pr_emerg("%s but panic disabled\n", msg);
-
- panic(msg);
-}
-
-static int ghes_proc(struct ghes *ghes)
-{
- struct acpi_hest_generic_status *estatus = ghes->estatus;
- u64 buf_paddr;
- int rc;
-
- rc = ghes_read_estatus(ghes, estatus, &buf_paddr, FIX_APEI_GHES_IRQ);
- if (rc)
- goto out;
-
- if (ghes_severity(estatus->error_severity) >= GHES_SEV_PANIC)
- __ghes_panic(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ);
-
- if (!ghes_estatus_cached(estatus)) {
- if (ghes_print_estatus(NULL, ghes->generic, estatus))
- ghes_estatus_cache_add(ghes->generic, estatus);
- }
- ghes_do_proc(ghes, estatus);
-
-out:
- ghes_clear_estatus(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ);
-
- return rc;
-}
-
static void ghes_add_timer(struct ghes *ghes)
{
struct acpi_hest_generic *g = ghes->generic;
@@ -1220,7 +245,7 @@ static void ghes_poll_func(struct timer_list *t)
unsigned long flags;
spin_lock_irqsave(&ghes_notify_lock_irq, flags);
- ghes_proc(ghes);
+ estatus_proc(&ghes->estatus_src);
spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
if (!(ghes->flags & GHES_EXITING))
ghes_add_timer(ghes);
@@ -1233,7 +258,7 @@ static irqreturn_t ghes_irq_func(int irq, void *data)
int rc;
spin_lock_irqsave(&ghes_notify_lock_irq, flags);
- rc = ghes_proc(ghes);
+ rc = estatus_proc(&ghes->estatus_src);
spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
if (rc)
return IRQ_NONE;
@@ -1250,7 +275,7 @@ static int ghes_notify_hed(struct notifier_block *this, unsigned long event,
spin_lock_irqsave(&ghes_notify_lock_irq, flags);
list_for_each_entry_rcu(ghes, &ghes_hed, list) {
- if (!ghes_proc(ghes))
+ if (!estatus_proc(&ghes->estatus_src))
ret = NOTIFY_OK;
}
spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
@@ -1262,142 +287,8 @@ static struct notifier_block ghes_notifier_hed = {
.notifier_call = ghes_notify_hed,
};
-/*
- * Handlers for CPER records may not be NMI safe. For example,
- * memory_failure_queue() takes spinlocks and calls schedule_work_on().
- * In any NMI-like handler, memory from ghes_estatus_pool is used to save
- * estatus, and added to the ghes_estatus_llist. irq_work_queue() causes
- * ghes_proc_in_irq() to run in IRQ context where each estatus in
- * ghes_estatus_llist is processed.
- *
- * Memory from the ghes_estatus_pool is also used with the ghes_estatus_cache
- * to suppress frequent messages.
- */
-static struct llist_head ghes_estatus_llist;
static struct irq_work ghes_proc_irq_work;
-static void ghes_proc_in_irq(struct irq_work *irq_work)
-{
- struct llist_node *llnode, *next;
- struct ghes_estatus_node *estatus_node;
- struct acpi_hest_generic *generic;
- struct acpi_hest_generic_status *estatus;
- u32 len, node_len;
-
- llnode = llist_del_all(&ghes_estatus_llist);
- /*
- * Because the time order of estatus in list is reversed,
- * revert it back to proper order.
- */
- llnode = llist_reverse_order(llnode);
- while (llnode) {
- next = llnode->next;
- estatus_node = llist_entry(llnode, struct ghes_estatus_node,
- llnode);
- estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
- len = cper_estatus_len(estatus);
- node_len = GHES_ESTATUS_NODE_LEN(len);
-
- ghes_do_proc(estatus_node->ghes, estatus);
-
- if (!ghes_estatus_cached(estatus)) {
- generic = estatus_node->generic;
- if (ghes_print_estatus(NULL, generic, estatus))
- ghes_estatus_cache_add(generic, estatus);
- }
- gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
- node_len);
-
- llnode = next;
- }
-}
-
-static void ghes_print_queued_estatus(void)
-{
- struct llist_node *llnode;
- struct ghes_estatus_node *estatus_node;
- struct acpi_hest_generic *generic;
- struct acpi_hest_generic_status *estatus;
-
- llnode = llist_del_all(&ghes_estatus_llist);
- /*
- * Because the time order of estatus in list is reversed,
- * revert it back to proper order.
- */
- llnode = llist_reverse_order(llnode);
- while (llnode) {
- estatus_node = llist_entry(llnode, struct ghes_estatus_node,
- llnode);
- estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
- generic = estatus_node->generic;
- ghes_print_estatus(NULL, generic, estatus);
- llnode = llnode->next;
- }
-}
-
-static int ghes_in_nmi_queue_one_entry(struct ghes *ghes,
- enum fixed_addresses fixmap_idx)
-{
- struct acpi_hest_generic_status *estatus, tmp_header;
- struct ghes_estatus_node *estatus_node;
- u32 len, node_len;
- u64 buf_paddr;
- int sev, rc;
-
- if (!IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG))
- return -EOPNOTSUPP;
-
- rc = __ghes_peek_estatus(ghes, &tmp_header, &buf_paddr, fixmap_idx);
- if (rc) {
- ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
- return rc;
- }
-
- rc = __ghes_check_estatus(ghes, &tmp_header);
- if (rc) {
- ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
- return rc;
- }
-
- len = cper_estatus_len(&tmp_header);
- node_len = GHES_ESTATUS_NODE_LEN(len);
- estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len);
- if (!estatus_node)
- return -ENOMEM;
-
- estatus_node->ghes = ghes;
- estatus_node->generic = ghes->generic;
- estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
-
- if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) {
- ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
- rc = -ENOENT;
- goto no_work;
- }
-
- sev = ghes_severity(estatus->error_severity);
- if (sev >= GHES_SEV_PANIC) {
- ghes_print_queued_estatus();
- __ghes_panic(ghes, estatus, buf_paddr, fixmap_idx);
- }
-
- ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
-
- /* This error has been reported before, don't process it again. */
- if (ghes_estatus_cached(estatus))
- goto no_work;
-
- llist_add(&estatus_node->llnode, &ghes_estatus_llist);
-
- return rc;
-
-no_work:
- gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
- node_len);
-
- return rc;
-}
-
static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
enum fixed_addresses fixmap_idx)
{
@@ -1406,7 +297,7 @@ static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
rcu_read_lock();
list_for_each_entry_rcu(ghes, rcu_list, list) {
- if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx))
+ if (!estatus_in_nmi_queue_one_entry(&ghes->estatus_src, fixmap_idx))
ret = 0;
}
rcu_read_unlock();
@@ -1510,13 +401,13 @@ static inline void ghes_nmi_remove(struct ghes *ghes) { }
static void ghes_nmi_init_cxt(void)
{
- init_irq_work(&ghes_proc_irq_work, ghes_proc_in_irq);
+ init_irq_work(&ghes_proc_irq_work, estatus_proc_in_irq);
}
static int __ghes_sdei_callback(struct ghes *ghes,
enum fixed_addresses fixmap_idx)
{
- if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx)) {
+ if (!estatus_in_nmi_queue_one_entry(&ghes->estatus_src, fixmap_idx)) {
irq_work_queue(&ghes_proc_irq_work);
return 0;
@@ -1693,7 +584,7 @@ static int ghes_probe(struct platform_device *ghes_dev)
/* Handle any pending errors right away */
spin_lock_irqsave(&ghes_notify_lock_irq, flags);
- ghes_proc(ghes);
+ estatus_proc(&ghes->estatus_src);
spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
return 0;
@@ -1846,13 +737,13 @@ EXPORT_SYMBOL_GPL(ghes_get_devices);
void ghes_register_report_chain(struct notifier_block *nb)
{
- atomic_notifier_chain_register(&ghes_report_chain, nb);
+ estatus_register_report_chain(nb);
}
EXPORT_SYMBOL_GPL(ghes_register_report_chain);
void ghes_unregister_report_chain(struct notifier_block *nb)
{
- atomic_notifier_chain_unregister(&ghes_report_chain, nb);
+ estatus_unregister_report_chain(nb);
}
EXPORT_SYMBOL_GPL(ghes_unregister_report_chain);
diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
index 259122730303..2080abef673d 100644
--- a/drivers/firmware/efi/estatus.c
+++ b/drivers/firmware/efi/estatus.c
@@ -16,23 +16,26 @@
#include <linux/pfn.h>
#include <linux/aer.h>
#include <linux/nmi.h>
+#include <linux/mm.h>
#include <linux/sched/clock.h>
#include <linux/sched/signal.h>
+#include <linux/spinlock.h>
#include <linux/uuid.h>
#include <linux/kconfig.h>
#include <linux/ras.h>
#include <linux/mutex.h>
+#include <linux/kfifo.h>
#include <linux/notifier.h>
#include <linux/workqueue.h>
#include <linux/task_work.h>
#include <ras/ras_event.h>
+#include <cxl/event.h>
#include <linux/estatus.h>
#include <asm/fixmap.h>
void estatus_pool_region_free(unsigned long addr, u32 size);
-/* Emit a printk at the exact level encoded in the HW_ERR tag we build. */
static void estatus_log_hw_error(char level, const char *seq_tag,
const char *name)
{
@@ -64,7 +67,7 @@ static void estatus_log_hw_error(char level, const char *seq_tag,
}
}
-static inline u32 estatus_len(struct acpi_hest_generic_status *estatus)
+static inline u32 estatus_len(estatus_generic_status *estatus)
{
if (estatus->raw_data_length)
return estatus->raw_data_offset + estatus->raw_data_length;
@@ -90,19 +93,19 @@ static inline u32 estatus_len(struct acpi_hest_generic_status *estatus)
#define ESTATUS_CACHE_LEN(estatus_len) \
(sizeof(struct estatus_cache) + (estatus_len))
#define ESTATUS_FROM_CACHE(cache) \
- ((struct acpi_hest_generic_status *) \
+ ((estatus_generic_status *) \
((struct estatus_cache *)(cache) + 1))
#define ESTATUS_NODE_LEN(estatus_len) \
(sizeof(struct estatus_node) + (estatus_len))
#define ESTATUS_FROM_NODE(node) \
- ((struct acpi_hest_generic_status *) \
+ ((estatus_generic_status *) \
((struct estatus_node *)(node) + 1))
#define ESTATUS_VENDOR_ENTRY_LEN(gdata_len) \
(sizeof(struct estatus_vendor_record_entry) + (gdata_len))
#define ESTATUS_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \
- ((struct acpi_hest_generic_data *) \
+ ((estatus_generic_data *) \
((struct estatus_vendor_record_entry *)(vendor_entry) + 1))
static ATOMIC_NOTIFIER_HEAD(estatus_report_chain);
@@ -249,7 +252,7 @@ EXPORT_SYMBOL_GPL(estatus_pool_region_free);
/* Check the top-level record header has an appropriate size. */
static int __estatus_check_estatus(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus)
+ estatus_generic_status *estatus)
{
u32 len = estatus_len(estatus);
size_t max_len = estatus_source_max_len(source);
@@ -274,7 +277,7 @@ static int __estatus_check_estatus(struct estatus_source *source,
/* Read the CPER block, returning its address, and header in estatus. */
static int __estatus_peek_estatus(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus,
+ estatus_generic_status *estatus,
phys_addr_t *buf_paddr,
enum fixed_addresses fixmap_idx)
{
@@ -306,7 +309,7 @@ static int __estatus_peek_estatus(struct estatus_source *source,
}
static int __estatus_read_estatus(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus,
+ estatus_generic_status *estatus,
phys_addr_t buf_paddr,
enum fixed_addresses fixmap_idx,
size_t buf_len)
@@ -329,7 +332,7 @@ static int __estatus_read_estatus(struct estatus_source *source,
}
static int estatus_read_estatus(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus,
+ estatus_generic_status *estatus,
phys_addr_t *buf_paddr,
enum fixed_addresses fixmap_idx)
{
@@ -348,7 +351,7 @@ static int estatus_read_estatus(struct estatus_source *source,
}
static void estatus_clear_estatus(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus,
+ estatus_generic_status *estatus,
phys_addr_t buf_paddr,
enum fixed_addresses fixmap_idx)
{
@@ -388,7 +391,7 @@ static inline int estatus_severity(int severity)
static void __estatus_print_estatus(const char *pfx,
struct estatus_source *source,
- const struct acpi_hest_generic_status *estatus)
+ const estatus_generic_status *estatus)
{
static atomic_t seqno;
unsigned int curr_seqno;
@@ -420,7 +423,7 @@ static void __estatus_print_estatus(const char *pfx,
static int estatus_print_estatus(const char *pfx,
struct estatus_source *source,
- const struct acpi_hest_generic_status *estatus)
+ const estatus_generic_status *estatus)
{
/* Not more than 2 messages every 5 seconds */
static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5 * HZ, 2);
@@ -442,13 +445,13 @@ static int estatus_print_estatus(const char *pfx,
* GHES error status reporting throttle, to report more kinds of
* errors, instead of just most frequently occurred errors.
*/
-static int estatus_cached(struct acpi_hest_generic_status *estatus)
+static int estatus_cached(estatus_generic_status *estatus)
{
u32 len;
int i, cached = 0;
unsigned long long now;
struct estatus_cache *cache;
- struct acpi_hest_generic_status *cache_estatus;
+ estatus_generic_status *cache_estatus;
len = estatus_len(estatus);
rcu_read_lock();
@@ -472,12 +475,12 @@ static int estatus_cached(struct acpi_hest_generic_status *estatus)
}
static struct estatus_cache *estatus_cache_alloc(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus)
+ estatus_generic_status *estatus)
{
int alloced;
u32 len, cache_len;
struct estatus_cache *cache;
- struct acpi_hest_generic_status *cache_estatus;
+ estatus_generic_status *cache_estatus;
alloced = atomic_add_return(1, &estatus_cache_alloced);
if (alloced > ESTATUS_CACHE_ALLOCED_MAX) {
@@ -513,7 +516,7 @@ static void estatus_cache_rcu_free(struct rcu_head *head)
}
static void estatus_cache_add(struct estatus_source *source,
- struct acpi_hest_generic_status *estatus)
+ estatus_generic_status *estatus)
{
unsigned long long now, duration, period, max_period = 0;
struct estatus_cache *cache, *new_cache;
@@ -704,7 +707,7 @@ static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bo
* ESTATUS_SEV_PANIC does not make it to this handling since the kernel must
* panic.
*/
-static void estatus_handle_aer(struct acpi_hest_generic_data *gdata)
+static void estatus_handle_aer(estatus_generic_data *gdata)
{
#ifdef CONFIG_ACPI_APEI_PCIEAER
struct cper_sec_pcie *pcie_err = estatus_get_payload(gdata);
@@ -759,7 +762,7 @@ EXPORT_SYMBOL_GPL(estatus_unregister_vendor_record_notifier);
static void estatus_vendor_record_work_func(struct work_struct *work)
{
struct estatus_vendor_record_entry *entry;
- struct acpi_hest_generic_data *gdata;
+ estatus_generic_data *gdata;
u32 len;
entry = container_of(work, struct estatus_vendor_record_entry, work);
@@ -774,7 +777,7 @@ static void estatus_vendor_record_work_func(struct work_struct *work)
static void estatus_defer_non_standard_event(estatus_generic_data *gdata, int sev)
{
- struct acpi_hest_generic_data *copied_gdata;
+ estatus_generic_data *copied_gdata;
struct estatus_vendor_record_entry *entry;
u32 len;
@@ -806,7 +809,7 @@ static inline bool estatus_is_sync_notify(struct estatus_source *source)
static void estatus_do_proc(struct estatus_source *source, const estatus_generic_status *estatus)
{
int sev, sec_sev;
- struct acpi_hest_generic_data *gdata;
+ estatus_generic_data *gdata;
guid_t *sec_type;
const guid_t *fru_id = &guid_null;
char *fru_text = "";
@@ -871,7 +874,7 @@ static void __estatus_panic(struct estatus_source *source, estatus_generic_statu
int estatus_proc(struct estatus_source *source)
{
- struct acpi_hest_generic_status *estatus = source->estatus;
+ estatus_generic_status *estatus = source->estatus;
phys_addr_t buf_paddr;
enum fixed_addresses fixmap_idx = estatus_source_fixmap(source);
int rc;
@@ -913,7 +916,7 @@ void estatus_proc_in_irq(struct irq_work *irq_work)
{
struct llist_node *llnode, *next;
struct estatus_node *estatus_node;
- struct acpi_hest_generic_status *source;
+ struct estatus_source *source;
estatus_generic_status *estatus;
u32 len, node_len;
@@ -927,7 +930,7 @@ void estatus_proc_in_irq(struct irq_work *irq_work)
next = llnode->next;
estatus_node = llist_entry(llnode, struct estatus_node,
llnode);
- struct estatus_source *source = estatus_node->source;
+ source = estatus_node->source;
estatus = ESTATUS_FROM_NODE(estatus_node);
len = estatus_len(estatus);
node_len = ESTATUS_NODE_LEN(len);
@@ -949,7 +952,7 @@ static void estatus_print_queued_estatus(void)
struct llist_node *llnode;
struct estatus_node *estatus_node;
struct estatus_source *source;
- struct acpi_hest_generic_status *estatus;
+ estatus_generic_status *estatus;
llnode = llist_del_all(&estatus_llist);
/*
@@ -976,7 +979,7 @@ void estatus_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
int estatus_in_nmi_queue_one_entry(struct estatus_source *source, enum fixed_addresses fixmap_idx)
{
- struct acpi_hest_generic_status *estatus, tmp_header;
+ estatus_generic_status *estatus, tmp_header;
struct estatus_node *estatus_node;
u32 len, node_len;
phys_addr_t buf_paddr;
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (8 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 09/12] ghes: route error handling through shared estatus core Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-17 11:41 ` Krzysztof Kozlowski
2025-12-17 11:28 ` [PATCH 11/12] ras: add DeviceTree estatus provider driver Ahmed Tiba
` (2 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Add a binding for firmware-first CPER providers described via
DeviceTree. It covers the shared status block, optional acknowledgment
registers, interrupt versus polling modes and the SEA notification
flag so non-ACPI platforms can describe their error sources.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
.../devicetree/bindings/ras/arm,ras-ffh.yaml | 95 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 96 insertions(+)
create mode 100644 Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
diff --git a/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml b/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
new file mode 100644
index 000000000000..0d2acbf8e8a8
--- /dev/null
+++ b/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ras/arm,ras-ffh.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Arm Firmware-First Handler (FFH) CPER provider
+
+maintainers:
+ - Ahmed Tiba <ahmed.tiba@arm.com>
+
+description: |
+ Some Arm platforms describe a firmware-first error handler that exposes a
+ Common Platform Error Record (CPER) buffer directly via DeviceTree. The OS
+ maps the buffer to consume the error records, and firmware signals that a new
+ record is ready either by asserting an interrupt or by relying on a periodic
+ poll. This binding describes the buffer and the associated notification
+ signal. If firmware delivers the error via Synchronous External Abort (SEA),
+ the optional sea-notify flag marks the source accordingly.
+
+properties:
+ compatible:
+ const: arm,ras-ffh
+
+ reg:
+ minItems: 1
+ items:
+ - description: CPER status block exposed by firmware
+ - description:
+ Optional 32- or 64-bit acknowledgment register. Firmware watches this
+ register and expects bit 0 to be written to 1 once the OS consumes the
+ status buffer so it can reuse the record.
+
+ reg-names:
+ items:
+ - const: status
+ - const: ack
+
+ interrupts:
+ maxItems: 1
+ description:
+ Optional interrupt used to signal that a new status record is ready. If
+ omitted, the OS relies on the polling interval property.
+
+ poll-interval:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 1
+ description:
+ Optional polling interval, in milliseconds, for platforms that cannot
+ route an interrupt.
+
+ arm,sea-notify:
+ type: boolean
+ description:
+ Set if the platform delivers these errors as Synchronous External Aborts.
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - if:
+ properties:
+ poll-interval: false
+ then:
+ required:
+ - interrupts
+ - if:
+ properties:
+ interrupts: false
+ then:
+ required:
+ - poll-interval
+ - if:
+ properties:
+ reg:
+ minItems: 2
+ then:
+ required:
+ - reg-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ ras-ffh@fe800000 {
+ compatible = "arm,ras-ffh";
+ reg = <0xfe800000 0x1000>,
+ <0xfe810000 0x4>;
+ reg-names = "status", "ack";
+ interrupts = <0 32 IRQ_TYPE_LEVEL_HIGH>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 67d79d4e612d..6b2ef2ddc0c7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21760,6 +21760,7 @@ F: drivers/rapidio/
RAS ERROR STATUS
M: Ahmed Tiba <ahmed.tiba@arm.com>
S: Maintained
+F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
F: drivers/firmware/efi/estatus.c
F: include/linux/estatus.h
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (9 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 10/12] dt-bindings: ras: document estatus provider Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-18 12:13 ` Will Deacon
2025-12-17 11:28 ` [PATCH 12/12] doc: ras: describe firmware-first estatus flow Ahmed Tiba
2025-12-21 1:35 ` [PATCH 00/12] ras: share firmware-first estatus handling Borislav Petkov
12 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Introduce a platform driver that maps the CPER status block described
in DeviceTree, feeds it into the estatus core and handles either IRQ- or
poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
driver can safely map the shared buffer while copying records.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
MAINTAINERS | 1 +
arch/arm64/include/asm/fixmap.h | 5 +
drivers/ras/Kconfig | 14 ++
drivers/ras/Makefile | 1 +
drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
include/linux/estatus.h | 3 +-
6 files changed, 341 insertions(+), 1 deletion(-)
create mode 100644 drivers/ras/estatus-dt.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6b2ef2ddc0c7..5567d5e82053 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21761,6 +21761,7 @@ RAS ERROR STATUS
M: Ahmed Tiba <ahmed.tiba@arm.com>
S: Maintained
F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
+F: drivers/ras/estatus-dt.c
F: drivers/firmware/efi/estatus.c
F: include/linux/estatus.h
diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
index 65555284446e..85ffba87bab9 100644
--- a/arch/arm64/include/asm/fixmap.h
+++ b/arch/arm64/include/asm/fixmap.h
@@ -64,6 +64,11 @@ enum fixed_addresses {
#endif
#endif /* CONFIG_ACPI_APEI_GHES */
+#ifdef CONFIG_RAS_ESTATUS_DT
+ /* Used for ESTATUS mapping from assorted contexts */
+ FIX_ESTATUS_IRQ,
+#endif /* CONFIG_RAS_ESTATUS_DT */
+
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
#ifdef CONFIG_RELOCATABLE
FIX_ENTRY_TRAMP_TEXT4, /* one extra slot for the data page */
diff --git a/drivers/ras/Kconfig b/drivers/ras/Kconfig
index fc4f4bb94a4c..0f8c0d3c26db 100644
--- a/drivers/ras/Kconfig
+++ b/drivers/ras/Kconfig
@@ -34,6 +34,20 @@ if RAS
source "arch/x86/ras/Kconfig"
source "drivers/ras/amd/atl/Kconfig"
+config RAS_ESTATUS_DT
+ tristate "DeviceTree estatus provider"
+ depends on ARM64
+ depends on OF && HAS_IOMEM
+ select UEFI_CPER
+ select RAS_ESTATUS_CORE
+ help
+ Enable support for firmware-first CPER providers that are described
+ via DeviceTree. The driver maps the firmware-provided CPER buffer
+ and consumes records when firmware signals that new data is ready,
+ either with an interrupt or via polling. Select this when running on
+ platforms that expose firmware-first error records without
+ ACPI/APEI GHES.
+
config RAS_FMPM
tristate "FRU Memory Poison Manager"
default m
diff --git a/drivers/ras/Makefile b/drivers/ras/Makefile
index 11f95d59d397..726455bcf950 100644
--- a/drivers/ras/Makefile
+++ b/drivers/ras/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_RAS_CEC) += cec.o
obj-$(CONFIG_RAS_FMPM) += amd/fmpm.o
obj-y += amd/atl/
+obj-$(CONFIG_RAS_ESTATUS_DT) += estatus-dt.o
diff --git a/drivers/ras/estatus-dt.c b/drivers/ras/estatus-dt.c
new file mode 100644
index 000000000000..75c85c404cb6
--- /dev/null
+++ b/drivers/ras/estatus-dt.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DeviceTree provider for firmware-first estatus error records
+ *
+ * Copyright (C) 2025 ARM Ltd.
+ * Author: Ahmed Tiba <ahmed.tiba@arm.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+#include <linux/estatus.h>
+#include <asm/fixmap.h>
+
+struct estatus_dt_ack {
+ void __iomem *addr;
+ u64 preserve;
+ u64 set;
+ u8 width;
+ bool present;
+};
+
+struct estatus_dt {
+ struct device *dev;
+ void __iomem *base;
+ phys_addr_t phys;
+ size_t block_size;
+
+ struct estatus_dt_ack ack;
+
+ struct estatus_source source;
+ int irq;
+
+ struct timer_list poll_timer;
+ u32 poll_interval_ms;
+ bool polling;
+
+ bool sea_notify;
+};
+
+static int estatus_dt_get_phys(struct estatus_source *source, phys_addr_t *addr)
+{
+ struct estatus_dt *ctx = source->priv;
+
+ *addr = ctx->phys;
+ return 0;
+}
+
+static int estatus_dt_read(struct estatus_source *source, phys_addr_t addr,
+ void *buf, size_t len,
+ enum fixed_addresses fixmap_idx)
+{
+ struct estatus_dt *ctx = source->priv;
+
+ (void)addr;
+ (void)fixmap_idx;
+
+ if (WARN_ON(len > ctx->block_size))
+ len = ctx->block_size;
+
+ memcpy_fromio(buf, ctx->base, len);
+
+ return 0;
+}
+
+static int estatus_dt_write(struct estatus_source *source, phys_addr_t addr,
+ const void *buf, size_t len,
+ enum fixed_addresses fixmap_idx)
+{
+ struct estatus_dt *ctx = source->priv;
+
+ (void)addr;
+ (void)fixmap_idx;
+
+ if (WARN_ON(len > ctx->block_size))
+ len = ctx->block_size;
+
+ memcpy_toio(ctx->base, buf, len);
+
+ return 0;
+}
+
+static void estatus_dt_ack(struct estatus_source *source)
+{
+ struct estatus_dt *ctx = source->priv;
+ u64 val;
+
+ if (!ctx->ack.present)
+ return;
+
+ if (ctx->ack.width == 64) {
+ val = readq(ctx->ack.addr);
+ val &= ctx->ack.preserve;
+ val |= ctx->ack.set;
+ writeq(val, ctx->ack.addr);
+ } else {
+ val = readl(ctx->ack.addr);
+ val &= (u32)ctx->ack.preserve;
+ val |= (u32)ctx->ack.set;
+ writel(val, ctx->ack.addr);
+ }
+}
+
+static size_t estatus_dt_get_max_len(struct estatus_source *source)
+{
+ struct estatus_dt *ctx = source->priv;
+
+ return ctx->block_size;
+}
+
+static enum estatus_notify_mode
+estatus_dt_get_notify_mode(struct estatus_source *source)
+{
+ struct estatus_dt *ctx = source->priv;
+
+ if (ctx->sea_notify)
+ return ESTATUS_NOTIFY_SEA;
+
+ return ESTATUS_NOTIFY_ASYNC;
+}
+
+static const char *estatus_dt_get_name(struct estatus_source *source)
+{
+ struct estatus_dt *ctx = source->priv;
+
+ return dev_name(ctx->dev);
+}
+
+static const struct estatus_ops estatus_dt_ops = {
+ .get_phys = estatus_dt_get_phys,
+ .read = estatus_dt_read,
+ .write = estatus_dt_write,
+ .ack = estatus_dt_ack,
+ .get_max_len = estatus_dt_get_max_len,
+ .get_notify_mode = estatus_dt_get_notify_mode,
+ .get_name = estatus_dt_get_name,
+};
+
+static irqreturn_t estatus_dt_irq(int irq, void *data)
+{
+ struct estatus_dt *ctx = data;
+
+ if (estatus_proc(&ctx->source))
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static void estatus_dt_poll(struct timer_list *t)
+{
+ struct estatus_dt *ctx = timer_container_of(ctx, t, poll_timer);
+
+ estatus_proc(&ctx->source);
+ mod_timer(&ctx->poll_timer,
+ jiffies + msecs_to_jiffies(ctx->poll_interval_ms));
+}
+
+static int estatus_dt_init_ack(struct platform_device *pdev,
+ struct estatus_dt *ctx)
+{
+ struct resource *res;
+ u64 preserve;
+ size_t size;
+ u32 width;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ack");
+ if (!res)
+ return 0;
+
+ ctx->ack.addr = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ctx->ack.addr))
+ return PTR_ERR(ctx->ack.addr);
+
+ size = resource_size(res);
+ if (size == 4)
+ width = 32;
+ else if (size == 8)
+ width = 64;
+ else {
+ dev_err(&pdev->dev, "Unsupported ack resource size %zu\n", size);
+ return -EINVAL;
+ }
+ ctx->ack.width = width;
+
+ preserve = width == 64 ? ~0ULL : ~0U;
+ ctx->ack.preserve = preserve;
+ ctx->ack.set = BIT_ULL(0);
+
+ ctx->ack.present = true;
+
+ return 0;
+}
+
+static int estatus_dt_probe(struct platform_device *pdev)
+{
+ struct estatus_dt *ctx;
+ struct resource *res;
+ size_t block_size;
+ int rc;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ block_size = resource_size(res);
+ if (!block_size) {
+ dev_err(&pdev->dev, "Status block region has zero size\n");
+ return -EINVAL;
+ }
+
+ rc = estatus_pool_init(1);
+ if (rc)
+ return rc;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = &pdev->dev;
+ ctx->sea_notify = of_property_read_bool(pdev->dev.of_node,
+ "arm,sea-notify");
+ ctx->poll_interval_ms = 0;
+ of_property_read_u32(pdev->dev.of_node,
+ "poll-interval", &ctx->poll_interval_ms);
+ if (ctx->poll_interval_ms)
+ ctx->polling = true;
+
+ ctx->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ctx->base))
+ return PTR_ERR(ctx->base);
+
+ ctx->phys = res->start;
+ ctx->block_size = block_size;
+
+ ctx->source.ops = &estatus_dt_ops;
+ ctx->source.priv = ctx;
+ ctx->source.estatus = devm_kzalloc(&pdev->dev, block_size, GFP_KERNEL);
+ if (!ctx->source.estatus)
+ return -ENOMEM;
+ ctx->source.fixmap_idx = FIX_ESTATUS_IRQ;
+
+ rc = estatus_dt_init_ack(pdev, ctx);
+ if (rc)
+ return rc;
+
+ ctx->irq = platform_get_irq_optional(pdev, 0);
+ if (ctx->irq < 0) {
+ if (ctx->irq != -ENXIO && ctx->irq != -EINVAL)
+ return ctx->irq;
+ ctx->irq = 0;
+ }
+
+ if (ctx->irq > 0) {
+ rc = devm_request_threaded_irq(&pdev->dev, ctx->irq,
+ NULL, estatus_dt_irq,
+ IRQF_ONESHOT,
+ dev_name(&pdev->dev), ctx);
+ if (rc)
+ return rc;
+ }
+
+ if (!ctx->polling && ctx->irq <= 0) {
+ dev_err(&pdev->dev,
+ "Either poll-interval or an interrupt is required\n");
+ return -EINVAL;
+ }
+
+ if (ctx->polling) {
+ timer_setup(&ctx->poll_timer, estatus_dt_poll, 0);
+ mod_timer(&ctx->poll_timer,
+ jiffies + msecs_to_jiffies(ctx->poll_interval_ms));
+ }
+
+ platform_set_drvdata(pdev, ctx);
+
+ dev_info(&pdev->dev, "Registered estatus provider (%s)\n",
+ ctx->polling ? "polling" : "interrupt");
+
+ return 0;
+}
+
+static void estatus_dt_remove(struct platform_device *pdev)
+{
+ struct estatus_dt *ctx = platform_get_drvdata(pdev);
+
+ if (ctx->polling)
+ timer_delete_sync(&ctx->poll_timer);
+}
+
+static const struct of_device_id estatus_dt_of_match[] = {
+ { .compatible = "arm,ras-ffh", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, estatus_dt_of_match);
+
+static struct platform_driver estatus_dt_driver = {
+ .probe = estatus_dt_probe,
+ .remove = estatus_dt_remove,
+ .driver = {
+ .name = "estatus-dt",
+ .of_match_table = estatus_dt_of_match,
+ },
+};
+
+module_platform_driver(estatus_dt_driver);
+
+MODULE_AUTHOR("Ahmed Tiba <ahmed.tiba@arm.com>");
+MODULE_DESCRIPTION("DeviceTree estatus provider");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/estatus.h b/include/linux/estatus.h
index 506a74ad60b9..2478f54b62e4 100644
--- a/include/linux/estatus.h
+++ b/include/linux/estatus.h
@@ -227,7 +227,8 @@ estatus_section_iter_next(struct estatus_section_iter *iter,
#define estatus_for_each_section(_estatus, _section) \
for (struct estatus_section_iter __estatus_iter = {0}; \
- ((_section) = estatus_section_iter_next(&__estatus_iter, (_estatus))); \
+ ((_section) = estatus_section_iter_next(&__estatus_iter, \
+ (estatus_generic_status *)(_estatus))); \
)
static inline int acpi_hest_get_version(struct acpi_hest_generic_data *gdata)
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 12/12] doc: ras: describe firmware-first estatus flow
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (10 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 11/12] ras: add DeviceTree estatus provider driver Ahmed Tiba
@ 2025-12-17 11:28 ` Ahmed Tiba
2025-12-21 1:35 ` [PATCH 00/12] ras: share firmware-first estatus handling Borislav Petkov
12 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 11:28 UTC (permalink / raw)
To: linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
Add a short section to the RAS admin guide that explains how the new
estatus core and DeviceTree provider are enabled, and point readers to
the binding that describes the firmware-first CPER layout.
Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
---
Documentation/admin-guide/RAS/main.rst | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/Documentation/admin-guide/RAS/main.rst b/Documentation/admin-guide/RAS/main.rst
index 5a45db32c49b..adff8b4bc84b 100644
--- a/Documentation/admin-guide/RAS/main.rst
+++ b/Documentation/admin-guide/RAS/main.rst
@@ -205,6 +205,30 @@ Architecture (MCA)\ [#f3]_.
.. [#f3] For more details about the Machine Check Architecture (MCA),
please read Documentation/arch/x86/x86_64/machinecheck.rst at the Kernel tree.
+Firmware-first CPER status providers
+------------------------------------
+
+Some systems expose Common Platform Error Record (CPER) data via firmware
+interfaces instead of relying on CPU machine-check banks. The kernel routes
+those records through the shared *estatus* core:
+
+* ``CONFIG_RAS_ESTATUS_CORE`` hosts the in-kernel data movers and notifiers.
+ It is selected automatically when ACPI/APEI GHES support is enabled or when
+ the DeviceTree provider below is built.
+* ``CONFIG_RAS_ESTATUS_DT`` adds the ``drivers/ras/estatus-dt.c`` provider that
+ maps a firmware-advertised CPER status block on non-ACPI platforms. The
+ DeviceTree binding is documented in
+ ``Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml`` and describes the
+ status-buffer region and the notification signal (interrupt or polling) that
+ firmware uses to advertise new records.
+
+Once a platform describes a firmware-first provider, both ACPI GHES and the
+DeviceTree driver reuse the same code paths: CPER sections are iterated with
+``estatus_for_each_section()``, logged through ``cper_estatus_print()``, and
+handed to the RAS notifier chains that trigger memory-offline, PCIe AER, or
+vendor-specific handlers. This keeps the behaviour consistent regardless of
+whether the error source is described via ACPI tables or DeviceTree.
+
EDAC - Error Detection And Correction
*************************************
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-17 11:28 ` [PATCH 10/12] dt-bindings: ras: document estatus provider Ahmed Tiba
@ 2025-12-17 11:41 ` Krzysztof Kozlowski
2025-12-17 17:49 ` Ahmed Tiba
0 siblings, 1 reply; 39+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-17 11:41 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2
On 17/12/2025 12:28, Ahmed Tiba wrote:
> Add a binding for firmware-first CPER providers described via
> DeviceTree. It covers the shared status block, optional acknowledgment
> registers, interrupt versus polling modes and the SEA notification
> flag so non-ACPI platforms can describe their error sources.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> ---
> .../devicetree/bindings/ras/arm,ras-ffh.yaml | 95 +++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 96 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
>
> diff --git a/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml b/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
> new file mode 100644
> index 000000000000..0d2acbf8e8a8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
What is ras? There is no such directory so some description would be
useful. Usually you do not get your own directory per binding.
> @@ -0,0 +1,95 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/ras/arm,ras-ffh.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Arm Firmware-First Handler (FFH) CPER provider
> +
> +maintainers:
> + - Ahmed Tiba <ahmed.tiba@arm.com>
> +
> +description: |
> + Some Arm platforms describe a firmware-first error handler that exposes a
> + Common Platform Error Record (CPER) buffer directly via DeviceTree. The OS
> + maps the buffer to consume the error records, and firmware signals that a new
> + record is ready either by asserting an interrupt or by relying on a periodic
> + poll. This binding describes the buffer and the associated notification
Do not describe what the binding does. Describe the hardware or firmware.
> + signal. If firmware delivers the error via Synchronous External Abort (SEA),
> + the optional sea-notify flag marks the source accordingly.
> +
> +properties:
> + compatible:
> + const: arm,ras-ffh
Again ras - what's that? Your patch or binding must explain that.
> +
> + reg:
> + minItems: 1
Why is this flexible?
> + items:
> + - description: CPER status block exposed by firmware
> + - description:
> + Optional 32- or 64-bit acknowledgment register. Firmware watches this
> + register and expects bit 0 to be written to 1 once the OS consumes the
> + status buffer so it can reuse the record.
> +
> + reg-names:
> + items:
> + - const: status
> + - const: ack
Does not match reg.
> +
> + interrupts:
> + maxItems: 1
> + description:
> + Optional interrupt used to signal that a new status record is ready. If
> + omitted, the OS relies on the polling interval property.
What OS is doing should not really matter. Either you have the interrupt
or not.
> +
> + poll-interval:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + minimum: 1
> + description:
> + Optional polling interval, in milliseconds, for platforms that cannot
> + route an interrupt.
That's OS policy, not suitable for binding.
> +
> + arm,sea-notify:
> + type: boolean
> + description:
> + Set if the platform delivers these errors as Synchronous External Aborts.
This is implied by the compatible, no?
> +
> +required:
> + - compatible
> + - reg
> +
> +allOf:
> + - if:
> + properties:
> + poll-interval: false
> + then:
> + required:
> + - interrupts
> + - if:
> + properties:
> + interrupts: false
> + then:
> + required:
> + - poll-interval
> + - if:
> + properties:
> + reg:
> + minItems: 2
> + then:
> + required:
> + - reg-names
Drop all this.
> +
> +unevaluatedProperties: false
I do not see any schema referenced.
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> + ras-ffh@fe800000 {
Node names should be generic. See also an explanation and list of
examples (not exhaustive) in DT specification:
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
If you cannot find a name matching your device, please check in kernel
sources for similar cases or you can grow the spec (via pull request to
DT spec repo).
> + compatible = "arm,ras-ffh";
> + reg = <0xfe800000 0x1000>,
> + <0xfe810000 0x4>;
> + reg-names = "status", "ack";
> + interrupts = <0 32 IRQ_TYPE_LEVEL_HIGH>;
Use proper defines.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-17 11:41 ` Krzysztof Kozlowski
@ 2025-12-17 17:49 ` Ahmed Tiba
2025-12-18 6:48 ` Krzysztof Kozlowski
0 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-17 17:49 UTC (permalink / raw)
To: krzk, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
On 17/12/2025 12:41, Krzysztof Kozlowski wrote:
> What is ras? There is no such directory so some description would be
> useful. Usually you do not get your own directory per binding.
Would it make sense to move it under `Documentation/devicetree/bindings/firmware`
and expand the description so it spells out that
Arm RAS refers to reliability, availability and serviceability firmware.
> Do not describe what the binding does. Describe the hardware or firmware.
I'll reword that section.
> Again ras - what's that? Your patch or binding must explain that.
I'll add that explanation to the description.
> Why is this flexible?
Some platforms only expose the CPER status buffer, while others also expose a
doorbell that firmware expects to toggle before writing the next record.
I'll keep `reg` at 1-2 entries but make the description clear about which
region is optional.
> Does not match reg.
`reg-names` will only be allowed when both regions are present,
and in that case it must list `"status", "ack"`
so the entries line up with `reg`.
If only the status buffer exists, the property stays omitted.
> What OS is doing should not really matter. Either you have the interrupt
> or not.
I’ll trim the wording so it just states that firmware
may assert an interrupt when a new record is ready.
> That's OS policy, not suitable for binding.
I’ll drop `poll-interval` from the binding and let the driver fall back
to a fixed polling interval when no interrupt is wired.
> This is implied by the compatible, no?
I’ll drop `arm,sea-notify` so the compatible alone defines the behaviour.
> Drop all this.
I’ll delete the `allOf` clauses once the policy properties are gone.
> I do not see any schema referenced.
I’ll switch from `unevaluatedProperties` to `additionalProperties: false`.
> Node names should be generic. See also an explanation and list of
> examples (not exhaustive) in DT specification:
> https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
> If you cannot find a name matching your device, please check in kernel
> sources for similar cases or you can grow the spec (via pull request to
> DT spec repo).
I’ll rename the example node to `estatus@fe800000`
so it describes the firmware error-status block rather than using the driver name.
> Use proper defines.
I’ll update the example to use `GIC_SPI` and the `IRQ_TYPE_*` macros for the interrupt specifier.
Best regards,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-17 17:49 ` Ahmed Tiba
@ 2025-12-18 6:48 ` Krzysztof Kozlowski
2025-12-18 10:22 ` Ahmed Tiba
2025-12-18 10:31 ` Ahmed Tiba
0 siblings, 2 replies; 39+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-18 6:48 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2
On 17/12/2025 18:49, Ahmed Tiba wrote:
> On 17/12/2025 12:41, Krzysztof Kozlowski wrote:
>> What is ras? There is no such directory so some description would be
>> useful. Usually you do not get your own directory per binding.
>
> Would it make sense to move it under `Documentation/devicetree/bindings/firmware`
> and expand the description so it spells out that
> Arm RAS refers to reliability, availability and serviceability firmware.
>
>> Do not describe what the binding does. Describe the hardware or firmware.
>
> I'll reword that section.
>
>> Again ras - what's that? Your patch or binding must explain that.
You cut almost entire context, so I don't know what these parts are
referring to.
Please follow standard mailing list reply style. I am not reading the
rest and I still expect to implement my feedback fully.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-18 6:48 ` Krzysztof Kozlowski
@ 2025-12-18 10:22 ` Ahmed Tiba
2025-12-18 10:31 ` Ahmed Tiba
1 sibling, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-18 10:22 UTC (permalink / raw)
To: krzk, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
On 18/12/2025 07:48, Krzysztof Kozlowski wrote:
> Please follow standard mailing list reply style. I am not reading the
> rest and I still expect to implement my feedback fully.
Apologies for the poor reply formatting.
I will resend my reply with proper inline formatting and full
context preserved, and follow up with a new revision implementing
your feedback.
Thanks.
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-18 6:48 ` Krzysztof Kozlowski
2025-12-18 10:22 ` Ahmed Tiba
@ 2025-12-18 10:31 ` Ahmed Tiba
2025-12-19 9:53 ` Krzysztof Kozlowski
1 sibling, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-18 10:31 UTC (permalink / raw)
To: krzk, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, ahmed.tiba
On 17/12/2025 12:41, Krzysztof Kozlowski wrote:
>> Add a binding for firmware-first CPER providers described via
>> DeviceTree. It covers the shared status block, optional acknowledgment
>> registers, interrupt versus polling modes and the SEA notification
>> flag so non-ACPI platforms can describe their error sources.
>>
>> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> ---
>> .../devicetree/bindings/ras/arm,ras-ffh.yaml | 95 +++++++++++++++++++
>> MAINTAINERS | 1 +
>> 2 files changed, 96 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml b/Documentation/devicetree/>bindings/ras/arm,ras-ffh.yaml
>> new file mode 100644
>> index 000000000000..0d2acbf8e8a8
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
>
> What is ras? There is no such directory so some description would be
> useful. Usually you do not get your own directory per binding.
For the next revision I will move the schema under
`Documentation/devicetree/bindings/firmware/` and expand the description to
spell out that Arm Reliability, Availability and Serviceability (RAS) firmware
exposes this FFH CPER provider.
>> @@ -0,0 +1,95 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/ras/arm,ras-ffh.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Arm Firmware-First Handler (FFH) CPER provider
>> +
>> +maintainers:
>> + - Ahmed Tiba <ahmed.tiba@arm.com>
>> +
>> +description: |
>> + Some Arm platforms describe a firmware-first error handler that exposes a
>> + Common Platform Error Record (CPER) buffer directly via DeviceTree. The OS
>> + maps the buffer to consume the error records, and firmware signals that a new
>> + record is ready either by asserting an interrupt or by relying on a periodic
>> + poll. This binding describes the buffer and the associated notification
>
> Do not describe what the binding does. Describe the hardware or firmware.
I'll reword the description so it focuses on the firmware-managed CPER buffer,
optional doorbell register and optional interrupt rather than describing how
the binding is consumed.
>> + signal. If firmware delivers the error via Synchronous External Abort (SEA),
>> + the optional sea-notify flag marks the source accordingly.
>> +
>> +properties:
>> + compatible:
>> + const: arm,ras-ffh
>
> Again ras - what's that? Your patch or binding must explain that.
That updated description will explicitly expand the Arm RAS acronym so the
compatible string is self-explanatory.
>> +
>> + reg:
>> + minItems: 1
>
> Why is this flexible?
I'll keep `reg` describing the CPER status buffer, cap it at two entries, and
document the second entry as the optional doorbell register that some firmware
requires before reusing the buffer.
>> + items:
>> + - description: CPER status block exposed by firmware
>> + - description:
>> + Optional 32- or 64-bit acknowledgment register. Firmware watches this
>> + register and expects bit 0 to be written to 1 once the OS consumes the
>> + status buffer so it can reuse the record.
>> +
>> + reg-names:
>> + items:
>> + - const: status
>> + - const: ack
>
> Does not match reg.
`reg-names` will remain optional, but when provided the first entry will be
restricted to `"status"` and the second (if present) to `"ack"`, matching the
single optional region permitted in `reg`.
>> +
>> + interrupts:
>> + maxItems: 1
>> + description:
>> + Optional interrupt used to signal that a new status record is ready. If
>> + omitted, the OS relies on the polling interval property.
>
> What OS is doing should not really matter. Either you have the interrupt
> or not.
I'll trim the wording so it just states that firmware may assert an interrupt
when a new record is ready.
>> +
>> + poll-interval:
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + minimum: 1
>> + description:
>> + Optional polling interval, in milliseconds, for platforms that cannot
>> + route an interrupt.
>
> That's OS policy, not suitable for binding.
I'll drop `poll-interval` from the binding so the driver can fall back to its
fixed interval when no interrupt is wired.
>> +
>> + arm,sea-notify:
>> + type: boolean
>> + description:
>> + Set if the platform delivers these errors as Synchronous External Aborts.
>
> This is implied by the compatible, no?
I'll drop `arm,sea-notify` so the compatible alone defines the behaviour.
>> +
>> +required:
>> + - compatible
>> + - reg
>> +
>> +allOf:
>> + - if:
>> + properties:
>> + poll-interval: false
>> + then:
>> + required:
>> + - interrupts
>> + - if:
>> + properties:
>> + interrupts: false
>> + then:
>> + required:
>> + - poll-interval
>> + - if:
>> + properties:
>> + reg:
>> + minItems: 2
>> + then:
>> + required:
>> + - reg-names
>
>Drop all this.
I'll drop this block entirely.
>> +
>> +unevaluatedProperties: false
>
> I do not see any schema referenced.
I'll switch to `additionalProperties: false` so we rely solely on the schema
referenced by `$schema` while still rejecting unknown properties.
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>> +
>> + ras-ffh@fe800000 {
>
> Node names should be generic. See also an explanation and list of
> examples (not exhaustive) in DT specification:
> https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.> html#generic-names-recommendation
> If you cannot find a name matching your device, please check in kernel
> sources for similar cases or you can grow the spec (via pull request to
> DT spec repo).
I'll rename the example node to the generic `error-handler@fe800000` so it
describes the shared error-status block instead of the driver name.
>> + compatible = "arm,ras-ffh";
>> + reg = <0xfe800000 0x1000>,
>> + <0xfe810000 0x4>;
>> + reg-names = "status", "ack";
>> + interrupts = <0 32 IRQ_TYPE_LEVEL_HIGH>;
>
> Use proper defines.
I'll also switch the example interrupt listing to `GIC_SPI` and `IRQ_TYPE_*`
macros.
Best regards,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-17 11:28 ` [PATCH 11/12] ras: add DeviceTree estatus provider driver Ahmed Tiba
@ 2025-12-18 12:13 ` Will Deacon
2025-12-18 13:42 ` Ahmed Tiba
0 siblings, 1 reply; 39+ messages in thread
From: Will Deacon @ 2025-12-18 12:13 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2
On Wed, Dec 17, 2025 at 11:28:44AM +0000, Ahmed Tiba wrote:
> Introduce a platform driver that maps the CPER status block described
> in DeviceTree, feeds it into the estatus core and handles either IRQ- or
> poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
> driver can safely map the shared buffer while copying records.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> ---
> MAINTAINERS | 1 +
> arch/arm64/include/asm/fixmap.h | 5 +
> drivers/ras/Kconfig | 14 ++
> drivers/ras/Makefile | 1 +
> drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
> include/linux/estatus.h | 3 +-
> 6 files changed, 341 insertions(+), 1 deletion(-)
> create mode 100644 drivers/ras/estatus-dt.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6b2ef2ddc0c7..5567d5e82053 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -21761,6 +21761,7 @@ RAS ERROR STATUS
> M: Ahmed Tiba <ahmed.tiba@arm.com>
> S: Maintained
> F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
> +F: drivers/ras/estatus-dt.c
> F: drivers/firmware/efi/estatus.c
> F: include/linux/estatus.h
>
> diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
> index 65555284446e..85ffba87bab9 100644
> --- a/arch/arm64/include/asm/fixmap.h
> +++ b/arch/arm64/include/asm/fixmap.h
> @@ -64,6 +64,11 @@ enum fixed_addresses {
> #endif
> #endif /* CONFIG_ACPI_APEI_GHES */
>
> +#ifdef CONFIG_RAS_ESTATUS_DT
> + /* Used for ESTATUS mapping from assorted contexts */
> + FIX_ESTATUS_IRQ,
> +#endif /* CONFIG_RAS_ESTATUS_DT */
Why do we need this in addition to the four existing GHES slots? The DT
code doesn't use it and I was assuming that the ACPI code would continue
to use the existing irq; is that not the case?
Will
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-18 12:13 ` Will Deacon
@ 2025-12-18 13:42 ` Ahmed Tiba
2025-12-18 15:19 ` Will Deacon
0 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-18 13:42 UTC (permalink / raw)
To: will
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Thu, 18 Dec 2025 12:13:25PM +0000, Will Deacon wrote:
>> Introduce a platform driver that maps the CPER status block described
>> in DeviceTree, feeds it into the estatus core and handles either IRQ- or
>> poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
>> driver can safely map the shared buffer while copying records.
>>
>> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> ---
>> MAINTAINERS | 1 +
>> arch/arm64/include/asm/fixmap.h | 5 +
>> drivers/ras/Kconfig | 14 ++
>> drivers/ras/Makefile | 1 +
>> drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
>> include/linux/estatus.h | 3 +-
>> 6 files changed, 341 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/ras/estatus-dt.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 6b2ef2ddc0c7..5567d5e82053 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -21761,6 +21761,7 @@ RAS ERROR STATUS
>> M: Ahmed Tiba <ahmed.tiba@arm.com>
>> S: Maintained
>> F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
>> +F: drivers/ras/estatus-dt.c
>> F: drivers/firmware/efi/estatus.c
>> F: include/linux/estatus.h
>>
>> diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
>> index 65555284446e..85ffba87bab9 100644
>> --- a/arch/arm64/include/asm/fixmap.h
>> +++ b/arch/arm64/include/asm/fixmap.h
>> @@ -64,6 +64,11 @@ enum fixed_addresses {
>> #endif
>> #endif /* CONFIG_ACPI_APEI_GHES */
>>
>> +#ifdef CONFIG_RAS_ESTATUS_DT
>> + /* Used for ESTATUS mapping from assorted contexts */
>> + FIX_ESTATUS_IRQ,
>> +#endif /* CONFIG_RAS_ESTATUS_DT */
>
> Why do we need this in addition to the four existing GHES slots? The DT
> code doesn't use it and I was assuming that the ACPI code would continue
> to use the existing irq; is that not the case?
We still need a dedicated slot when only the DT provider is built.
All four GHES slots are defined as part of the ACPI implementation,
so they are not present in a DT-only configuration.
The estatus core always requests a fixmap index from each provider
before copying a CPER record. As a result, the DT driver must supply
its own slot to return a valid enum value to satisfy the common code.
The ACPI/GHES path continues to use the existing four slots exactly as
before. This change simply ensures that the DT provider does not depend
on ACPI being enabled solely to satisfy the estatus core interface.
Best regards,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-18 13:42 ` Ahmed Tiba
@ 2025-12-18 15:19 ` Will Deacon
2025-12-19 9:02 ` Ahmed Tiba
0 siblings, 1 reply; 39+ messages in thread
From: Will Deacon @ 2025-12-18 15:19 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2
On Thu, Dec 18, 2025 at 01:42:47PM +0000, Ahmed Tiba wrote:
> On Thu, 18 Dec 2025 12:13:25PM +0000, Will Deacon wrote:
> >> Introduce a platform driver that maps the CPER status block described
> >> in DeviceTree, feeds it into the estatus core and handles either IRQ- or
> >> poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
> >> driver can safely map the shared buffer while copying records.
> >>
> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> >> ---
> >> MAINTAINERS | 1 +
> >> arch/arm64/include/asm/fixmap.h | 5 +
> >> drivers/ras/Kconfig | 14 ++
> >> drivers/ras/Makefile | 1 +
> >> drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
> >> include/linux/estatus.h | 3 +-
> >> 6 files changed, 341 insertions(+), 1 deletion(-)
> >> create mode 100644 drivers/ras/estatus-dt.c
> >>
> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> index 6b2ef2ddc0c7..5567d5e82053 100644
> >> --- a/MAINTAINERS
> >> +++ b/MAINTAINERS
> >> @@ -21761,6 +21761,7 @@ RAS ERROR STATUS
> >> M: Ahmed Tiba <ahmed.tiba@arm.com>
> >> S: Maintained
> >> F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
> >> +F: drivers/ras/estatus-dt.c
> >> F: drivers/firmware/efi/estatus.c
> >> F: include/linux/estatus.h
> >>
> >> diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
> >> index 65555284446e..85ffba87bab9 100644
> >> --- a/arch/arm64/include/asm/fixmap.h
> >> +++ b/arch/arm64/include/asm/fixmap.h
> >> @@ -64,6 +64,11 @@ enum fixed_addresses {
> >> #endif
> >> #endif /* CONFIG_ACPI_APEI_GHES */
> >>
> >> +#ifdef CONFIG_RAS_ESTATUS_DT
> >> + /* Used for ESTATUS mapping from assorted contexts */
> >> + FIX_ESTATUS_IRQ,
> >> +#endif /* CONFIG_RAS_ESTATUS_DT */
> >
> > Why do we need this in addition to the four existing GHES slots? The DT
> > code doesn't use it and I was assuming that the ACPI code would continue
> > to use the existing irq; is that not the case?
>
>
> We still need a dedicated slot when only the DT provider is built.
> All four GHES slots are defined as part of the ACPI implementation,
> so they are not present in a DT-only configuration.
>
> The estatus core always requests a fixmap index from each provider
> before copying a CPER record. As a result, the DT driver must supply
> its own slot to return a valid enum value to satisfy the common code.
Sorry, but I still don't follow this. The DT code doesn't use the fixmap,
does it? It looks like it maps the buffer ahead of time using
devm_ioremap_resource() and then the accessors don't use the fixmap
index at all, hence the horrible '(void)fixmap_idx;' cast which presumably
stops the compiler from complaining about an unused variable.
Will
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 02/12] ras: add estatus core implementation
2025-12-17 11:28 ` [PATCH 02/12] ras: add estatus core implementation Ahmed Tiba
@ 2025-12-18 15:42 ` Mauro Carvalho Chehab
2025-12-19 14:35 ` Ahmed Tiba
2025-12-21 19:31 ` kernel test robot
1 sibling, 1 reply; 39+ messages in thread
From: Mauro Carvalho Chehab @ 2025-12-18 15:42 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2
On Wed, Dec 17, 2025 at 11:28:35AM +0000, Ahmed Tiba wrote:
> Add estatus.c, hook it into the EFI Makefile, and register
> the MAINTAINERS entry for the new code. The implementation provides the
> memory-pool helpers, notifier plumbing, and utility functions that the
> GHES and DeviceTree providers will reuse in later commits.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> ---
> MAINTAINERS | 1 +
> drivers/firmware/efi/Makefile | 1 +
> drivers/firmware/efi/estatus.c | 560 +++++++++++++++++++++++++++++++++
If I'm understanding this patch series, you will be basically moving more than
half of the code from drivers/acpi/apei/ghes.c to drivers/firmware/efi/estatus.c:
drivers/acpi/apei/ghes.c | 1292 ++-------------
drivers/firmware/efi/estatus.c | 1056 ++++++++++++
$ wc drivers/acpi/apei/ghes.c drivers/firmware/efi/estatus.c -l
894 drivers/acpi/apei/ghes.c
1056 drivers/firmware/efi/estatus.c
1950 total
The way you're doing of adding things first, and then removing on
separate patches is error prone, makes it hard to review and
it becomes a lot harder to identify whose are the original authors
of the code.
This will cause undetected conflicts with already-submitted patches
that are under review.
You should instead be moving function per function as-is. Then,
adjust the code to make it more generic.
Regards,
Mauro
> 3 files changed, 562 insertions(+)
> create mode 100644 drivers/firmware/efi/estatus.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 501b6d300aa5..67d79d4e612d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -21760,6 +21760,7 @@ F: drivers/rapidio/
> RAS ERROR STATUS
> M: Ahmed Tiba <ahmed.tiba@arm.com>
> S: Maintained
> +F: drivers/firmware/efi/estatus.c
> F: include/linux/estatus.h
>
> RAS INFRASTRUCTURE
> diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
> index 8efbcf699e4f..03708d915bcf 100644
> --- a/drivers/firmware/efi/Makefile
> +++ b/drivers/firmware/efi/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o
> obj-$(CONFIG_EFI_ESRT) += esrt.o
> obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
> obj-$(CONFIG_UEFI_CPER) += cper.o cper_cxl.o
> +obj-$(CONFIG_RAS_ESTATUS_CORE) += estatus.o
> obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
> subdir-$(CONFIG_EFI_STUB) += libstub
> obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o
> diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
> new file mode 100644
> index 000000000000..8dae5c73ce27
> --- /dev/null
> +++ b/drivers/firmware/efi/estatus.c
> @@ -0,0 +1,560 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Firmware-first RAS: Generic Error Status Core
> + *
> + * Copyright (C) 2025 ARM Ltd.
> + * Author: Ahmed Tiba <ahmed.tiba@arm.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/cper.h>
> +#include <linux/ratelimit.h>
> +#include <linux/vmalloc.h>
> +#include <linux/llist.h>
> +#include <linux/genalloc.h>
> +#include <linux/pci.h>
> +#include <linux/pfn.h>
> +#include <linux/aer.h>
> +#include <linux/nmi.h>
> +#include <linux/sched/clock.h>
> +#include <linux/uuid.h>
> +#include <linux/kconfig.h>
> +#include <linux/ras.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/workqueue.h>
> +#include <linux/task_work.h>
> +#include <ras/ras_event.h>
> +
> +#include <linux/estatus.h>
> +#include <asm/fixmap.h>
> +
> +void estatus_pool_region_free(unsigned long addr, u32 size);
> +
> +static void estatus_log_hw_error(char level, const char *seq_tag,
> + const char *name)
> +{
> + switch (level) {
> + case '0':
> + pr_emerg("%sHardware error from %s\n", seq_tag, name);
> + break;
> + case '1':
> + pr_alert("%sHardware error from %s\n", seq_tag, name);
> + break;
> + case '2':
> + pr_crit("%sHardware error from %s\n", seq_tag, name);
> + break;
> + case '3':
> + pr_err("%sHardware error from %s\n", seq_tag, name);
> + break;
> + case '4':
> + pr_warn("%sHardware error from %s\n", seq_tag, name);
> + break;
> + case '5':
> + pr_notice("%sHardware error from %s\n", seq_tag, name);
> + break;
> + case '6':
> + pr_info("%sHardware error from %s\n", seq_tag, name);
> + break;
> + default:
> + pr_debug("%sHardware error from %s\n", seq_tag, name);
> + break;
> + }
> +}
> +
> +static inline u32 estatus_len(struct acpi_hest_generic_status *estatus)
> +{
> + if (estatus->raw_data_length)
> + return estatus->raw_data_offset + estatus->raw_data_length;
> +
> + return sizeof(*estatus) + estatus->data_length;
> +}
> +
> +#define ESTATUS_PFX "ESTATUS: "
> +
> +#define ESTATUS_ESOURCE_PREALLOC_MAX_SIZE_SIZE 65536
> +
> +#define ESTATUS_POOL_MIN_ALLOC_ORDER 3
> +
> +/* This is just an estimation for memory pool allocation */
> +#define ESTATUS_CACHE_AVG_SIZE 512
> +
> +#define ESTATUS_CACHES_SIZE 4
> +
> +#define ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL
> +/* Prevent too many caches are allocated because of RCU */
> +#define ESTATUS_CACHE_ALLOCED_MAX (ESTATUS_CACHES_SIZE * 3 / 2)
> +
> +#define ESTATUS_CACHE_LEN(estatus_len) \
> + (sizeof(struct estatus_cache) + (estatus_len))
> +#define ESTATUS_FROM_CACHE(cache) \
> + ((struct acpi_hest_generic_status *) \
> + ((struct estatus_cache *)(cache) + 1))
> +
> +#define ESTATUS_NODE_LEN(estatus_len) \
> + (sizeof(struct estatus_node) + (estatus_len))
> +#define ESTATUS_FROM_NODE(node) \
> + ((struct acpi_hest_generic_status *) \
> + ((struct estatus_node *)(node) + 1))
> +
> +#define ESTATUS_VENDOR_ENTRY_LEN(gdata_len) \
> + (sizeof(struct estatus_vendor_record_entry) + (gdata_len))
> +#define ESTATUS_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \
> + ((struct acpi_hest_generic_data *) \
> + ((struct estatus_vendor_record_entry *)(vendor_entry) + 1))
> +
> +static ATOMIC_NOTIFIER_HEAD(estatus_report_chain);
> +
> +struct estatus_vendor_record_entry {
> + struct work_struct work;
> + int error_severity;
> + char vendor_record[];
> +};
> +
> +static struct estatus_cache __rcu *estatus_caches[ESTATUS_CACHES_SIZE];
> +static atomic_t estatus_cache_alloced;
> +
> +static int estatus_panic_timeout __read_mostly = 30;
> +
> +static struct gen_pool *estatus_pool;
> +static DEFINE_MUTEX(estatus_pool_mutex);
> +
> +static inline const char *estatus_source_name(struct estatus_source *source)
> +{
> + if (source->ops && source->ops->get_name)
> + return source->ops->get_name(source);
> +
> + return "unknown";
> +}
> +
> +static inline size_t estatus_source_max_len(struct estatus_source *source)
> +{
> + if (source->ops && source->ops->get_max_len)
> + return source->ops->get_max_len(source);
> +
> + return 0;
> +}
> +
> +static inline enum estatus_notify_mode
> +estatus_source_notify_mode(struct estatus_source *source)
> +{
> + if (source->ops && source->ops->get_notify_mode)
> + return source->ops->get_notify_mode(source);
> +
> + return ESTATUS_NOTIFY_ASYNC;
> +}
> +
> +static inline int estatus_source_get_phys(struct estatus_source *source,
> + phys_addr_t *addr)
> +{
> + if (!source->ops || !source->ops->get_phys)
> + return -EOPNOTSUPP;
> +
> + return source->ops->get_phys(source, addr);
> +}
> +
> +static inline int estatus_source_read(struct estatus_source *source,
> + phys_addr_t addr, void *buf, size_t len,
> + enum fixed_addresses fixmap_idx)
> +{
> + if (!source->ops || !source->ops->read)
> + return -EOPNOTSUPP;
> +
> + return source->ops->read(source, addr, buf, len, fixmap_idx);
> +}
> +
> +static inline int estatus_source_write(struct estatus_source *source,
> + phys_addr_t addr, const void *buf,
> + size_t len,
> + enum fixed_addresses fixmap_idx)
> +{
> + if (!source->ops || !source->ops->write)
> + return -EOPNOTSUPP;
> +
> + return source->ops->write(source, addr, buf, len, fixmap_idx);
> +}
> +
> +static inline void estatus_source_ack(struct estatus_source *source)
> +{
> + if (source->ops && source->ops->ack)
> + source->ops->ack(source);
> +}
> +
> +int estatus_pool_init(unsigned int num_ghes)
> +{
> + unsigned long addr, len;
> + int rc = 0;
> +
> + mutex_lock(&estatus_pool_mutex);
> + if (estatus_pool)
> + goto out_unlock;
> +
> + estatus_pool = gen_pool_create(ESTATUS_POOL_MIN_ALLOC_ORDER, -1);
> + if (!estatus_pool) {
> + rc = -ENOMEM;
> + goto out_unlock;
> + }
> +
> + if (!num_ghes)
> + num_ghes = 1;
> +
> + len = ESTATUS_CACHE_AVG_SIZE * ESTATUS_CACHE_ALLOCED_MAX;
> + len += (num_ghes * ESTATUS_ESOURCE_PREALLOC_MAX_SIZE_SIZE);
> +
> + addr = (unsigned long)vmalloc(PAGE_ALIGN(len));
> + if (!addr) {
> + rc = -ENOMEM;
> + goto err_pool_alloc;
> + }
> +
> + rc = gen_pool_add(estatus_pool, addr, PAGE_ALIGN(len), -1);
> + if (rc)
> + goto err_pool_add;
> +
> +out_unlock:
> + mutex_unlock(&estatus_pool_mutex);
> + return rc;
> +
> +err_pool_add:
> + vfree((void *)addr);
> +err_pool_alloc:
> + gen_pool_destroy(estatus_pool);
> + estatus_pool = NULL;
> + goto out_unlock;
> +}
> +
> +/**
> + * estatus_pool_region_free - free previously allocated memory
> + * from the estatus_pool.
> + * @addr: address of memory to free.
> + * @size: size of memory to free.
> + *
> + * Returns none.
> + */
> +void estatus_pool_region_free(unsigned long addr, u32 size)
> +{
> + gen_pool_free(estatus_pool, addr, size);
> +}
> +EXPORT_SYMBOL_GPL(estatus_pool_region_free);
> +
> +/* Check the top-level record header has an appropriate size. */
> +static int __estatus_check_estatus(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus)
> +{
> + u32 len = estatus_len(estatus);
> + size_t max_len = estatus_source_max_len(source);
> +
> + if (len < sizeof(*estatus)) {
> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Truncated error status block!\n");
> + return -EIO;
> + }
> +
> + if (max_len && len > max_len) {
> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Invalid error status block length!\n");
> + return -EIO;
> + }
> +
> + if (cper_estatus_check_header(estatus)) {
> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Invalid CPER header!\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +/* Read the CPER block, returning its address, and header in estatus. */
> +static int __estatus_peek_estatus(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus,
> + phys_addr_t *buf_paddr,
> + enum fixed_addresses fixmap_idx)
> +{
> + int rc;
> +
> + rc = estatus_source_get_phys(source, buf_paddr);
> + if (rc) {
> + *buf_paddr = 0;
> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX
> + "Failed to get error status block address for provider %s: %d\n",
> + estatus_source_name(source), rc);
> + return rc;
> + }
> +
> + if (!*buf_paddr)
> + return -ENOENT;
> +
> + rc = estatus_source_read(source, *buf_paddr, estatus,
> + sizeof(*estatus), fixmap_idx);
> + if (rc)
> + return rc;
> +
> + if (!estatus->block_status) {
> + *buf_paddr = 0;
> + return -ENOENT;
> + }
> +
> + return 0;
> +}
> +
> +static int __estatus_read_estatus(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus,
> + phys_addr_t buf_paddr,
> + enum fixed_addresses fixmap_idx,
> + size_t buf_len)
> +{
> + int rc;
> +
> + rc = estatus_source_read(source, buf_paddr, estatus, buf_len,
> + fixmap_idx);
> + if (rc)
> + return rc;
> +
> + if (cper_estatus_check(estatus)) {
> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX
> + "Failed to read error status block for provider %s!\n",
> + estatus_source_name(source));
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int estatus_read_estatus(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus,
> + phys_addr_t *buf_paddr,
> + enum fixed_addresses fixmap_idx)
> +{
> + int rc;
> +
> + rc = __estatus_peek_estatus(source, estatus, buf_paddr, fixmap_idx);
> + if (rc)
> + return rc;
> +
> + rc = __estatus_check_estatus(source, estatus);
> + if (rc)
> + return rc;
> +
> + return __estatus_read_estatus(source, estatus, *buf_paddr,
> + fixmap_idx, estatus_len(estatus));
> +}
> +
> +static void estatus_clear_estatus(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus,
> + phys_addr_t buf_paddr,
> + enum fixed_addresses fixmap_idx)
> +{
> + int rc;
> +
> + estatus->block_status = 0;
> +
> + if (!buf_paddr)
> + return;
> +
> + rc = estatus_source_write(source, buf_paddr, estatus,
> + sizeof(estatus->block_status), fixmap_idx);
> + if (rc)
> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX
> + "Failed to clear error status block for provider %s: %d\n",
> + estatus_source_name(source), rc);
> +
> + estatus_source_ack(source);
> +}
> +
> +static inline int estatus_severity(int severity)
> +{
> + switch (severity) {
> + case CPER_SEV_INFORMATIONAL:
> + return ESTATUS_SEV_NO;
> + case CPER_SEV_CORRECTED:
> + return ESTATUS_SEV_CORRECTED;
> + case CPER_SEV_RECOVERABLE:
> + return ESTATUS_SEV_RECOVERABLE;
> + case CPER_SEV_FATAL:
> + return ESTATUS_SEV_PANIC;
> + default:
> + /* Unknown, go panic */
> + return ESTATUS_SEV_PANIC;
> + }
> +}
> +
> +static void __estatus_print_estatus(const char *pfx,
> + struct estatus_source *source,
> + const struct acpi_hest_generic_status *estatus)
> +{
> + static atomic_t seqno;
> + unsigned int curr_seqno;
> + char pfx_seq[64];
> + char seq_tag[64];
> + const char *name = estatus_source_name(source);
> + const char *level = pfx;
> + char level_char = '4';
> +
> + if (!level) {
> + if (estatus_severity(estatus->error_severity) <=
> + ESTATUS_SEV_CORRECTED)
> + level = KERN_WARNING;
> + else
> + level = KERN_ERR;
> + }
> +
> + if (level[0] == KERN_SOH_ASCII && level[1])
> + level_char = level[1];
> + else if (estatus_severity(estatus->error_severity) > ESTATUS_SEV_CORRECTED)
> + level_char = '3';
> +
> + curr_seqno = atomic_inc_return(&seqno);
> + snprintf(seq_tag, sizeof(seq_tag), "{%u}" HW_ERR, curr_seqno);
> + snprintf(pfx_seq, sizeof(pfx_seq), "%s%s", level, seq_tag);
> + estatus_log_hw_error(level_char, seq_tag, name);
> + cper_estatus_print(pfx_seq, estatus);
> +}
> +
> +static int estatus_print_estatus(const char *pfx,
> + struct estatus_source *source,
> + const struct acpi_hest_generic_status *estatus)
> +{
> + /* Not more than 2 messages every 5 seconds */
> + static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5 * HZ, 2);
> + static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5 * HZ, 2);
> + struct ratelimit_state *ratelimit;
> +
> + if (estatus_severity(estatus->error_severity) <= ESTATUS_SEV_CORRECTED)
> + ratelimit = &ratelimit_corrected;
> + else
> + ratelimit = &ratelimit_uncorrected;
> + if (__ratelimit(ratelimit)) {
> + __estatus_print_estatus(pfx, source, estatus);
> + return 1;
> + }
> + return 0;
> +}
> +
> +/*
> + * GHES error status reporting throttle, to report more kinds of
> + * errors, instead of just most frequently occurred errors.
> + */
> +static int estatus_cached(struct acpi_hest_generic_status *estatus)
> +{
> + u32 len;
> + int i, cached = 0;
> + unsigned long long now;
> + struct estatus_cache *cache;
> + struct acpi_hest_generic_status *cache_estatus;
> +
> + len = estatus_len(estatus);
> + rcu_read_lock();
> + for (i = 0; i < ESTATUS_CACHES_SIZE; i++) {
> + cache = rcu_dereference(estatus_caches[i]);
> + if (!cache)
> + continue;
> + if (len != cache->estatus_len)
> + continue;
> + cache_estatus = ESTATUS_FROM_CACHE(cache);
> + if (memcmp(estatus, cache_estatus, len))
> + continue;
> + atomic_inc(&cache->count);
> + now = sched_clock();
> + if (now - cache->time_in < ESTATUS_IN_CACHE_MAX_NSEC)
> + cached = 1;
> + break;
> + }
> + rcu_read_unlock();
> + return cached;
> +}
> +
> +static struct estatus_cache *estatus_cache_alloc(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus)
> +{
> + int alloced;
> + u32 len, cache_len;
> + struct estatus_cache *cache;
> + struct acpi_hest_generic_status *cache_estatus;
> +
> + alloced = atomic_add_return(1, &estatus_cache_alloced);
> + if (alloced > ESTATUS_CACHE_ALLOCED_MAX) {
> + atomic_dec(&estatus_cache_alloced);
> + return NULL;
> + }
> + len = estatus_len(estatus);
> + cache_len = ESTATUS_CACHE_LEN(len);
> + cache = (void *)gen_pool_alloc(estatus_pool, cache_len);
> + if (!cache) {
> + atomic_dec(&estatus_cache_alloced);
> + return NULL;
> + }
> + cache_estatus = ESTATUS_FROM_CACHE(cache);
> + memcpy(cache_estatus, estatus, len);
> + cache->estatus_len = len;
> + atomic_set(&cache->count, 0);
> + cache->source = source;
> + cache->time_in = sched_clock();
> + return cache;
> +}
> +
> +static void estatus_cache_rcu_free(struct rcu_head *head)
> +{
> + struct estatus_cache *cache;
> + u32 len;
> +
> + cache = container_of(head, struct estatus_cache, rcu);
> + len = estatus_len(ESTATUS_FROM_CACHE(cache));
> + len = ESTATUS_CACHE_LEN(len);
> + gen_pool_free(estatus_pool, (unsigned long)cache, len);
> + atomic_dec(&estatus_cache_alloced);
> +}
> +
> +static void estatus_cache_add(struct estatus_source *source,
> + struct acpi_hest_generic_status *estatus)
> +{
> + unsigned long long now, duration, period, max_period = 0;
> + struct estatus_cache *cache, *new_cache;
> + struct estatus_cache __rcu *victim;
> + int i, slot = -1, count;
> +
> + new_cache = estatus_cache_alloc(source, estatus);
> + if (!new_cache)
> + return;
> +
> + rcu_read_lock();
> + now = sched_clock();
> + for (i = 0; i < ESTATUS_CACHES_SIZE; i++) {
> + cache = rcu_dereference(estatus_caches[i]);
> + if (!cache) {
> + slot = i;
> + break;
> + }
> + duration = now - cache->time_in;
> + if (duration >= ESTATUS_IN_CACHE_MAX_NSEC) {
> + slot = i;
> + break;
> + }
> + count = atomic_read(&cache->count);
> + period = duration;
> + do_div(period, (count + 1));
> + if (period > max_period) {
> + max_period = period;
> + slot = i;
> + }
> + }
> + rcu_read_unlock();
> +
> + if (slot != -1) {
> + /*
> + * Use release semantics to ensure that estatus_cached()
> + * running on another CPU will see the updated cache fields if
> + * it can see the new value of the pointer.
> + */
> + victim = xchg_release(&estatus_caches[slot],
> + RCU_INITIALIZER(new_cache));
> +
> + /*
> + * At this point, victim may point to a cached item different
> + * from the one based on which we selected the slot. Instead of
> + * going to the loop again to pick another slot, let's just
> + * drop the other item anyway: this may cause a false cache
> + * miss later on, but that won't cause any problems.
> + */
> + if (victim)
> + call_rcu(&unrcu_pointer(victim)->rcu,
> + estatus_cache_rcu_free);
> + }
> +}
> --
> 2.43.0
>
--
Thanks,
Mauro
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-17 11:28 ` [PATCH 03/12] ras: add estatus vendor handling and processing Ahmed Tiba
@ 2025-12-18 16:04 ` Mauro Carvalho Chehab
2025-12-19 14:49 ` Ahmed Tiba
2025-12-21 23:39 ` kernel test robot
1 sibling, 1 reply; 39+ messages in thread
From: Mauro Carvalho Chehab @ 2025-12-18 16:04 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2
Em Wed, 17 Dec 2025 11:28:36 +0000
Ahmed Tiba <ahmed.tiba@arm.com> escreveu:
> Teach the estatus core how to walk CPER records and expose the vendor
> record notification path. This adds the section iteration helpers,
> the logging helpers that mirror the GHES behaviour, and the deferred
> work used to hand vendor GUIDs to interested drivers. No users switch
> over yet; this simply moves the common logic out of GHES so the next
> patches can wire it up.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
...
> +static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
Huh?
This is a CPER record from GHES. Why are you moving CPER code out
of ghes.c, placing in a file named estatus.c? Doesn't make much
sense on my eyes...
Same applies to to other GHES CPER record types.
Thanks,
Mauro
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-18 15:19 ` Will Deacon
@ 2025-12-19 9:02 ` Ahmed Tiba
2025-12-19 13:00 ` Will Deacon
0 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 9:02 UTC (permalink / raw)
To: will
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Thu, 18 Dec 2025 03:19:17PM +0000, Will Deacon wrote:
> On Thu, Dec 18, 2025 at 01:42:47PM +0000, Ahmed Tiba wrote:
>> On Thu, 18 Dec 2025 12:13:25PM +0000, Will Deacon wrote:
>> >> Introduce a platform driver that maps the CPER status block described
>> >> in DeviceTree, feeds it into the estatus core and handles either IRQ- or
>> >> poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
>> >> driver can safely map the shared buffer while copying records.
>> >>
>> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> >> ---
>> >> MAINTAINERS | 1 +
>> >> arch/arm64/include/asm/fixmap.h | 5 +
>> >> drivers/ras/Kconfig | 14 ++
>> >> drivers/ras/Makefile | 1 +
>> >> drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
>> >> include/linux/estatus.h | 3 +-
>> >> 6 files changed, 341 insertions(+), 1 deletion(-)
>> >> create mode 100644 drivers/ras/estatus-dt.c
>> >>
>> >> diff --git a/MAINTAINERS b/MAINTAINERS
>> >> index 6b2ef2ddc0c7..5567d5e82053 100644
>> >> --- a/MAINTAINERS
>> >> +++ b/MAINTAINERS
>> >> @@ -21761,6 +21761,7 @@ RAS ERROR STATUS
>> >> M: Ahmed Tiba <ahmed.tiba@arm.com>
>> >> S: Maintained
>> >> F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
>> >> +F: drivers/ras/estatus-dt.c
>> >> F: drivers/firmware/efi/estatus.c
>> >> F: include/linux/estatus.h
>> >>
>> >> diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
>> >> index 65555284446e..85ffba87bab9 100644
>> >> --- a/arch/arm64/include/asm/fixmap.h
>> >> +++ b/arch/arm64/include/asm/fixmap.h
>> >> @@ -64,6 +64,11 @@ enum fixed_addresses {
>> >> #endif
>> >> #endif /* CONFIG_ACPI_APEI_GHES */
>> >>
>> >> +#ifdef CONFIG_RAS_ESTATUS_DT
>> >> + /* Used for ESTATUS mapping from assorted contexts */
>> >> + FIX_ESTATUS_IRQ,
>> >> +#endif /* CONFIG_RAS_ESTATUS_DT */
>> >
>> > Why do we need this in addition to the four existing GHES slots? The DT
>> > code doesn't use it and I was assuming that the ACPI code would continue
>> > to use the existing irq; is that not the case?
>>
>>
>> We still need a dedicated slot when only the DT provider is built.
>> All four GHES slots are defined as part of the ACPI implementation,
>> so they are not present in a DT-only configuration.
>>
>> The estatus core always requests a fixmap index from each provider
>> before copying a CPER record. As a result, the DT driver must supply
>> its own slot to return a valid enum value to satisfy the common code.
>
> Sorry, but I still don't follow this. The DT code doesn't use the fixmap,
> does it? It looks like it maps the buffer ahead of time using
> devm_ioremap_resource() and then the accessors don't use the fixmap
> index at all, hence the horrible '(void)fixmap_idx;' cast which presumably
> stops the compiler from complaining about an unused variable.
Correct. The current DT driver keeps the CPER buffer permanently mapped with
devm_ioremap_resource() and that (void)fixmap_idx; line is just silencing
the warning. I’ll fix that by dropping the permanent mapping and copying the
status block via the fixmap entry, so the DT implementation mirrors GHES. That
gets rid of the cast and makes FIX_ESTATUS_IRQ do real work.
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-18 10:31 ` Ahmed Tiba
@ 2025-12-19 9:53 ` Krzysztof Kozlowski
2025-12-19 10:37 ` Ahmed Tiba
2025-12-19 10:47 ` Ahmed Tiba
0 siblings, 2 replies; 39+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-19 9:53 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2
On 18/12/2025 11:31, Ahmed Tiba wrote:
> On 17/12/2025 12:41, Krzysztof Kozlowski wrote:
>>> +properties:
>>> + compatible:
>>> + const: arm,ras-ffh
>>
>> Again ras - what's that? Your patch or binding must explain that.
>
> That updated description will explicitly expand the Arm RAS acronym so the
> compatible string is self-explanatory.
>
>>> +
>>> + reg:
>>> + minItems: 1
>>
>> Why is this flexible?
>
> I'll keep `reg` describing the CPER status buffer, cap it at two entries, and
> document the second entry as the optional doorbell register that some firmware
> requires before reusing the buffer.
I still do not understand why this is flexible or in other words - why
second address space appears and disappears.
>
>>> + items:
>>> + - description: CPER status block exposed by firmware
>>> + - description:
>>> + Optional 32- or 64-bit acknowledgment register. Firmware watches this
>>> + register and expects bit 0 to be written to 1 once the OS consumes the
>>> + status buffer so it can reuse the record.
>>> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-19 9:53 ` Krzysztof Kozlowski
@ 2025-12-19 10:37 ` Ahmed Tiba
2025-12-19 10:47 ` Ahmed Tiba
1 sibling, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 10:37 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, Ahmed.Tiba
On 19/12/2025 10:53, Krzysztof Kozlowski wrote:
>> On 17/12/2025 12:41, Krzysztof Kozlowski wrote:
>>>> +properties:
>>>> + compatible:
>>>> + const: arm,ras-ffh
>>>
>>> Again ras - what's that? Your patch or binding must explain that.
>>
>> That updated description will explicitly expand the Arm RAS acronym so the
>> compatible string is self-explanatory.
>>
>>>> +
>>>> + reg:
>>>> + minItems: 1
>>>
>>> Why is this flexible?
>>
>> I'll keep `reg` describing the CPER status buffer, cap it at two entries, and
>> document the second entry as the optional doorbell register that some firmware
>> requires before reusing the buffer.
>
> I still do not understand why this is flexible or in other words - why
> second address space appears and disappears.
The second address space is only present for firmware that exposes an ACK register.
Not all platforms require this extra handshake so that address shows up only
when the extra handshake exists. I’ll say that clearly in the binding
so it’s obvious the region is optional.
>>
>>>> + items:
>>>> + - description: CPER status block exposed by firmware
>>>> + - description:
>>>> + Optional 32- or 64-bit acknowledgment register. Firmware watches this
>>>> + register and expects bit 0 to be written to 1 once the OS consumes the
>>>> + status buffer so it can reuse the record.
>>>> +
Best regards,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 10/12] dt-bindings: ras: document estatus provider
2025-12-19 9:53 ` Krzysztof Kozlowski
2025-12-19 10:37 ` Ahmed Tiba
@ 2025-12-19 10:47 ` Ahmed Tiba
1 sibling, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 10:47 UTC (permalink / raw)
To: Krzysztof Kozlowski, linux-acpi, devicetree
Cc: tony.luck, bp, robh, krzk+dt, conor+dt, catalin.marinas, will,
linux-arm-kernel, rafael, linux-doc, Dmitry.Lamerov,
Michael.Zhao2, Ahmed.Tiba
On 19/12/2025 10:53, Krzysztof Kozlowski wrote:
>> On 17/12/2025 12:41, Krzysztof Kozlowski wrote:
>>>> +properties:
>>>> + compatible:
>>>> + const: arm,ras-ffh
>>>
>>> Again ras - what's that? Your patch or binding must explain that.
>>
>> That updated description will explicitly expand the Arm RAS acronym so the
>> compatible string is self-explanatory.
>>
>>>> +
>>>> + reg:
>>>> + minItems: 1
>>>
>>> Why is this flexible?
>>
>> I'll keep `reg` describing the CPER status buffer, cap it at two entries, and
>> document the second entry as the optional doorbell register that some firmware
>> requires before reusing the buffer.
>
> I still do not understand why this is flexible or in other words - why
> second address space appears and disappears.
The second address space is only present for firmware that exposes an ACK register.
Not all platforms require this extra handshake so that address shows up only
when the extra handshake exists. I’ll say that clearly in the binding
so it’s obvious the region is optional.
>>
>>>> + items:
>>>> + - description: CPER status block exposed by firmware
>>>> + - description:
>>>> + Optional 32- or 64-bit acknowledgment register. Firmware watches this
>>>> + register and expects bit 0 to be written to 1 once the OS consumes the
>>>> + status buffer so it can reuse the record.
>>>> +
Best regards,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-19 9:02 ` Ahmed Tiba
@ 2025-12-19 13:00 ` Will Deacon
2025-12-19 17:21 ` Ahmed Tiba
0 siblings, 1 reply; 39+ messages in thread
From: Will Deacon @ 2025-12-19 13:00 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2
On Fri, Dec 19, 2025 at 09:02:35AM +0000, Ahmed Tiba wrote:
> On Thu, 18 Dec 2025 03:19:17PM +0000, Will Deacon wrote:
> > On Thu, Dec 18, 2025 at 01:42:47PM +0000, Ahmed Tiba wrote:
> >> On Thu, 18 Dec 2025 12:13:25PM +0000, Will Deacon wrote:
> >> >> Introduce a platform driver that maps the CPER status block described
> >> >> in DeviceTree, feeds it into the estatus core and handles either IRQ- or
> >> >> poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
> >> >> driver can safely map the shared buffer while copying records.
> >> >>
> >> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> >> >> ---
> >> >> MAINTAINERS | 1 +
> >> >> arch/arm64/include/asm/fixmap.h | 5 +
> >> >> drivers/ras/Kconfig | 14 ++
> >> >> drivers/ras/Makefile | 1 +
> >> >> drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
> >> >> include/linux/estatus.h | 3 +-
> >> >> 6 files changed, 341 insertions(+), 1 deletion(-)
> >> >> create mode 100644 drivers/ras/estatus-dt.c
> >> >>
> >> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> >> index 6b2ef2ddc0c7..5567d5e82053 100644
> >> >> --- a/MAINTAINERS
> >> >> +++ b/MAINTAINERS
> >> >> @@ -21761,6 +21761,7 @@ RAS ERROR STATUS
> >> >> M: Ahmed Tiba <ahmed.tiba@arm.com>
> >> >> S: Maintained
> >> >> F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
> >> >> +F: drivers/ras/estatus-dt.c
> >> >> F: drivers/firmware/efi/estatus.c
> >> >> F: include/linux/estatus.h
> >> >>
> >> >> diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
> >> >> index 65555284446e..85ffba87bab9 100644
> >> >> --- a/arch/arm64/include/asm/fixmap.h
> >> >> +++ b/arch/arm64/include/asm/fixmap.h
> >> >> @@ -64,6 +64,11 @@ enum fixed_addresses {
> >> >> #endif
> >> >> #endif /* CONFIG_ACPI_APEI_GHES */
> >> >>
> >> >> +#ifdef CONFIG_RAS_ESTATUS_DT
> >> >> + /* Used for ESTATUS mapping from assorted contexts */
> >> >> + FIX_ESTATUS_IRQ,
> >> >> +#endif /* CONFIG_RAS_ESTATUS_DT */
> >> >
> >> > Why do we need this in addition to the four existing GHES slots? The DT
> >> > code doesn't use it and I was assuming that the ACPI code would continue
> >> > to use the existing irq; is that not the case?
> >>
> >>
> >> We still need a dedicated slot when only the DT provider is built.
> >> All four GHES slots are defined as part of the ACPI implementation,
> >> so they are not present in a DT-only configuration.
> >>
> >> The estatus core always requests a fixmap index from each provider
> >> before copying a CPER record. As a result, the DT driver must supply
> >> its own slot to return a valid enum value to satisfy the common code.
> >
> > Sorry, but I still don't follow this. The DT code doesn't use the fixmap,
> > does it? It looks like it maps the buffer ahead of time using
> > devm_ioremap_resource() and then the accessors don't use the fixmap
> > index at all, hence the horrible '(void)fixmap_idx;' cast which presumably
> > stops the compiler from complaining about an unused variable.
>
> Correct. The current DT driver keeps the CPER buffer permanently mapped with
> devm_ioremap_resource() and that (void)fixmap_idx; line is just silencing
> the warning. I’ll fix that by dropping the permanent mapping and copying the
> status block via the fixmap entry, so the DT implementation mirrors GHES. That
> gets rid of the cast and makes FIX_ESTATUS_IRQ do real work.
Why can't you just drop FIX_ESTATUS_IRQ entirely? Your original
justification was:
> We still need a dedicated slot when only the DT provider is built.
but as above, the DT driver doesn't actually need it.
Will
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 02/12] ras: add estatus core implementation
2025-12-18 15:42 ` Mauro Carvalho Chehab
@ 2025-12-19 14:35 ` Ahmed Tiba
0 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 14:35 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Wed, Dec 18, 2025 at 04:42:42PM +0100, Mauro Carvalho Chehab wrote:
>> Add estatus.c, hook it into the EFI Makefile, and register
>> the MAINTAINERS entry for the new code. The implementation provides the
>> memory-pool helpers, notifier plumbing, and utility functions that the
>> GHES and DeviceTree providers will reuse in later commits.
>>
>> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> ---
>> MAINTAINERS | 1 +
>> drivers/firmware/efi/Makefile | 1 +
>> drivers/firmware/efi/estatus.c | 560 +++++++++++++++++++++++++++++++++
> If I'm understanding this patch series, you will be basically moving more than
> half of the code from drivers/acpi/apei/ghes.c to drivers/firmware/efi/estatus.c:
>
> drivers/acpi/apei/ghes.c | 1292 ++-------------
> drivers/firmware/efi/estatus.c | 1056 ++++++++++++
>
> $ wc drivers/acpi/apei/ghes.c drivers/firmware/efi/estatus.c -l
> 894 drivers/acpi/apei/ghes.c
> 1056 drivers/firmware/efi/estatus.c
> 1950 total
>
> The way you're doing of adding things first, and then removing on
> separate patches is error prone, makes it hard to review and
> it becomes a lot harder to identify whose are the original authors
> of the code.
I see your point. I tried staging the additions ahead of the removals so the new helpers
were readable, but I can see that makes review and authorship tracking painful.
> This will cause undetected conflicts with already-submitted patches
> that are under review.
>
> You should instead be moving function per function as-is. Then,
> adjust the code to make it more generic.
I did try moving each helper first and doing the cleanup afterwards,
but the series quickly ballooned and the ordering became confusing
once later patches started depending on code that was still in transit.
I’m happy to try again, though I expect to run into the same issue
unless there’s a known pattern to follow. If you have a reference series or a preferred ordering
that you’d recommend, please point me to it so I can align with that approach.
>> 3 files changed, 562 insertions(+)
>> create mode 100644 drivers/firmware/efi/estatus.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 501b6d300aa5..67d79d4e612d 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -21760,6 +21760,7 @@ F: drivers/rapidio/
>> RAS ERROR STATUS
>> M: Ahmed Tiba <ahmed.tiba@arm.com>
>> S: Maintained
>> +F: drivers/firmware/efi/estatus.c
>> F: include/linux/estatus.h
>>
>> RAS INFRASTRUCTURE
>> diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
>> index 8efbcf699e4f..03708d915bcf 100644
>> --- a/drivers/firmware/efi/Makefile
>> +++ b/drivers/firmware/efi/Makefile
>> @@ -20,6 +20,7 @@ obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o
>> obj-$(CONFIG_EFI_ESRT) += esrt.o
>> obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
>> obj-$(CONFIG_UEFI_CPER) += cper.o cper_cxl.o
>> +obj-$(CONFIG_RAS_ESTATUS_CORE) += estatus.o
>> obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o
>> subdir-$(CONFIG_EFI_STUB) += libstub
>> obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o
>> diff --git a/drivers/firmware/efi/estatus.c b/drivers/firmware/efi/estatus.c
>> new file mode 100644
>> index 000000000000..8dae5c73ce27
>> --- /dev/null
>> +++ b/drivers/firmware/efi/estatus.c
>> @@ -0,0 +1,560 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Firmware-first RAS: Generic Error Status Core
>> + *
>> + * Copyright (C) 2025 ARM Ltd.
>> + * Author: Ahmed Tiba <ahmed.tiba@arm.com>
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/cper.h>
>> +#include <linux/ratelimit.h>
>> +#include <linux/vmalloc.h>
>> +#include <linux/llist.h>
>> +#include <linux/genalloc.h>
>> +#include <linux/pci.h>
>> +#include <linux/pfn.h>
>> +#include <linux/aer.h>
>> +#include <linux/nmi.h>
>> +#include <linux/sched/clock.h>
>> +#include <linux/uuid.h>
>> +#include <linux/kconfig.h>
>> +#include <linux/ras.h>
>> +#include <linux/mutex.h>
>> +#include <linux/notifier.h>
>> +#include <linux/workqueue.h>
>> +#include <linux/task_work.h>
>> +#include <ras/ras_event.h>
>> +
>> +#include <linux/estatus.h>
>> +#include <asm/fixmap.h>
>> +
>> +void estatus_pool_region_free(unsigned long addr, u32 size);
>> +
>> +static void estatus_log_hw_error(char level, const char *seq_tag,
>> + const char *name)
>> +{
>> + switch (level) {
>> + case '0':
>> + pr_emerg("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + case '1':
>> + pr_alert("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + case '2':
>> + pr_crit("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + case '3':
>> + pr_err("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + case '4':
>> + pr_warn("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + case '5':
>> + pr_notice("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + case '6':
>> + pr_info("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + default:
>> + pr_debug("%sHardware error from %s\n", seq_tag, name);
>> + break;
>> + }
>> +}
>> +
>> +static inline u32 estatus_len(struct acpi_hest_generic_status *estatus)
>> +{
>> + if (estatus->raw_data_length)
>> + return estatus->raw_data_offset + estatus->raw_data_length;
>> +
>> + return sizeof(*estatus) + estatus->data_length;
>> +}
>> +
>> +#define ESTATUS_PFX "ESTATUS: "
>> +
>> +#define ESTATUS_ESOURCE_PREALLOC_MAX_SIZE_SIZE 65536
>> +
>> +#define ESTATUS_POOL_MIN_ALLOC_ORDER 3
>> +
>> +/* This is just an estimation for memory pool allocation */
>> +#define ESTATUS_CACHE_AVG_SIZE 512
>> +
>> +#define ESTATUS_CACHES_SIZE 4
>> +
>> +#define ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL
>> +/* Prevent too many caches are allocated because of RCU */
>> +#define ESTATUS_CACHE_ALLOCED_MAX (ESTATUS_CACHES_SIZE * 3 / 2)
>> +
>> +#define ESTATUS_CACHE_LEN(estatus_len) \
>> + (sizeof(struct estatus_cache) + (estatus_len))
>> +#define ESTATUS_FROM_CACHE(cache) \
>> + ((struct acpi_hest_generic_status *) \
>> + ((struct estatus_cache *)(cache) + 1))
>> +
>> +#define ESTATUS_NODE_LEN(estatus_len) \
>> + (sizeof(struct estatus_node) + (estatus_len))
>> +#define ESTATUS_FROM_NODE(node) \
>> + ((struct acpi_hest_generic_status *) \
>> + ((struct estatus_node *)(node) + 1))
>> +
>> +#define ESTATUS_VENDOR_ENTRY_LEN(gdata_len) \
>> + (sizeof(struct estatus_vendor_record_entry) + (gdata_len))
>> +#define ESTATUS_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \
>> + ((struct acpi_hest_generic_data *) \
>> + ((struct estatus_vendor_record_entry *)(vendor_entry) + 1))
>> +
>> +static ATOMIC_NOTIFIER_HEAD(estatus_report_chain);
>> +
>> +struct estatus_vendor_record_entry {
>> + struct work_struct work;
>> + int error_severity;
>> + char vendor_record[];
>> +};
>> +
>> +static struct estatus_cache __rcu *estatus_caches[ESTATUS_CACHES_SIZE];
>> +static atomic_t estatus_cache_alloced;
>> +
>> +static int estatus_panic_timeout __read_mostly = 30;
>> +
>> +static struct gen_pool *estatus_pool;
>> +static DEFINE_MUTEX(estatus_pool_mutex);
>> +
>> +static inline const char *estatus_source_name(struct estatus_source *source)
>> +{
>> + if (source->ops && source->ops->get_name)
>> + return source->ops->get_name(source);
>> +
>> + return "unknown";
>> +}
>> +
>> +static inline size_t estatus_source_max_len(struct estatus_source *source)
>> +{
>> + if (source->ops && source->ops->get_max_len)
>> + return source->ops->get_max_len(source);
>> +
>> + return 0;
>> +}
>> +
>> +static inline enum estatus_notify_mode
>> +estatus_source_notify_mode(struct estatus_source *source)
>> +{
>> + if (source->ops && source->ops->get_notify_mode)
>> + return source->ops->get_notify_mode(source);
>> +
>> + return ESTATUS_NOTIFY_ASYNC;
>> +}
>> +
>> +static inline int estatus_source_get_phys(struct estatus_source *source,
>> + phys_addr_t *addr)
>> +{
>> + if (!source->ops || !source->ops->get_phys)
>> + return -EOPNOTSUPP;
>> +
>> + return source->ops->get_phys(source, addr);
>> +}
>> +
>> +static inline int estatus_source_read(struct estatus_source *source,
>> + phys_addr_t addr, void *buf, size_t len,
>> + enum fixed_addresses fixmap_idx)
>> +{
>> + if (!source->ops || !source->ops->read)
>> + return -EOPNOTSUPP;
>> +
>> + return source->ops->read(source, addr, buf, len, fixmap_idx);
>> +}
>> +
>> +static inline int estatus_source_write(struct estatus_source *source,
>> + phys_addr_t addr, const void *buf,
>> + size_t len,
>> + enum fixed_addresses fixmap_idx)
>> +{
>> + if (!source->ops || !source->ops->write)
>> + return -EOPNOTSUPP;
>> +
>> + return source->ops->write(source, addr, buf, len, fixmap_idx);
>> +}
>> +
>> +static inline void estatus_source_ack(struct estatus_source *source)
>> +{
>> + if (source->ops && source->ops->ack)
>> + source->ops->ack(source);
>> +}
>> +
>> +int estatus_pool_init(unsigned int num_ghes)
>> +{
>> + unsigned long addr, len;
>> + int rc = 0;
>> +
>> + mutex_lock(&estatus_pool_mutex);
>> + if (estatus_pool)
>> + goto out_unlock;
>> +
>> + estatus_pool = gen_pool_create(ESTATUS_POOL_MIN_ALLOC_ORDER, -1);
>> + if (!estatus_pool) {
>> + rc = -ENOMEM;
>> + goto out_unlock;
>> + }
>> +
>> + if (!num_ghes)
>> + num_ghes = 1;
>> +
>> + len = ESTATUS_CACHE_AVG_SIZE * ESTATUS_CACHE_ALLOCED_MAX;
>> + len += (num_ghes * ESTATUS_ESOURCE_PREALLOC_MAX_SIZE_SIZE);
>> +
>> + addr = (unsigned long)vmalloc(PAGE_ALIGN(len));
>> + if (!addr) {
>> + rc = -ENOMEM;
>> + goto err_pool_alloc;
>> + }
>> +
>> + rc = gen_pool_add(estatus_pool, addr, PAGE_ALIGN(len), -1);
>> + if (rc)
>> + goto err_pool_add;
>> +
>> +out_unlock:
>> + mutex_unlock(&estatus_pool_mutex);
>> + return rc;
>> +
>> +err_pool_add:
>> + vfree((void *)addr);
>> +err_pool_alloc:
>> + gen_pool_destroy(estatus_pool);
>> + estatus_pool = NULL;
>> + goto out_unlock;
>> +}
>> +
>> +/**
>> + * estatus_pool_region_free - free previously allocated memory
>> + * from the estatus_pool.
>> + * @addr: address of memory to free.
>> + * @size: size of memory to free.
>> + *
>> + * Returns none.
>> + */
>> +void estatus_pool_region_free(unsigned long addr, u32 size)
>> +{
>> + gen_pool_free(estatus_pool, addr, size);
>> +}
>> +EXPORT_SYMBOL_GPL(estatus_pool_region_free);
>> +
>> +/* Check the top-level record header has an appropriate size. */
>> +static int __estatus_check_estatus(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus)
>> +{
>> + u32 len = estatus_len(estatus);
>> + size_t max_len = estatus_source_max_len(source);
>> +
>> + if (len < sizeof(*estatus)) {
>> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Truncated error status block!\n");
>> + return -EIO;
>> + }
>> +
>> + if (max_len && len > max_len) {
>> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Invalid error status block length!\n");
>> + return -EIO;
>> + }
>> +
>> + if (cper_estatus_check_header(estatus)) {
>> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX "Invalid CPER header!\n");
>> + return -EIO;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/* Read the CPER block, returning its address, and header in estatus. */
>> +static int __estatus_peek_estatus(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus,
>> + phys_addr_t *buf_paddr,
>> + enum fixed_addresses fixmap_idx)
>> +{
>> + int rc;
>> +
>> + rc = estatus_source_get_phys(source, buf_paddr);
>> + if (rc) {
>> + *buf_paddr = 0;
>> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX
>> + "Failed to get error status block address for provider %s: %d\n",
>> + estatus_source_name(source), rc);
>> + return rc;
>> + }
>> +
>> + if (!*buf_paddr)
>> + return -ENOENT;
>> +
>> + rc = estatus_source_read(source, *buf_paddr, estatus,
>> + sizeof(*estatus), fixmap_idx);
>> + if (rc)
>> + return rc;
>> +
>> + if (!estatus->block_status) {
>> + *buf_paddr = 0;
>> + return -ENOENT;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int __estatus_read_estatus(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus,
>> + phys_addr_t buf_paddr,
>> + enum fixed_addresses fixmap_idx,
>> + size_t buf_len)
>> +{
>> + int rc;
>> +
>> + rc = estatus_source_read(source, buf_paddr, estatus, buf_len,
>> + fixmap_idx);
>> + if (rc)
>> + return rc;
>> +
>> + if (cper_estatus_check(estatus)) {
>> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX
>> + "Failed to read error status block for provider %s!\n",
>> + estatus_source_name(source));
>> + return -EIO;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int estatus_read_estatus(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus,
>> + phys_addr_t *buf_paddr,
>> + enum fixed_addresses fixmap_idx)
>> +{
>> + int rc;
>> +
>> + rc = __estatus_peek_estatus(source, estatus, buf_paddr, fixmap_idx);
>> + if (rc)
>> + return rc;
>> +
>> + rc = __estatus_check_estatus(source, estatus);
>> + if (rc)
>> + return rc;
>> +
>> + return __estatus_read_estatus(source, estatus, *buf_paddr,
>> + fixmap_idx, estatus_len(estatus));
>> +}
>> +
>> +static void estatus_clear_estatus(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus,
>> + phys_addr_t buf_paddr,
>> + enum fixed_addresses fixmap_idx)
>> +{
>> + int rc;
>> +
>> + estatus->block_status = 0;
>> +
>> + if (!buf_paddr)
>> + return;
>> +
>> + rc = estatus_source_write(source, buf_paddr, estatus,
>> + sizeof(estatus->block_status), fixmap_idx);
>> + if (rc)
>> + pr_warn_ratelimited(FW_WARN ESTATUS_PFX
>> + "Failed to clear error status block for provider %s: %d\n",
>> + estatus_source_name(source), rc);
>> +
>> + estatus_source_ack(source);
>> +}
>> +
>> +static inline int estatus_severity(int severity)
>> +{
>> + switch (severity) {
>> + case CPER_SEV_INFORMATIONAL:
>> + return ESTATUS_SEV_NO;
>> + case CPER_SEV_CORRECTED:
>> + return ESTATUS_SEV_CORRECTED;
>> + case CPER_SEV_RECOVERABLE:
>> + return ESTATUS_SEV_RECOVERABLE;
>> + case CPER_SEV_FATAL:
>> + return ESTATUS_SEV_PANIC;
>> + default:
>> + /* Unknown, go panic */
>> + return ESTATUS_SEV_PANIC;
>> + }
>> +}
>> +
>> +static void __estatus_print_estatus(const char *pfx,
>> + struct estatus_source *source,
>> + const struct acpi_hest_generic_status *estatus)
>> +{
>> + static atomic_t seqno;
>> + unsigned int curr_seqno;
>> + char pfx_seq[64];
>> + char seq_tag[64];
>> + const char *name = estatus_source_name(source);
>> + const char *level = pfx;
>> + char level_char = '4';
>> +
>> + if (!level) {
>> + if (estatus_severity(estatus->error_severity) <=
>> + ESTATUS_SEV_CORRECTED)
>> + level = KERN_WARNING;
>> + else
>> + level = KERN_ERR;
>> + }
>> +
>> + if (level[0] == KERN_SOH_ASCII && level[1])
>> + level_char = level[1];
>> + else if (estatus_severity(estatus->error_severity) > ESTATUS_SEV_CORRECTED)
>> + level_char = '3';
>> +
>> + curr_seqno = atomic_inc_return(&seqno);
>> + snprintf(seq_tag, sizeof(seq_tag), "{%u}" HW_ERR, curr_seqno);
>> + snprintf(pfx_seq, sizeof(pfx_seq), "%s%s", level, seq_tag);
>> + estatus_log_hw_error(level_char, seq_tag, name);
>> + cper_estatus_print(pfx_seq, estatus);
>> +}
>> +
>> +static int estatus_print_estatus(const char *pfx,
>> + struct estatus_source *source,
>> + const struct acpi_hest_generic_status *estatus)
>> +{
>> + /* Not more than 2 messages every 5 seconds */
>> + static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5 * HZ, 2);
>> + static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5 * HZ, 2);
>> + struct ratelimit_state *ratelimit;
>> +
>> + if (estatus_severity(estatus->error_severity) <= ESTATUS_SEV_CORRECTED)
>> + ratelimit = &ratelimit_corrected;
>> + else
>> + ratelimit = &ratelimit_uncorrected;
>> + if (__ratelimit(ratelimit)) {
>> + __estatus_print_estatus(pfx, source, estatus);
>> + return 1;
>> + }
>> + return 0;
>> +}
>> +
>> +/*
>> + * GHES error status reporting throttle, to report more kinds of
>> + * errors, instead of just most frequently occurred errors.
>> + */
>> +static int estatus_cached(struct acpi_hest_generic_status *estatus)
>> +{
>> + u32 len;
>> + int i, cached = 0;
>> + unsigned long long now;
>> + struct estatus_cache *cache;
>> + struct acpi_hest_generic_status *cache_estatus;
>> +
>> + len = estatus_len(estatus);
>> + rcu_read_lock();
>> + for (i = 0; i < ESTATUS_CACHES_SIZE; i++) {
>> + cache = rcu_dereference(estatus_caches[i]);
>> + if (!cache)
>> + continue;
>> + if (len != cache->estatus_len)
>> + continue;
>> + cache_estatus = ESTATUS_FROM_CACHE(cache);
>> + if (memcmp(estatus, cache_estatus, len))
>> + continue;
>> + atomic_inc(&cache->count);
>> + now = sched_clock();
>> + if (now - cache->time_in < ESTATUS_IN_CACHE_MAX_NSEC)
>> + cached = 1;
>> + break;
>> + }
>> + rcu_read_unlock();
>> + return cached;
>> +}
>> +
>> +static struct estatus_cache *estatus_cache_alloc(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus)
>> +{
>> + int alloced;
>> + u32 len, cache_len;
>> + struct estatus_cache *cache;
>> + struct acpi_hest_generic_status *cache_estatus;
>> +
>> + alloced = atomic_add_return(1, &estatus_cache_alloced);
>> + if (alloced > ESTATUS_CACHE_ALLOCED_MAX) {
>> + atomic_dec(&estatus_cache_alloced);
>> + return NULL;
>> + }
>> + len = estatus_len(estatus);
>> + cache_len = ESTATUS_CACHE_LEN(len);
>> + cache = (void *)gen_pool_alloc(estatus_pool, cache_len);
>> + if (!cache) {
>> + atomic_dec(&estatus_cache_alloced);
>> + return NULL;
>> + }
>> + cache_estatus = ESTATUS_FROM_CACHE(cache);
>> + memcpy(cache_estatus, estatus, len);
>> + cache->estatus_len = len;
>> + atomic_set(&cache->count, 0);
>> + cache->source = source;
>> + cache->time_in = sched_clock();
>> + return cache;
>> +}
>> +
>> +static void estatus_cache_rcu_free(struct rcu_head *head)
>> +{
>> + struct estatus_cache *cache;
>> + u32 len;
>> +
>> + cache = container_of(head, struct estatus_cache, rcu);
>> + len = estatus_len(ESTATUS_FROM_CACHE(cache));
>> + len = ESTATUS_CACHE_LEN(len);
>> + gen_pool_free(estatus_pool, (unsigned long)cache, len);
>> + atomic_dec(&estatus_cache_alloced);
>> +}
>> +
>> +static void estatus_cache_add(struct estatus_source *source,
>> + struct acpi_hest_generic_status *estatus)
>> +{
>> + unsigned long long now, duration, period, max_period = 0;
>> + struct estatus_cache *cache, *new_cache;
>> + struct estatus_cache __rcu *victim;
>> + int i, slot = -1, count;
>> +
>> + new_cache = estatus_cache_alloc(source, estatus);
>> + if (!new_cache)
>> + return;
>> +
>> + rcu_read_lock();
>> + now = sched_clock();
>> + for (i = 0; i < ESTATUS_CACHES_SIZE; i++) {
>> + cache = rcu_dereference(estatus_caches[i]);
>> + if (!cache) {
>> + slot = i;
>> + break;
>> + }
>> + duration = now - cache->time_in;
>> + if (duration >= ESTATUS_IN_CACHE_MAX_NSEC) {
>> + slot = i;
>> + break;
>> + }
>> + count = atomic_read(&cache->count);
>> + period = duration;
>> + do_div(period, (count + 1));
>> + if (period > max_period) {
>> + max_period = period;
>> + slot = i;
>> + }
>> + }
>> + rcu_read_unlock();
>> +
>> + if (slot != -1) {
>> + /*
>> + * Use release semantics to ensure that estatus_cached()
>> + * running on another CPU will see the updated cache fields if
>> + * it can see the new value of the pointer.
>> + */
>> + victim = xchg_release(&estatus_caches[slot],
>> + RCU_INITIALIZER(new_cache));
>> +
>> + /*
>> + * At this point, victim may point to a cached item different
>> + * from the one based on which we selected the slot. Instead of
>> + * going to the loop again to pick another slot, let's just
>> + * drop the other item anyway: this may cause a false cache
>> + * miss later on, but that won't cause any problems.
>> + */
>> + if (victim)
>> + call_rcu(&unrcu_pointer(victim)->rcu,
>> + estatus_cache_rcu_free);
>> + }
>> +}
>> --
>> 2.43.0
>>
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-18 16:04 ` Mauro Carvalho Chehab
@ 2025-12-19 14:49 ` Ahmed Tiba
2025-12-19 15:30 ` Mauro Carvalho Chehab
0 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 14:49 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Wed, Dec 18, 2025 at 05:04:53PM +0100, Mauro Carvalho Chehab wrote:
>> Teach the estatus core how to walk CPER records and expose the vendor
>> record notification path. This adds the section iteration helpers,
>> the logging helpers that mirror the GHES behaviour, and the deferred
>> work used to hand vendor GUIDs to interested drivers. No users switch
>> over yet; this simply moves the common logic out of GHES so the next
>> patches can wire it up.
>>
>> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>
>...
>
>> +static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
>
> Huh?
>
> This is a CPER record from GHES. Why are you moving CPER code out
> of ghes.c, placing in a file named estatus.c? Doesn't make much
> sense on my eyes...
>
> Same applies to to other GHES CPER record types.
GHES still fills in the CPER record, but the parsing and logging logic is
shared with the new DeviceTree provider so I pulled those helpers into the
estatus core. Both providers already call into the same notifier chain and
memory-pool helpers; this patch just moves the generic CPER walking routines
next to the rest of the common code so the DT path doesn’t have to grow its
own copy. If you’d prefer a different file layout or naming to make that
intent clearer, I’m happy to adjust.
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-19 14:49 ` Ahmed Tiba
@ 2025-12-19 15:30 ` Mauro Carvalho Chehab
2025-12-19 18:11 ` Ahmed Tiba
0 siblings, 1 reply; 39+ messages in thread
From: Mauro Carvalho Chehab @ 2025-12-19 15:30 UTC (permalink / raw)
To: Ahmed Tiba
Cc: Mauro Carvalho Chehab, linux-acpi, devicetree, tony.luck, bp,
robh, krzk+dt, conor+dt, catalin.marinas, will, linux-arm-kernel,
rafael, linux-doc, Dmitry.Lamerov, Michael.Zhao2
On Fri, Dec 19, 2025 at 02:49:02PM +0000, Ahmed Tiba wrote:
>
> On Wed, Dec 18, 2025 at 05:04:53PM +0100, Mauro Carvalho Chehab wrote:
>
> >> Teach the estatus core how to walk CPER records and expose the vendor
> >> record notification path. This adds the section iteration helpers,
> >> the logging helpers that mirror the GHES behaviour, and the deferred
> >> work used to hand vendor GUIDs to interested drivers. No users switch
> >> over yet; this simply moves the common logic out of GHES so the next
> >> patches can wire it up.
> >>
> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> >
> >...
> >
> >> +static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
> >
> > Huh?
> >
> > This is a CPER record from GHES. Why are you moving CPER code out
> > of ghes.c, placing in a file named estatus.c? Doesn't make much
> > sense on my eyes...
> >
> > Same applies to to other GHES CPER record types.
>
> GHES still fills in the CPER record, but the parsing and logging logic is
> shared with the new DeviceTree provider so I pulled those helpers into the
> estatus core.
I see, but this is not really estatus core. Instead, it is part of GHES CPER
handling logic, which is defined at ACPI and UEFI specs. moving it to estatus
sounds odd, at least on my eyes.
Perhaps I'm failing to see where at ACPI/UEFI specs how CPER would be
integrated with an OpenFirmware approach to handle CPER without GHES.
Care to point to the relevant specs, if any?
> Both providers already call into the same notifier chain and
> memory-pool helpers; this patch just moves the generic CPER walking routines
> next to the rest of the common code so the DT path doesn’t have to grow its
> own copy. If you’d prefer a different file layout or naming to make that
> intent clearer, I’m happy to adjust.
Moving the code from ghes.c to estatus.c or to elsewhere shouldn't make any
difference, as the DT handling logic could simply be calling the functions
from ghes.c (or estatus.c). I fail to see why they need to be moved.
--
Thanks,
Mauro
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 11/12] ras: add DeviceTree estatus provider driver
2025-12-19 13:00 ` Will Deacon
@ 2025-12-19 17:21 ` Ahmed Tiba
0 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 17:21 UTC (permalink / raw)
To: Will Deacon
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Fri, 19 Dec 2025 13:00:08 +0000, Will Deacon wrote:
>On Fri, Dec 19, 2025 at 09:02:35AM +0000, Ahmed Tiba wrote:
>> On Thu, 18 Dec 2025 03:19:17PM +0000, Will Deacon wrote:
>> > On Thu, Dec 18, 2025 at 01:42:47PM +0000, Ahmed Tiba wrote:
>> >> On Thu, 18 Dec 2025 12:13:25PM +0000, Will Deacon wrote:
>> >> >> Introduce a platform driver that maps the CPER status block described
>> >> >> in DeviceTree, feeds it into the estatus core and handles either IRQ- or
>> >> >> poll-driven notifications. Arm64 gains a FIX_ESTATUS_IRQ slot so the
>> >> >> driver can safely map the shared buffer while copying records.
>> >> >>
>> >> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> >> >> ---
>> >> >> MAINTAINERS | 1 +
>> >> >> arch/arm64/include/asm/fixmap.h | 5 +
>> >> >> drivers/ras/Kconfig | 14 ++
>> >> >> drivers/ras/Makefile | 1 +
>> >> >> drivers/ras/estatus-dt.c | 318 ++++++++++++++++++++++++++++++++
>> >> >> include/linux/estatus.h | 3 +-
>> >> >> 6 files changed, 341 insertions(+), 1 deletion(-)
>> >> >> create mode 100644 drivers/ras/estatus-dt.c
>> >> >>
>> >> >> diff --git a/MAINTAINERS b/MAINTAINERS
>> >> >> index 6b2ef2ddc0c7..5567d5e82053 100644
>> >> >> --- a/MAINTAINERS
>> >> >> +++ b/MAINTAINERS
>> >> >> @@ -21761,6 +21761,7 @@ RAS ERROR STATUS
>> >> >> M: Ahmed Tiba <ahmed.tiba@arm.com>
>> >> >> S: Maintained
>> >> >> F: Documentation/devicetree/bindings/ras/arm,ras-ffh.yaml
>> >> >> +F: drivers/ras/estatus-dt.c
>> >> >> F: drivers/firmware/efi/estatus.c
>> >> >> F: include/linux/estatus.h
>> >> >>
>> >> >> diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h
>> >> >> index 65555284446e..85ffba87bab9 100644
>> >> >> --- a/arch/arm64/include/asm/fixmap.h
>> >> >> +++ b/arch/arm64/include/asm/fixmap.h
>> >> >> @@ -64,6 +64,11 @@ enum fixed_addresses {
>> >> >> #endif
>> >> >> #endif /* CONFIG_ACPI_APEI_GHES */
>> >> >>
>> >> >> +#ifdef CONFIG_RAS_ESTATUS_DT
>> >> >> + /* Used for ESTATUS mapping from assorted contexts */
>> >> >> + FIX_ESTATUS_IRQ,
>> >> >> +#endif /* CONFIG_RAS_ESTATUS_DT */
>> >> >
>> >> > Why do we need this in addition to the four existing GHES slots? The DT
>> >> > code doesn't use it and I was assuming that the ACPI code would continue
>> >> > to use the existing irq; is that not the case?
>> >>
>> >>
>> >> We still need a dedicated slot when only the DT provider is built.
>> >> All four GHES slots are defined as part of the ACPI implementation,
>> >> so they are not present in a DT-only configuration.
>> >>
>> >> The estatus core always requests a fixmap index from each provider
>> >> before copying a CPER record. As a result, the DT driver must supply
>> >> its own slot to return a valid enum value to satisfy the common code.
>> >
>> > Sorry, but I still don't follow this. The DT code doesn't use the fixmap,
>> > does it? It looks like it maps the buffer ahead of time using
>> > devm_ioremap_resource() and then the accessors don't use the fixmap
>> > index at all, hence the horrible '(void)fixmap_idx;' cast which presumably
>> > stops the compiler from complaining about an unused variable.
>>
>> Correct. The current DT driver keeps the CPER buffer permanently mapped with
>> devm_ioremap_resource() and that (void)fixmap_idx; line is just silencing
>> the warning. I’ll fix that by dropping the permanent mapping and copying the
>> status block via the fixmap entry, so the DT implementation mirrors GHES. That
>> gets rid of the cast and makes FIX_ESTATUS_IRQ do real work.
> Why can't you just drop FIX_ESTATUS_IRQ entirely? Your original
> justification was:
>
>> We still need a dedicated slot when only the DT provider is built.
>
> but as above, the DT driver doesn't actually need it.
The DT provider is intended to mirror the GHES path, so both need to supply a
fixmap slot to satisfy the estatus core interface.
I could drop FIX_ESTATUS_IRQ entirely, but that would require relaxing the
estatus core so a provider can explicitly indicate that it does not use a
fixmap and instead relies on a permanent mapping. If we want to stay aligned
with the ACPI/GHES model, keeping the fixmap-based approach seems preferable.
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-19 15:30 ` Mauro Carvalho Chehab
@ 2025-12-19 18:11 ` Ahmed Tiba
2025-12-22 8:13 ` Mauro Carvalho Chehab
0 siblings, 1 reply; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-19 18:11 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Fri, Dec 19, 2025 at 04:30:40PM +0100, Mauro Carvalho Chehab wrote:
>On Fri, Dec 19, 2025 at 02:49:02PM +0000, Ahmed Tiba wrote:
>>
>> On Wed, Dec 18, 2025 at 05:04:53PM +0100, Mauro Carvalho Chehab wrote:
>>
>> >> Teach the estatus core how to walk CPER records and expose the vendor
>> >> record notification path. This adds the section iteration helpers,
>> >> the logging helpers that mirror the GHES behaviour, and the deferred
>> >> work used to hand vendor GUIDs to interested drivers. No users switch
>> >> over yet; this simply moves the common logic out of GHES so the next
>> >> patches can wire it up.
>> >>
>> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> >
>> >...
>> >
>> >> +static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
>> >
>> > Huh?
>> >
>> > This is a CPER record from GHES. Why are you moving CPER code out
>> > of ghes.c, placing in a file named estatus.c? Doesn't make much
>> > sense on my eyes...
>> >
>> > Same applies to to other GHES CPER record types.
>>
>> GHES still fills in the CPER record, but the parsing and logging logic is
>> shared with the new DeviceTree provider so I pulled those helpers into the
>> estatus core.
>
> I see, but this is not really estatus core. Instead, it is part of GHES CPER
> handling logic, which is defined at ACPI and UEFI specs. moving it to estatus
> sounds odd, at least on my eyes.
>
> Perhaps I'm failing to see where at ACPI/UEFI specs how CPER would be
> integrated with an OpenFirmware approach to handle CPER without GHES.
> Care to point to the relevant specs, if any?
ACPI/APEI (via GHES) defines how CPER records are discovered and notified on ACPI systems,
but there is no ACPI or UEFI-defined equivalent for OpenFirmware/DeviceTree platforms.
UEFI standardises the CPER record format itself, not the transport or discovery mechanism.
On non-ACPI systems we still receive the same UEFI-defined CPER payload
from firmware, but Linux needs a different, platform-specific contract
to locate and acknowledge it. The DT binding is a Linux-side description
of that contract rather than something defined by ACPI/UEFI.
>> Both providers already call into the same notifier chain and
>> memory-pool helpers; this patch just moves the generic CPER walking routines
>> next to the rest of the common code so the DT path doesn’t have to grow its
>> own copy. If you’d prefer a different file layout or naming to make that
>> intent clearer, I’m happy to adjust.
> Moving the code from ghes.c to estatus.c or to elsewhere shouldn't make any
> difference, as the DT handling logic could simply be calling the functions
> from ghes.c (or estatus.c). I fail to see why they need to be moved.
The motivation is to provide a shared implementation for non-ACPI providers,
so that the DT path does not depend on ACPI/APEI.
While the helpers currently live in ghes.c, they are CPER-specific and do not rely on ACPI tables,
APEI infrastructure, or GHES notification semantics. Keeping them there effectively makes GHES
the only place those helpers can live, even though the logic itself is provider-agnostic.
By moving the CPER parsing and logging pieces into a common location,
both GHES and the DT provider can reuse the same implementation,
while the ACPI-specific discovery and notification code remains under drivers/acpi/apei/.
This avoids having the DT provider reach into GHES internals or duplicate CPER handling code.
If the current naming or file layout makes that separation unclear, I’m happy to adjust it.
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 00/12] ras: share firmware-first estatus handling
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
` (11 preceding siblings ...)
2025-12-17 11:28 ` [PATCH 12/12] doc: ras: describe firmware-first estatus flow Ahmed Tiba
@ 2025-12-21 1:35 ` Borislav Petkov
2025-12-29 11:54 ` Ahmed Tiba
12 siblings, 1 reply; 39+ messages in thread
From: Borislav Petkov @ 2025-12-21 1:35 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2
On Wed, Dec 17, 2025 at 11:28:33AM +0000, Ahmed Tiba wrote:
> Platforms that rely on firmware-first RAS today only get the full Linux
> handling pipeline when the records arrive through ACPI/APEI GHES. This
> series lifts the generic parts of GHES into a reusable estatus core, wires
Why is this thing called "error status"?
Why is error status so significant so that you need to call it a thing,
much less a "core"?
It looks like you basically want to dump error records from a system
which doesn't support GHES into the common path so they get decoded?
I mean, I'm only guessing because I don't get any wiser from this text.
So how about you give the high-level, practical use spiel first? What's
the use case?
Then we can talk implementation details.
Thx.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 02/12] ras: add estatus core implementation
2025-12-17 11:28 ` [PATCH 02/12] ras: add estatus core implementation Ahmed Tiba
2025-12-18 15:42 ` Mauro Carvalho Chehab
@ 2025-12-21 19:31 ` kernel test robot
1 sibling, 0 replies; 39+ messages in thread
From: kernel test robot @ 2025-12-21 19:31 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree
Cc: oe-kbuild-all, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, ahmed.tiba
Hi Ahmed,
kernel test robot noticed the following build warnings:
[auto build test WARNING on efi/next]
[also build test WARNING on rafael-pm/linux-next rafael-pm/bleeding-edge robh/for-next linus/master v6.19-rc1 next-20251219]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ahmed-Tiba/ras-add-estatus-core-interfaces/20251217-200718
base: https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git next
patch link: https://lore.kernel.org/r/20251217112845.1814119-3-ahmed.tiba%40arm.com
patch subject: [PATCH 02/12] ras: add estatus core implementation
config: x86_64-rhel-9.4 (https://download.01.org/0day-ci/archive/20251221/202512212012.arq4zqYv-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251221/202512212012.arq4zqYv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512212012.arq4zqYv-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/firmware/efi/estatus.c:505:13: warning: 'estatus_cache_add' defined but not used [-Wunused-function]
505 | static void estatus_cache_add(struct estatus_source *source,
| ^~~~~~~~~~~~~~~~~
drivers/firmware/efi/estatus.c:435:12: warning: 'estatus_cached' defined but not used [-Wunused-function]
435 | static int estatus_cached(struct acpi_hest_generic_status *estatus)
| ^~~~~~~~~~~~~~
drivers/firmware/efi/estatus.c:411:12: warning: 'estatus_print_estatus' defined but not used [-Wunused-function]
411 | static int estatus_print_estatus(const char *pfx,
| ^~~~~~~~~~~~~~~~~~~~~
drivers/firmware/efi/estatus.c:340:13: warning: 'estatus_clear_estatus' defined but not used [-Wunused-function]
340 | static void estatus_clear_estatus(struct estatus_source *source,
| ^~~~~~~~~~~~~~~~~~~~~
drivers/firmware/efi/estatus.c:321:12: warning: 'estatus_read_estatus' defined but not used [-Wunused-function]
321 | static int estatus_read_estatus(struct estatus_source *source,
| ^~~~~~~~~~~~~~~~~~~~
>> drivers/firmware/efi/estatus.c:117:12: warning: 'estatus_panic_timeout' defined but not used [-Wunused-variable]
117 | static int estatus_panic_timeout __read_mostly = 30;
| ^~~~~~~~~~~~~~~~~~~~~
In file included from arch/x86/include/asm/uprobes.h:13,
from include/linux/uprobes.h:66,
from include/linux/mm_types.h:16,
from include/linux/mmzone.h:22,
from include/linux/gfp.h:7,
from include/linux/xarray.h:16,
from include/linux/list_lru.h:14,
from include/linux/fs/super_types.h:7,
from include/linux/fs/super.h:5,
from include/linux/fs.h:5,
from include/linux/seq_file.h:11,
from include/linux/seq_buf.h:7,
from include/linux/trace_seq.h:5,
from include/linux/cper.h:13,
from drivers/firmware/efi/estatus.c:10:
drivers/firmware/efi/estatus.c:106:29: warning: 'estatus_report_chain' defined but not used [-Wunused-variable]
106 | static ATOMIC_NOTIFIER_HEAD(estatus_report_chain);
| ^~~~~~~~~~~~~~~~~~~~
include/linux/notifier.h:116:37: note: in definition of macro 'ATOMIC_NOTIFIER_HEAD'
116 | struct atomic_notifier_head name = \
| ^~~~
vim +/estatus_panic_timeout +117 drivers/firmware/efi/estatus.c
116
> 117 static int estatus_panic_timeout __read_mostly = 30;
118
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-17 11:28 ` [PATCH 03/12] ras: add estatus vendor handling and processing Ahmed Tiba
2025-12-18 16:04 ` Mauro Carvalho Chehab
@ 2025-12-21 23:39 ` kernel test robot
1 sibling, 0 replies; 39+ messages in thread
From: kernel test robot @ 2025-12-21 23:39 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree
Cc: oe-kbuild-all, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, ahmed.tiba
Hi Ahmed,
kernel test robot noticed the following build errors:
[auto build test ERROR on efi/next]
[also build test ERROR on rafael-pm/linux-next rafael-pm/bleeding-edge robh/for-next arm64/for-next/core linus/master v6.19-rc1 next-20251219]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ahmed-Tiba/ras-add-estatus-core-interfaces/20251217-200718
base: https://git.kernel.org/pub/scm/linux/kernel/git/efi/efi.git next
patch link: https://lore.kernel.org/r/20251217112845.1814119-4-ahmed.tiba%40arm.com
patch subject: [PATCH 03/12] ras: add estatus vendor handling and processing
config: x86_64-rhel-9.4 (https://download.01.org/0day-ci/archive/20251222/202512220010.sdcS5LYV-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251222/202512220010.sdcS5LYV-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512220010.sdcS5LYV-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/firmware/efi/estatus.c: In function 'estatus_source_fixmap':
>> drivers/firmware/efi/estatus.c:126:24: error: 'FIX_HOLE' undeclared (first use in this function)
126 | return FIX_HOLE;
| ^~~~~~~~
drivers/firmware/efi/estatus.c:126:24: note: each undeclared identifier is reported only once for each function it appears in
drivers/firmware/efi/estatus.c: In function 'estatus_handle_arm_hw_error':
drivers/firmware/efi/estatus.c:656:9: error: too few arguments to function 'log_arm_hw_error'
656 | log_arm_hw_error(err);
| ^~~~~~~~~~~~~~~~
In file included from drivers/firmware/efi/estatus.c:23:
include/linux/ras.h:27:6: note: declared here
27 | void log_arm_hw_error(struct cper_sec_proc_arm *err, const u8 sev);
| ^~~~~~~~~~~~~~~~
In file included from drivers/firmware/efi/estatus.c:30:
drivers/firmware/efi/estatus.c: In function 'estatus_do_proc':
include/linux/estatus.h:231:71: warning: passing argument 2 of 'estatus_section_iter_next' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
231 | ((_section) = estatus_section_iter_next(&__estatus_iter, (_estatus))); \
| ^~~~~~~~~~
drivers/firmware/efi/estatus.c:817:9: note: in expansion of macro 'estatus_for_each_section'
817 | estatus_for_each_section(estatus, gdata) {
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/estatus.h:208:51: note: expected 'struct acpi_hest_generic_status *' but argument is of type 'const struct acpi_hest_generic_status *'
208 | estatus_generic_status *estatus)
| ^
drivers/firmware/efi/estatus.c: At top level:
drivers/firmware/efi/estatus.c:947:13: warning: 'estatus_print_queued_estatus' defined but not used [-Wunused-function]
947 | static void estatus_print_queued_estatus(void)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/firmware/efi/estatus.c:118:12: warning: 'estatus_panic_timeout' defined but not used [-Wunused-variable]
118 | static int estatus_panic_timeout __read_mostly = 30;
| ^~~~~~~~~~~~~~~~~~~~~
vim +/FIX_HOLE +126 drivers/firmware/efi/estatus.c
122
123 static enum fixed_addresses estatus_source_fixmap(struct estatus_source *source)
124 {
125 if (WARN_ON_ONCE(!source->fixmap_idx))
> 126 return FIX_HOLE;
127
128 return source->fixmap_idx;
129 }
130
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-19 18:11 ` Ahmed Tiba
@ 2025-12-22 8:13 ` Mauro Carvalho Chehab
2025-12-29 15:01 ` Ahmed Tiba
0 siblings, 1 reply; 39+ messages in thread
From: Mauro Carvalho Chehab @ 2025-12-22 8:13 UTC (permalink / raw)
To: Ahmed Tiba
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, linux-efi
Em Fri, 19 Dec 2025 18:11:54 +0000
Ahmed Tiba <ahmed.tiba@arm.com> escreveu:
> On Fri, Dec 19, 2025 at 04:30:40PM +0100, Mauro Carvalho Chehab wrote:
> >On Fri, Dec 19, 2025 at 02:49:02PM +0000, Ahmed Tiba wrote:
> >>
> >> On Wed, Dec 18, 2025 at 05:04:53PM +0100, Mauro Carvalho Chehab wrote:
> >>
> >> >> Teach the estatus core how to walk CPER records and expose the vendor
> >> >> record notification path. This adds the section iteration helpers,
> >> >> the logging helpers that mirror the GHES behaviour, and the deferred
> >> >> work used to hand vendor GUIDs to interested drivers. No users switch
> >> >> over yet; this simply moves the common logic out of GHES so the next
> >> >> patches can wire it up.
> >> >>
> >> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> >> >
> >> >...
> >> >
> >> >> +static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
> >> >
> >> > Huh?
> >> >
> >> > This is a CPER record from GHES. Why are you moving CPER code out
> >> > of ghes.c, placing in a file named estatus.c? Doesn't make much
> >> > sense on my eyes...
> >> >
> >> > Same applies to to other GHES CPER record types.
> >>
> >> GHES still fills in the CPER record, but the parsing and logging logic is
> >> shared with the new DeviceTree provider so I pulled those helpers into the
> >> estatus core.
> >
> > I see, but this is not really estatus core. Instead, it is part of GHES CPER
> > handling logic, which is defined at ACPI and UEFI specs. moving it to estatus
> > sounds odd, at least on my eyes.
> >
> > Perhaps I'm failing to see where at ACPI/UEFI specs how CPER would be
> > integrated with an OpenFirmware approach to handle CPER without GHES.
> > Care to point to the relevant specs, if any?
>
> ACPI/APEI (via GHES) defines how CPER records are discovered and notified on ACPI systems,
> but there is no ACPI or UEFI-defined equivalent for OpenFirmware/DeviceTree platforms.
> UEFI standardises the CPER record format itself, not the transport or discovery mechanism.
>
> On non-ACPI systems we still receive the same UEFI-defined CPER payload
> from firmware, but Linux needs a different, platform-specific contract
> to locate and acknowledge it. The DT binding is a Linux-side description
> of that contract rather than something defined by ACPI/UEFI.
That's where I'm failing to understand: CPER is part of UEFI spec, and
the only deliverable mechanism I'm aware of for CPER is via GHES or
GHESv2 - e.g. via ACPI.
Within the scope of https://uefi.org/specifications, I'm failing
to see any other deliverable mechanism.
> >> Both providers already call into the same notifier chain and
> >> memory-pool helpers; this patch just moves the generic CPER walking routines
> >> next to the rest of the common code so the DT path doesn’t have to grow its
> >> own copy. If you’d prefer a different file layout or naming to make that
> >> intent clearer, I’m happy to adjust.
>
> > Moving the code from ghes.c to estatus.c or to elsewhere shouldn't make any
> > difference, as the DT handling logic could simply be calling the functions
> > from ghes.c (or estatus.c). I fail to see why they need to be moved.
>
> The motivation is to provide a shared implementation for non-ACPI providers,
> so that the DT path does not depend on ACPI/APEI.
>
> While the helpers currently live in ghes.c, they are CPER-specific and do not rely on ACPI tables,
> APEI infrastructure, or GHES notification semantics. Keeping them there effectively makes GHES
> the only place those helpers can live, even though the logic itself is provider-agnostic.
The logic is related to GHES, as this seems to be the only standardized
mechanism to report CPER records. As it is part of APEI, get_maintainers
points to the people that have been maintaining it as:
$ ./scripts/get_maintainer.pl -f ./drivers/acpi/apei/ghes.c
"Rafael J. Wysocki" <rafael@kernel.org> (maintainer:ACPI APEI,commit_signer:6/13=46%)
Tony Luck <tony.luck@intel.com> (reviewer:ACPI APEI,commit_signer:3/13=23%)
Borislav Petkov <bp@alien8.de> (reviewer:ACPI APEI,removed_lines:5/62=8%)
Hanjun Guo <guohanjun@huawei.com> (reviewer:ACPI APEI,commit_signer:4/13=31%)
Mauro Carvalho Chehab <mchehab@kernel.org> (reviewer:ACPI APEI,authored:1/13=8%,removed_lines:6/62=10%)
Shuai Xue <xueshuai@linux.alibaba.com> (reviewer:ACPI APEI,commit_signer:5/13=38%,authored:2/13=15%,added_lines:56/218=26%,removed_lines:34/62=55%)
Len Brown <lenb@kernel.org> (reviewer:ACPI)
Jonathan Cameron <Jonathan.Cameron@huawei.com> (commit_signer:5/13=38%)
Breno Leitao <leitao@debian.org> (authored:2/13=15%,added_lines:38/218=17%)
Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com> (authored:2/13=15%,added_lines:103/218=47%)
Ankit Agrawal <ankita@nvidia.com> (authored:1/13=8%,removed_lines:6/62=10%)
Jason Tian <jason@os.amperecomputing.com> (removed_lines:7/62=11%)
linux-acpi@vger.kernel.org (open list:ACPI APEI)
linux-kernel@vger.kernel.org (open list)
Moving it elsewhere would make it confusing, as the expected deliverable
mechanism for CPER is via GHES - as this is the only one defined at the
uefi.org specs.
While it might be moved to EFI and placed under cper.c,
get_maintainers.pl would point to:
$ ./scripts/get_maintainer.pl -f ./drivers/firmware/efi/cper.c
Ard Biesheuvel <ardb@kernel.org> (maintainer:EXTENSIBLE FIRMWARE INTERFACE (EFI))
linux-efi@vger.kernel.org (open list:EXTENSIBLE FIRMWARE INTERFACE (EFI))
linux-kernel@vger.kernel.org (open list)
which is not the people that have been maintaining RAS.
Placing it under a "estatus.c" file would make it completely
dissociated with UEFI/ACPI specs, as this name means nothing at
the specs.
Also, adding a new maintainer's entry won't make any sense, as the
people that currently reviews and maintains GHES/CPER records
should be kept.
> By moving the CPER parsing and logging pieces into a common location,
> both GHES and the DT provider can reuse the same implementation,
> while the ACPI-specific discovery and notification code remains under drivers/acpi/apei/.
> This avoids having the DT provider reach into GHES internals or duplicate CPER handling code.
As Boris mentioned on patch 00/12, we need to better understand
the high level scenario, as it is still not clear to me how a
firmware-first notification would happen without ACPI.
> If the current naming or file layout makes that separation unclear, I’m happy to adjust it.
Thanks,
Mauro
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 00/12] ras: share firmware-first estatus handling
2025-12-21 1:35 ` [PATCH 00/12] ras: share firmware-first estatus handling Borislav Petkov
@ 2025-12-29 11:54 ` Ahmed Tiba
0 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-29 11:54 UTC (permalink / raw)
To: Borislav Petkov
Cc: linux-acpi, devicetree, tony.luck, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, Ahmed.Tiba
On Sun, Dec 21, 2025 at 02:35:34 +0100, Borislav Petkov wrote:
> On Wed, Dec 17, 2025 at 11:28:33AM +0000, Ahmed Tiba wrote:
>> Platforms that rely on firmware-first RAS today only get the full Linux
>> handling pipeline when the records arrive through ACPI/APEI GHES. This
>> series lifts the generic parts of GHES into a reusable estatus core, wires
>
> Why is this thing called "error status"?
By “error status” I’m referring to the UEFI CPER Generic Error Status block,
which is the standard firmware-produced error payload that Linux already
consumes via GHES on ACPI systems. I’m not introducing a new error model
here; the intent is to reuse the existing CPER decoding and handling once
that payload exists.
> Why is error status so significant so that you need to call it a thing,
> much less a "core"?
The reason this shows up as a separate “core” is that CPER parsing,
logging, and vendor dispatch are provider-agnostic once a Generic Error
Status block exists, independent of how it was discovered or notified.
> It looks like you basically want to dump error records from a system
> which doesn't support GHES into the common path so they get decoded?
>
> I mean, I'm only guessing because I don't get any wiser from this text.
>
> So how about you give the high-level, practical use spiel first? What's
> the use case?
The practical use case is firmware-first RAS platforms that emit CPER
records but do not use ACPI/APEI GHES for discovery or notification. Today,
those platforms either have to duplicate CPER parsing logic or miss out on
the common Linux RAS handling (standard logging, memory failure flow,
vendor notification paths). As a result, the full firmware-first RAS
pipeline effectively only works when CPER arrives through GHES.
GHES remains one transport for delivering CPER records, but this
series separates the transport from the decoding so that other firmware-
first providers can reuse the same handling without duplicating code or
depending on ACPI/APEI internals.
As far as I can tell from the scope of https://uefi.org/specifications,
the UEFI specifications don’t define a notification mechanism for
DeviceTree systems—only the ACPI/APEI path is spelled out. Right now the DT
transport is described solely by the binding in patch 9/12. If there’s a
better place to document or name this, I’d appreciate guidance
so it’s clear how firmware-first notification happens on DT outside ACPI.
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/12] ras: add estatus vendor handling and processing
2025-12-22 8:13 ` Mauro Carvalho Chehab
@ 2025-12-29 15:01 ` Ahmed Tiba
0 siblings, 0 replies; 39+ messages in thread
From: Ahmed Tiba @ 2025-12-29 15:01 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: linux-acpi, devicetree, tony.luck, bp, robh, krzk+dt, conor+dt,
catalin.marinas, will, linux-arm-kernel, rafael, linux-doc,
Dmitry.Lamerov, Michael.Zhao2, linux-efi, Ahmed.Tiba
On Mon, Dec 22, 2025 at 09:13:34 +0100, Mauro Carvalho Chehab wrote:
>Em Fri, 19 Dec 2025 18:11:54 +0000
>Ahmed Tiba <ahmed.tiba@arm.com> escreveu:
>
>> On Fri, Dec 19, 2025 at 04:30:40PM +0100, Mauro Carvalho Chehab wrote:
>> >On Fri, Dec 19, 2025 at 02:49:02PM +0000, Ahmed Tiba wrote:
>> >>
>> >> On Wed, Dec 18, 2025 at 05:04:53PM +0100, Mauro Carvalho Chehab wrote:
>> >>
>> >> >> Teach the estatus core how to walk CPER records and expose the vendor
>> >> >> record notification path. This adds the section iteration helpers,
>> >> >> the logging helpers that mirror the GHES behaviour, and the deferred
>> >> >> work used to hand vendor GUIDs to interested drivers. No users switch
>> >> >> over yet; this simply moves the common logic out of GHES so the next
>> >> >> patches can wire it up.
>> >> >>
>> >> >> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
>> >> >
>> >> >...
>> >> >
>> >> >> +static bool estatus_handle_arm_hw_error(estatus_generic_data *gdata, int sev, bool sync)
>> >> >
>> >> > Huh?
>> >> >
>> >> > This is a CPER record from GHES. Why are you moving CPER code out
>> >> > of ghes.c, placing in a file named estatus.c? Doesn't make much
>> >> > sense on my eyes...
>> >> >
>> >> > Same applies to to other GHES CPER record types.
>> >>
>> >> GHES still fills in the CPER record, but the parsing and logging logic is
>> >> shared with the new DeviceTree provider so I pulled those helpers into the
>> >> estatus core.
>> >
>> > I see, but this is not really estatus core. Instead, it is part of GHES CPER
>> > handling logic, which is defined at ACPI and UEFI specs. moving it to estatus
>> > sounds odd, at least on my eyes.
>> >
>> > Perhaps I'm failing to see where at ACPI/UEFI specs how CPER would be
>> > integrated with an OpenFirmware approach to handle CPER without GHES.
>> > Care to point to the relevant specs, if any?
>>
>> ACPI/APEI (via GHES) defines how CPER records are discovered and notified on ACPI systems,
>> but there is no ACPI or UEFI-defined equivalent for OpenFirmware/DeviceTree platforms.
>> UEFI standardises the CPER record format itself, not the transport or discovery mechanism.
>>
>> On non-ACPI systems we still receive the same UEFI-defined CPER payload
>> from firmware, but Linux needs a different, platform-specific contract
>> to locate and acknowledge it. The DT binding is a Linux-side description
>> of that contract rather than something defined by ACPI/UEFI.
>
> That's where I'm failing to understand: CPER is part of UEFI spec, and
> the only deliverable mechanism I'm aware of for CPER is via GHES or
> GHESv2 - e.g. via ACPI.
>
> Within the scope of https://uefi.org/specifications, I'm failing
> to see any other deliverable mechanism.
Right, the UEFI specs only describe the GHES/ACPI path. There isn’t
a spec-defined transport for firmware-first CPER delivery outside ACPI/GHES,
which is why patch 9/12 describes a Linux-side DT contract
for exposing the CPER status buffer and notification path.
If there’s a better place to document or name that DT transport, I’d appreciate pointers.
>> >> Both providers already call into the same notifier chain and
>> >> memory-pool helpers; this patch just moves the generic CPER walking routines
>> >> next to the rest of the common code so the DT path doesn’t have to grow its
>> >> own copy. If you’d prefer a different file layout or naming to make that
>> >> intent clearer, I’m happy to adjust.
>>
>> > Moving the code from ghes.c to estatus.c or to elsewhere shouldn't make any
>> > difference, as the DT handling logic could simply be calling the functions
>> > from ghes.c (or estatus.c). I fail to see why they need to be moved.
>>
>> The motivation is to provide a shared implementation for non-ACPI providers,
>> so that the DT path does not depend on ACPI/APEI.
>>
>> While the helpers currently live in ghes.c, they are CPER-specific and do not rely on ACPI tables,
>> APEI infrastructure, or GHES notification semantics. Keeping them there effectively makes GHES
>> the only place those helpers can live, even though the logic itself is provider-agnostic.
>
> The logic is related to GHES, as this seems to be the only standardized
> mechanism to report CPER records. As it is part of APEI, get_maintainers
> points to the people that have been maintaining it as:
>
> $ ./scripts/get_maintainer.pl -f ./drivers/acpi/apei/ghes.c
> "Rafael J. Wysocki" <rafael@kernel.org> (maintainer:ACPI APEI,commit_signer:6/13=46%)
> Tony Luck <tony.luck@intel.com> (reviewer:ACPI APEI,commit_signer:3/13=23%)
> Borislav Petkov <bp@alien8.de> (reviewer:ACPI APEI,removed_lines:5/62=8%)
> Hanjun Guo <guohanjun@huawei.com> (reviewer:ACPI APEI,commit_signer:4/13=31%)
> Mauro Carvalho Chehab <mchehab@kernel.org> (reviewer:ACPI APEI,authored:1/13=8%,removed_lines:6/62=10%)
> Shuai Xue <xueshuai@linux.alibaba.com> (reviewer:ACPI APEI,commit_signer:5/13=38%,authored:2/13=15%,added_lines:56/218=26%,removed_lines:34/62=55%)
> Len Brown <lenb@kernel.org> (reviewer:ACPI)
> Jonathan Cameron <Jonathan.Cameron@huawei.com> (commit_signer:5/13=38%)
> Breno Leitao <leitao@debian.org> (authored:2/13=15%,added_lines:38/218=17%)
> Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com> (authored:2/13=15%,added_lines:103/218=47%)
> Ankit Agrawal <ankita@nvidia.com> (authored:1/13=8%,removed_lines:6/62=10%)
> Jason Tian <jason@os.amperecomputing.com> (removed_lines:7/62=11%)
> linux-acpi@vger.kernel.org (open list:ACPI APEI)
> linux-kernel@vger.kernel.org (open list)
>
> Moving it elsewhere would make it confusing, as the expected deliverable
> mechanism for CPER is via GHES - as this is the only one defined at the
> uefi.org specs.
>
> While it might be moved to EFI and placed under cper.c,
> get_maintainers.pl would point to:
>
> $ ./scripts/get_maintainer.pl -f ./drivers/firmware/efi/cper.c
> Ard Biesheuvel <ardb@kernel.org> (maintainer:EXTENSIBLE FIRMWARE INTERFACE (EFI))
> linux-efi@vger.kernel.org (open list:EXTENSIBLE FIRMWARE INTERFACE (EFI))
> linux-kernel@vger.kernel.org (open list)
>
> which is not the people that have been maintaining RAS.
>
> Placing it under a "estatus.c" file would make it completely
> dissociated with UEFI/ACPI specs, as this name means nothing at
> the specs.
>
> Also, adding a new maintainer's entry won't make any sense, as the
> people that currently reviews and maintains GHES/CPER records
> should be kept.
You’re right that per UEFI/ACPI, GHES is the only standardised delivery path.
My goal isn’t to redefine that; it’s to avoid duplicating the CPER parsing/logging
code for non-ACPI platforms. I’m happy to restructure the helpers however you
think best so we keep the existing ACPI/APEI maintainer model intact.
>> By moving the CPER parsing and logging pieces into a common location,
>> both GHES and the DT provider can reuse the same implementation,
>> while the ACPI-specific discovery and notification code remains under drivers/acpi/apei/.
>> This avoids having the DT provider reach into GHES internals or duplicate CPER handling code.
>
> As Boris mentioned on patch 00/12, we need to better understand
> the high level scenario, as it is still not clear to me how a
> firmware-first notification would happen without ACPI.
That scenario is a firmware-first platform where firmware still produces a UEFI
CPER Generic Error Status block but signals Linux via the DT-described transport.
Once the buffer is available, the provider feeds it into the same estatus decoding path as GHES.
>> If the current naming or file layout makes that separation unclear, I’m happy to adjust it.
Thanks,
Ahmed
^ permalink raw reply [flat|nested] 39+ messages in thread
end of thread, other threads:[~2025-12-29 15:02 UTC | newest]
Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-17 11:28 [PATCH 00/12] ras: share firmware-first estatus handling Ahmed Tiba
2025-12-17 11:28 ` [PATCH 01/12] ras: add estatus core interfaces Ahmed Tiba
2025-12-17 11:28 ` [PATCH 02/12] ras: add estatus core implementation Ahmed Tiba
2025-12-18 15:42 ` Mauro Carvalho Chehab
2025-12-19 14:35 ` Ahmed Tiba
2025-12-21 19:31 ` kernel test robot
2025-12-17 11:28 ` [PATCH 03/12] ras: add estatus vendor handling and processing Ahmed Tiba
2025-12-18 16:04 ` Mauro Carvalho Chehab
2025-12-19 14:49 ` Ahmed Tiba
2025-12-19 15:30 ` Mauro Carvalho Chehab
2025-12-19 18:11 ` Ahmed Tiba
2025-12-22 8:13 ` Mauro Carvalho Chehab
2025-12-29 15:01 ` Ahmed Tiba
2025-12-21 23:39 ` kernel test robot
2025-12-17 11:28 ` [PATCH 04/12] ras: add estatus queuing and IRQ/NMI handling Ahmed Tiba
2025-12-17 11:28 ` [PATCH 05/12] ras: flesh out estatus processing core Ahmed Tiba
2025-12-17 11:28 ` [PATCH 06/12] efi/cper: adopt estatus iteration helpers Ahmed Tiba
2025-12-17 11:28 ` [PATCH 07/12] ghes: prepare estatus hooks for shared handling Ahmed Tiba
2025-12-17 11:28 ` [PATCH 08/12] ghes: add estatus provider ops Ahmed Tiba
2025-12-17 11:28 ` [PATCH 09/12] ghes: route error handling through shared estatus core Ahmed Tiba
2025-12-17 11:28 ` [PATCH 10/12] dt-bindings: ras: document estatus provider Ahmed Tiba
2025-12-17 11:41 ` Krzysztof Kozlowski
2025-12-17 17:49 ` Ahmed Tiba
2025-12-18 6:48 ` Krzysztof Kozlowski
2025-12-18 10:22 ` Ahmed Tiba
2025-12-18 10:31 ` Ahmed Tiba
2025-12-19 9:53 ` Krzysztof Kozlowski
2025-12-19 10:37 ` Ahmed Tiba
2025-12-19 10:47 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 11/12] ras: add DeviceTree estatus provider driver Ahmed Tiba
2025-12-18 12:13 ` Will Deacon
2025-12-18 13:42 ` Ahmed Tiba
2025-12-18 15:19 ` Will Deacon
2025-12-19 9:02 ` Ahmed Tiba
2025-12-19 13:00 ` Will Deacon
2025-12-19 17:21 ` Ahmed Tiba
2025-12-17 11:28 ` [PATCH 12/12] doc: ras: describe firmware-first estatus flow Ahmed Tiba
2025-12-21 1:35 ` [PATCH 00/12] ras: share firmware-first estatus handling Borislav Petkov
2025-12-29 11:54 ` Ahmed Tiba
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).