[PATCH v3 08/10] ACPI: APEI: share GHES CPER helpers
From: Ahmed Tiba <hidden>
Date: 2026-03-18 20:49:28
Also in:
linux-acpi, linux-cxl, linux-devicetree, linux-doc
Subsystem:
acpi, acpi apei, acpi component architecture (acpica), the rest · Maintainers:
"Rafael J. Wysocki", Saket Dumbre, Linus Torvalds
Wire GHES up to the helper routines in ghes_cper.c and remove the local copies from ghes.c. This keeps the control flow identical while letting the helpers be shared with other firmware-first providers. Signed-off-by: Ahmed Tiba <redacted> --- drivers/acpi/apei/ghes.c | 415 +-------------------------------------- drivers/acpi/apei/ghes_cper.c | 438 +++++++++++++++++++++++++++++++++++++++++- include/acpi/ghes_cper.h | 20 ++ 3 files changed, 459 insertions(+), 414 deletions(-)
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 136993704d52..25abd3594c89 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c@@ -67,8 +67,6 @@ #define FIX_APEI_GHES_SDEI_CRITICAL __end_of_fixed_addresses #endif -static ATOMIC_NOTIFIER_HEAD(ghes_report_chain); - /* * This driver isn't really modular, however for the time being, * continuing to use module_param is the easiest way to remain
@@ -113,421 +111,12 @@ static DEFINE_MUTEX(ghes_devs_mutex); */ static DEFINE_SPINLOCK(ghes_notify_lock_irq); -struct gen_pool *ghes_estatus_pool; - -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; -} - -/** - * ghes_estatus_pool_region_free - free previously allocated memory - * from the ghes_estatus_pool. - * @addr: address of memory to free. - * @size: size of memory to free. - * - * Returns none. - */ -void ghes_estatus_pool_region_free(unsigned long addr, u32 size) -{ - gen_pool_free(ghes_estatus_pool, addr, size); -} -EXPORT_SYMBOL_GPL(ghes_estatus_pool_region_free); - -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; - } -} - - -/** - * 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; - int length = gdata->error_data_length; - char error_type[120]; - bool queued = false; - int sec_sev, i; - char *p; - - sec_sev = ghes_severity(gdata->error_severity); - if (length >= sizeof(*err)) { - log_arm_hw_error(err, sec_sev); - } else { - pr_warn(FW_BUG "arm error length: %d\n", length); - pr_warn(FW_BUG "length is too small\n"); - pr_warn(FW_BUG "firmware-generated error record is incorrect\n"); - return false; - } - - if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE) - return false; - - p = (char *)(err + 1); - length -= sizeof(err); - - for (i = 0; i < err->err_info_num; i++) { - struct cper_arm_err_info *err_info; - bool is_cache, has_pa; - - /* Ensure we have enough data for the error info header */ - if (length < sizeof(*err_info)) - break; - - err_info = (struct cper_arm_err_info *)p; - - /* Validate the claimed length before using it */ - length -= err_info->length; - if (length < 0) - break; - - is_cache = err_info->type & CPER_ARM_CACHE_ERROR; - 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 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_cper_handle_status(ghes->dev, ghes->generic, + estatus, is_hest_sync_notify(ghes)); } static void __ghes_panic(struct ghes *ghes,
diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
index ac9d2be03f45..bd172b769953 100644
--- a/drivers/acpi/apei/ghes_cper.c
+++ b/drivers/acpi/apei/ghes_cper.c@@ -13,22 +13,32 @@ */ #include <linux/aer.h> +#include <linux/bitfield.h> +#include <linux/device.h> #include <linux/err.h> #include <linux/genalloc.h> -#include <linux/irq_work.h> #include <linux/io.h> +#include <linux/irq_work.h> #include <linux/kfifo.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/math64.h> #include <linux/mm.h> +#include <linux/string.h> +#include <linux/uuid.h> +#include <linux/sched/signal.h> +#include <linux/task_work.h> #include <linux/notifier.h> #include <linux/llist.h> +#include <linux/ras.h> +#include <ras/ras_event.h> #include <linux/ratelimit.h> #include <linux/rcupdate.h> #include <linux/rculist.h> #include <linux/sched/clock.h> #include <linux/slab.h> +#include <linux/vmcore_info.h> +#include <linux/vmalloc.h> #include <acpi/apei.h> #include <acpi/ghes_cper.h>
@@ -38,9 +48,363 @@ #include "apei-internal.h" +ATOMIC_NOTIFIER_HEAD(ghes_report_chain); + +#ifndef CONFIG_ACPI_APEI +void __weak arch_apei_report_mem_error(int sev, struct cper_sec_mem_err *mem_err) { } +#endif + static struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE]; static atomic_t ghes_estatus_cache_alloced; +struct gen_pool *ghes_estatus_pool; + +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; +} +EXPORT_SYMBOL_GPL(ghes_estatus_pool_init); + +/** + * ghes_estatus_pool_region_free - free previously allocated memory + * from the ghes_estatus_pool. + * @addr: address of memory to free. + * @size: size of memory to free. + * + * Returns none. + */ +void ghes_estatus_pool_region_free(unsigned long addr, u32 size) +{ + gen_pool_free(ghes_estatus_pool, addr, size); +} +EXPORT_SYMBOL_GPL(ghes_estatus_pool_region_free); + +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; + } +} + +/** + * 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; +} + +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; +} + +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; + int length = gdata->error_data_length; + char error_type[120]; + bool queued = false; + int sec_sev, i; + char *p; + + sec_sev = ghes_severity(gdata->error_severity); + if (length >= sizeof(*err)) { + log_arm_hw_error(err, sec_sev); + } else { + pr_warn(FW_BUG "arm error length: %d\n", length); + pr_warn(FW_BUG "length is too small\n"); + pr_warn(FW_BUG "firmware-generated error record is incorrect\n"); + return false; + } + + if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE) + return false; + + p = (char *)(err + 1); + length -= sizeof(err); + + for (i = 0; i < err->err_info_num; i++) { + struct cper_arm_err_info *err_info; + bool is_cache, has_pa; + + /* Ensure we have enough data for the error info header */ + if (length < sizeof(*err_info)) + break; + + err_info = (struct cper_arm_err_info *)p; + + /* Validate the claimed length before using it */ + length -= err_info->length; + if (length < 0) + break; + + is_cache = err_info->type & CPER_ARM_CACHE_ERROR; + 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. + */ +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 +} + +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); +} + +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) { + 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("%sHardware error from APEI Generic Hardware Error Source: %d\n", + pfx_seq, generic->header.source_id); + cper_estatus_print(pfx_seq, estatus); +} + +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; +} + +#ifdef CONFIG_ACPI_APEI static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx) { phys_addr_t paddr;
@@ -272,6 +636,7 @@ void ghes_clear_estatus(struct ghes *ghes, if (is_hest_type_generic_v2(ghes)) ghes_ack_error(ghes->generic_v2); } +#endif /* CONFIG_ACPI_APEI */ static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list);
@@ -323,6 +688,77 @@ void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata, schedule_work(&entry->work); } +void ghes_cper_handle_status(struct device *dev, + const struct acpi_hest_generic *generic, + const struct acpi_hest_generic_status *estatus, + bool sync) +{ + 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; + + 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(dev, + HW_ERR GHES_PFX "%s:%d: synchronous unrecoverable error (SIGBUS)\n", + current->comm, task_pid_nr(current)); + force_sig(SIGBUS); + } +} /* 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,
diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
index 6bb35719105d..d7fe71bd67bb 100644
--- a/include/acpi/ghes_cper.h
+++ b/include/acpi/ghes_cper.h@@ -20,6 +20,8 @@ #define ACPI_APEI_GHES_CPER_H #include <linux/atomic.h> +#include <linux/device.h> +#include <linux/notifier.h> #include <linux/workqueue.h> #include <acpi/ghes.h>
@@ -60,6 +62,7 @@ ((struct ghes_vendor_record_entry *)(vendor_entry) + 1)) extern struct gen_pool *ghes_estatus_pool; +extern struct atomic_notifier_head ghes_report_chain; static inline bool is_hest_type_generic_v2(struct ghes *ghes) {
@@ -108,6 +111,23 @@ void ghes_estatus_cache_add(struct acpi_hest_generic *generic, struct acpi_hest_generic_status *estatus); void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata, int sev); +int ghes_severity(int severity); +bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, + int sev, bool sync); +bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, + int sev, bool sync); +void ghes_handle_aer(struct acpi_hest_generic_data *gdata); +void ghes_log_hwerr(int sev, guid_t *sec_type); +void __ghes_print_estatus(const char *pfx, + const struct acpi_hest_generic *generic, + const struct acpi_hest_generic_status *estatus); +int ghes_print_estatus(const char *pfx, + const struct acpi_hest_generic *generic, + const struct acpi_hest_generic_status *estatus); +void ghes_cper_handle_status(struct device *dev, + const struct acpi_hest_generic *generic, + const struct acpi_hest_generic_status *estatus, + bool sync); void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, int severity); int cxl_cper_register_prot_err_work(struct work_struct *work);
--
2.43.0