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.
The weak arch_apei_report_mem_error() fallback is only there to keep the shared helper buildable when GHES_CPER_HELPERS is enabled without ACPI_APEI, while preserving the current GHES behaviour when ACPI_APEI is enabled. Serialize ghes_estatus_pool_init() and allow later users to extend the shared pool instead of racing a second initialization or silently reusing a pool sized only for the first user. Fix queued tracking and AER overflow cleanup. Signed-off-by: Ahmed Tiba <[email protected]> --- drivers/acpi/apei/ghes.c | 464 +-------------------------------------- drivers/acpi/apei/ghes_cper.c | 500 ++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pcie/aer.c | 9 +- include/acpi/ghes_cper.h | 20 ++ 4 files changed, 529 insertions(+), 464 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 2a83d326e692..c643c1703819 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,469 +111,11 @@ 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 bool ghes_cxl_event_len_valid(struct acpi_hest_generic_data *gdata, - struct cxl_cper_event_rec *rec) -{ - if (gdata->error_data_length < sizeof(*rec) || - rec->hdr.length <= sizeof(rec->hdr) || - rec->hdr.length > gdata->error_data_length || - rec->hdr.length > sizeof(*rec)) { - pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n", - rec->hdr.length); - return false; - } - - return true; -} - -static bool ghes_cxl_prot_err_len_valid(struct acpi_hest_generic_data *gdata, - struct cxl_cper_sec_prot_err *prot_err) -{ - if (gdata->error_data_length < sizeof(*prot_err) + - sizeof(struct cxl_ras_capability_regs)) { - pr_err(FW_WARN "CXL CPER Invalid protocol error length (%u)\n", - gdata->error_data_length); - return false; - } - - if (prot_err->dvsec_len > - gdata->error_data_length - sizeof(*prot_err) - - sizeof(struct cxl_ras_capability_regs)) { - pr_err(FW_WARN "CXL CPER invalid DVSEC length (%u)\n", - prot_err->dvsec_len); - return false; - } - - return true; -} - 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); - - if (!ghes_cxl_prot_err_len_valid(gdata, prot_err)) - continue; - - 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); - - if (!ghes_cxl_event_len_valid(gdata, rec)) - continue; - - 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); - - if (!ghes_cxl_event_len_valid(gdata, rec)) - continue; - - 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); - - if (!ghes_cxl_event_len_valid(gdata, rec)) - continue; - - 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 b59e3ed3eab3..ec8881ada97e 100644 --- a/drivers/acpi/apei/ghes_cper.c +++ b/drivers/acpi/apei/ghes_cper.c @@ -13,7 +13,9 @@ */ #include <linux/aer.h> +#include <linux/bitfield.h> #include <linux/cleanup.h> +#include <linux/device.h> #include <linux/err.h> #include <linux/genalloc.h> #include <linux/io.h> @@ -21,11 +23,20 @@ #include <linux/kernel.h> #include <linux/math64.h> #include <linux/mm.h> +#include <linux/mutex.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/ras.h> +#include <ras/ras_event.h> #include <linux/ratelimit.h> #include <linux/rcupdate.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/acpi_io.h> @@ -36,9 +47,497 @@ #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; +static DEFINE_MUTEX(ghes_estatus_pool_lock); + +int ghes_estatus_pool_init(unsigned int num_ghes) +{ + unsigned long addr, len; + int rc; + + len = GHES_ESTATUS_CACHE_AVG_SIZE * GHES_ESTATUS_CACHE_ALLOCED_MAX; + len += (num_ghes * GHES_ESOURCE_PREALLOC_MAX_SIZE); + len = PAGE_ALIGN(len); + + mutex_lock(&ghes_estatus_pool_lock); + + if (!ghes_estatus_pool) { + ghes_estatus_pool = gen_pool_create(GHES_ESTATUS_POOL_MIN_ALLOC_ORDER, -1); + if (!ghes_estatus_pool) { + rc = -ENOMEM; + goto out_unlock; + } + } + + addr = (unsigned long)vmalloc(len); + if (!addr) { + rc = -ENOMEM; + goto err_pool_alloc; + } + + rc = gen_pool_add(ghes_estatus_pool, addr, len, -1); + if (rc) + goto err_pool_add; + + mutex_unlock(&ghes_estatus_pool_lock); + return 0; + +err_pool_add: + vfree((void *)addr); + +err_pool_alloc: + if (!gen_pool_avail(ghes_estatus_pool) && !gen_pool_size(ghes_estatus_pool)) { + gen_pool_destroy(ghes_estatus_pool); + ghes_estatus_pool = NULL; + } +out_unlock: + mutex_unlock(&ghes_estatus_pool_lock); + return rc; +} +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); +} + +static bool ghes_cxl_event_len_valid(struct acpi_hest_generic_data *gdata, + struct cxl_cper_event_rec *rec) +{ + if (gdata->error_data_length < sizeof(*rec) || + rec->hdr.length <= sizeof(rec->hdr) || + rec->hdr.length > gdata->error_data_length || + rec->hdr.length > sizeof(*rec)) { + pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n", + rec->hdr.length); + return false; + } + + return true; +} + +static bool ghes_cxl_prot_err_len_valid(struct acpi_hest_generic_data *gdata, + struct cxl_cper_sec_prot_err *prot_err) +{ + if (gdata->error_data_length < sizeof(*prot_err) + + sizeof(struct cxl_ras_capability_regs)) { + pr_err(FW_WARN "CXL CPER Invalid protocol error length (%u)\n", + gdata->error_data_length); + return false; + } + + if (prot_err->dvsec_len > + gdata->error_data_length - sizeof(*prot_err) - + sizeof(struct cxl_ras_capability_regs)) { + pr_err(FW_WARN "CXL CPER invalid DVSEC length (%u)\n", + prot_err->dvsec_len); + return false; + } + + return true; +} + +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); + + if (!ghes_cxl_prot_err_len_valid(gdata, prot_err)) + continue; + + 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); + + if (!ghes_cxl_event_len_valid(gdata, rec)) + continue; + + 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); + + if (!ghes_cxl_event_len_valid(gdata, rec)) + continue; + + 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); + + if (!ghes_cxl_event_len_valid(gdata, rec)) + continue; + + 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); + } +} + +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); +} + +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; @@ -284,6 +783,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); diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index c4fd9c0b2a54..1448f4f725d4 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1228,6 +1228,8 @@ static void aer_recover_work_func(struct work_struct *work) entry.domain, entry.bus, PCI_SLOT(entry.devfn), PCI_FUNC(entry.devfn)); + ghes_estatus_pool_region_free((unsigned long)entry.regs, + sizeof(struct aer_capability_regs)); continue; } pci_print_aer(pdev, entry.severity, entry.regs); @@ -1274,9 +1276,12 @@ void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, if (kfifo_in_spinlocked(&aer_recover_ring, &entry, 1, &aer_recover_ring_lock)) schedule_work(&aer_recover_work); - else - pr_err("buffer overflow in recovery for %04x:%02x:%02x.%x\n", + else { + ghes_estatus_pool_region_free((unsigned long)aer_regs, + sizeof(*aer_regs)); + pr_err_ratelimited("buffer overflow in recovery for %04x:%02x:%02x.%x\n", domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); + } } EXPORT_SYMBOL_GPL(aer_recover_queue); #endif diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h index a853a5996cdf..e09a33d343d7 100644 --- a/include/acpi/ghes_cper.h +++ b/include/acpi/ghes_cper.h @@ -16,6 +16,8 @@ #ifndef ACPI_APEI_GHES_CPER_H #define ACPI_APEI_GHES_CPER_H +#include <linux/device.h> +#include <linux/notifier.h> #include <linux/workqueue.h> #include <acpi/ghes.h> @@ -57,6 +59,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) { @@ -107,6 +110,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
