162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * APEI Generic Hardware Error Source support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Generic Hardware Error Source provides a way to report platform 662306a36Sopenharmony_ci * hardware errors (such as that from chipset). It works in so called 762306a36Sopenharmony_ci * "Firmware First" mode, that is, hardware errors are reported to 862306a36Sopenharmony_ci * firmware firstly, then reported to Linux by firmware. This way, 962306a36Sopenharmony_ci * some non-standard hardware error registers or non-standard hardware 1062306a36Sopenharmony_ci * link can be checked by firmware to produce more hardware error 1162306a36Sopenharmony_ci * information for Linux. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * For more information about Generic Hardware Error Source, please 1462306a36Sopenharmony_ci * refer to ACPI Specification version 4.0, section 17.3.2.6 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * Copyright 2010,2011 Intel Corp. 1762306a36Sopenharmony_ci * Author: Huang Ying <ying.huang@intel.com> 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <linux/arm_sdei.h> 2162306a36Sopenharmony_ci#include <linux/kernel.h> 2262306a36Sopenharmony_ci#include <linux/moduleparam.h> 2362306a36Sopenharmony_ci#include <linux/init.h> 2462306a36Sopenharmony_ci#include <linux/acpi.h> 2562306a36Sopenharmony_ci#include <linux/io.h> 2662306a36Sopenharmony_ci#include <linux/interrupt.h> 2762306a36Sopenharmony_ci#include <linux/timer.h> 2862306a36Sopenharmony_ci#include <linux/cper.h> 2962306a36Sopenharmony_ci#include <linux/platform_device.h> 3062306a36Sopenharmony_ci#include <linux/mutex.h> 3162306a36Sopenharmony_ci#include <linux/ratelimit.h> 3262306a36Sopenharmony_ci#include <linux/vmalloc.h> 3362306a36Sopenharmony_ci#include <linux/irq_work.h> 3462306a36Sopenharmony_ci#include <linux/llist.h> 3562306a36Sopenharmony_ci#include <linux/genalloc.h> 3662306a36Sopenharmony_ci#include <linux/pci.h> 3762306a36Sopenharmony_ci#include <linux/pfn.h> 3862306a36Sopenharmony_ci#include <linux/aer.h> 3962306a36Sopenharmony_ci#include <linux/nmi.h> 4062306a36Sopenharmony_ci#include <linux/sched/clock.h> 4162306a36Sopenharmony_ci#include <linux/uuid.h> 4262306a36Sopenharmony_ci#include <linux/ras.h> 4362306a36Sopenharmony_ci#include <linux/task_work.h> 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#include <acpi/actbl1.h> 4662306a36Sopenharmony_ci#include <acpi/ghes.h> 4762306a36Sopenharmony_ci#include <acpi/apei.h> 4862306a36Sopenharmony_ci#include <asm/fixmap.h> 4962306a36Sopenharmony_ci#include <asm/tlbflush.h> 5062306a36Sopenharmony_ci#include <ras/ras_event.h> 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#include "apei-internal.h" 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#define GHES_PFX "GHES: " 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci#define GHES_ESTATUS_MAX_SIZE 65536 5762306a36Sopenharmony_ci#define GHES_ESOURCE_PREALLOC_MAX_SIZE 65536 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci#define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER 3 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/* This is just an estimation for memory pool allocation */ 6262306a36Sopenharmony_ci#define GHES_ESTATUS_CACHE_AVG_SIZE 512 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci#define GHES_ESTATUS_CACHES_SIZE 4 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci#define GHES_ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL 6762306a36Sopenharmony_ci/* Prevent too many caches are allocated because of RCU */ 6862306a36Sopenharmony_ci#define GHES_ESTATUS_CACHE_ALLOCED_MAX (GHES_ESTATUS_CACHES_SIZE * 3 / 2) 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci#define GHES_ESTATUS_CACHE_LEN(estatus_len) \ 7162306a36Sopenharmony_ci (sizeof(struct ghes_estatus_cache) + (estatus_len)) 7262306a36Sopenharmony_ci#define GHES_ESTATUS_FROM_CACHE(estatus_cache) \ 7362306a36Sopenharmony_ci ((struct acpi_hest_generic_status *) \ 7462306a36Sopenharmony_ci ((struct ghes_estatus_cache *)(estatus_cache) + 1)) 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci#define GHES_ESTATUS_NODE_LEN(estatus_len) \ 7762306a36Sopenharmony_ci (sizeof(struct ghes_estatus_node) + (estatus_len)) 7862306a36Sopenharmony_ci#define GHES_ESTATUS_FROM_NODE(estatus_node) \ 7962306a36Sopenharmony_ci ((struct acpi_hest_generic_status *) \ 8062306a36Sopenharmony_ci ((struct ghes_estatus_node *)(estatus_node) + 1)) 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci#define GHES_VENDOR_ENTRY_LEN(gdata_len) \ 8362306a36Sopenharmony_ci (sizeof(struct ghes_vendor_record_entry) + (gdata_len)) 8462306a36Sopenharmony_ci#define GHES_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \ 8562306a36Sopenharmony_ci ((struct acpi_hest_generic_data *) \ 8662306a36Sopenharmony_ci ((struct ghes_vendor_record_entry *)(vendor_entry) + 1)) 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* 8962306a36Sopenharmony_ci * NMI-like notifications vary by architecture, before the compiler can prune 9062306a36Sopenharmony_ci * unused static functions it needs a value for these enums. 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ci#ifndef CONFIG_ARM_SDE_INTERFACE 9362306a36Sopenharmony_ci#define FIX_APEI_GHES_SDEI_NORMAL __end_of_fixed_addresses 9462306a36Sopenharmony_ci#define FIX_APEI_GHES_SDEI_CRITICAL __end_of_fixed_addresses 9562306a36Sopenharmony_ci#endif 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic ATOMIC_NOTIFIER_HEAD(ghes_report_chain); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic inline bool is_hest_type_generic_v2(struct ghes *ghes) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* 10562306a36Sopenharmony_ci * A platform may describe one error source for the handling of synchronous 10662306a36Sopenharmony_ci * errors (e.g. MCE or SEA), or for handling asynchronous errors (e.g. SCI 10762306a36Sopenharmony_ci * or External Interrupt). On x86, the HEST notifications are always 10862306a36Sopenharmony_ci * asynchronous, so only SEA on ARM is delivered as a synchronous 10962306a36Sopenharmony_ci * notification. 11062306a36Sopenharmony_ci */ 11162306a36Sopenharmony_cistatic inline bool is_hest_sync_notify(struct ghes *ghes) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci u8 notify_type = ghes->generic->notify.type; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return notify_type == ACPI_HEST_NOTIFY_SEA; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci/* 11962306a36Sopenharmony_ci * This driver isn't really modular, however for the time being, 12062306a36Sopenharmony_ci * continuing to use module_param is the easiest way to remain 12162306a36Sopenharmony_ci * compatible with existing boot arg use cases. 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_cibool ghes_disable; 12462306a36Sopenharmony_cimodule_param_named(disable, ghes_disable, bool, 0); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci/* 12762306a36Sopenharmony_ci * "ghes.edac_force_enable" forcibly enables ghes_edac and skips the platform 12862306a36Sopenharmony_ci * check. 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_cistatic bool ghes_edac_force_enable; 13162306a36Sopenharmony_cimodule_param_named(edac_force_enable, ghes_edac_force_enable, bool, 0); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci/* 13462306a36Sopenharmony_ci * All error sources notified with HED (Hardware Error Device) share a 13562306a36Sopenharmony_ci * single notifier callback, so they need to be linked and checked one 13662306a36Sopenharmony_ci * by one. This holds true for NMI too. 13762306a36Sopenharmony_ci * 13862306a36Sopenharmony_ci * RCU is used for these lists, so ghes_list_mutex is only used for 13962306a36Sopenharmony_ci * list changing, not for traversing. 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_cistatic LIST_HEAD(ghes_hed); 14262306a36Sopenharmony_cistatic DEFINE_MUTEX(ghes_list_mutex); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci/* 14562306a36Sopenharmony_ci * A list of GHES devices which are given to the corresponding EDAC driver 14662306a36Sopenharmony_ci * ghes_edac for further use. 14762306a36Sopenharmony_ci */ 14862306a36Sopenharmony_cistatic LIST_HEAD(ghes_devs); 14962306a36Sopenharmony_cistatic DEFINE_MUTEX(ghes_devs_mutex); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci/* 15262306a36Sopenharmony_ci * Because the memory area used to transfer hardware error information 15362306a36Sopenharmony_ci * from BIOS to Linux can be determined only in NMI, IRQ or timer 15462306a36Sopenharmony_ci * handler, but general ioremap can not be used in atomic context, so 15562306a36Sopenharmony_ci * the fixmap is used instead. 15662306a36Sopenharmony_ci * 15762306a36Sopenharmony_ci * This spinlock is used to prevent the fixmap entry from being used 15862306a36Sopenharmony_ci * simultaneously. 15962306a36Sopenharmony_ci */ 16062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(ghes_notify_lock_irq); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistruct ghes_vendor_record_entry { 16362306a36Sopenharmony_ci struct work_struct work; 16462306a36Sopenharmony_ci int error_severity; 16562306a36Sopenharmony_ci char vendor_record[]; 16662306a36Sopenharmony_ci}; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic struct gen_pool *ghes_estatus_pool; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE]; 17162306a36Sopenharmony_cistatic atomic_t ghes_estatus_cache_alloced; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic int ghes_panic_timeout __read_mostly = 30; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci phys_addr_t paddr; 17862306a36Sopenharmony_ci pgprot_t prot; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci paddr = PFN_PHYS(pfn); 18162306a36Sopenharmony_ci prot = arch_apei_get_mem_attribute(paddr); 18262306a36Sopenharmony_ci __set_fixmap(fixmap_idx, paddr, prot); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci return (void __iomem *) __fix_to_virt(fixmap_idx); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci int _idx = virt_to_fix((unsigned long)vaddr); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci WARN_ON_ONCE(fixmap_idx != _idx); 19262306a36Sopenharmony_ci clear_fixmap(fixmap_idx); 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ciint ghes_estatus_pool_init(unsigned int num_ghes) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci unsigned long addr, len; 19862306a36Sopenharmony_ci int rc; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci ghes_estatus_pool = gen_pool_create(GHES_ESTATUS_POOL_MIN_ALLOC_ORDER, -1); 20162306a36Sopenharmony_ci if (!ghes_estatus_pool) 20262306a36Sopenharmony_ci return -ENOMEM; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci len = GHES_ESTATUS_CACHE_AVG_SIZE * GHES_ESTATUS_CACHE_ALLOCED_MAX; 20562306a36Sopenharmony_ci len += (num_ghes * GHES_ESOURCE_PREALLOC_MAX_SIZE); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci addr = (unsigned long)vmalloc(PAGE_ALIGN(len)); 20862306a36Sopenharmony_ci if (!addr) 20962306a36Sopenharmony_ci goto err_pool_alloc; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci rc = gen_pool_add(ghes_estatus_pool, addr, PAGE_ALIGN(len), -1); 21262306a36Sopenharmony_ci if (rc) 21362306a36Sopenharmony_ci goto err_pool_add; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci return 0; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cierr_pool_add: 21862306a36Sopenharmony_ci vfree((void *)addr); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cierr_pool_alloc: 22162306a36Sopenharmony_ci gen_pool_destroy(ghes_estatus_pool); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci return -ENOMEM; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci/** 22762306a36Sopenharmony_ci * ghes_estatus_pool_region_free - free previously allocated memory 22862306a36Sopenharmony_ci * from the ghes_estatus_pool. 22962306a36Sopenharmony_ci * @addr: address of memory to free. 23062306a36Sopenharmony_ci * @size: size of memory to free. 23162306a36Sopenharmony_ci * 23262306a36Sopenharmony_ci * Returns none. 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_civoid ghes_estatus_pool_region_free(unsigned long addr, u32 size) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci gen_pool_free(ghes_estatus_pool, addr, size); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_estatus_pool_region_free); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int map_gen_v2(struct ghes *ghes) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci return apei_map_generic_address(&ghes->generic_v2->read_ack_register); 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic void unmap_gen_v2(struct ghes *ghes) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci apei_unmap_generic_address(&ghes->generic_v2->read_ack_register); 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void ghes_ack_error(struct acpi_hest_generic_v2 *gv2) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci int rc; 25362306a36Sopenharmony_ci u64 val = 0; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci rc = apei_read(&val, &gv2->read_ack_register); 25662306a36Sopenharmony_ci if (rc) 25762306a36Sopenharmony_ci return; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset; 26062306a36Sopenharmony_ci val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci apei_write(val, &gv2->read_ack_register); 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic struct ghes *ghes_new(struct acpi_hest_generic *generic) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct ghes *ghes; 26862306a36Sopenharmony_ci unsigned int error_block_length; 26962306a36Sopenharmony_ci int rc; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci ghes = kzalloc(sizeof(*ghes), GFP_KERNEL); 27262306a36Sopenharmony_ci if (!ghes) 27362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci ghes->generic = generic; 27662306a36Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) { 27762306a36Sopenharmony_ci rc = map_gen_v2(ghes); 27862306a36Sopenharmony_ci if (rc) 27962306a36Sopenharmony_ci goto err_free; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci rc = apei_map_generic_address(&generic->error_status_address); 28362306a36Sopenharmony_ci if (rc) 28462306a36Sopenharmony_ci goto err_unmap_read_ack_addr; 28562306a36Sopenharmony_ci error_block_length = generic->error_block_length; 28662306a36Sopenharmony_ci if (error_block_length > GHES_ESTATUS_MAX_SIZE) { 28762306a36Sopenharmony_ci pr_warn(FW_WARN GHES_PFX 28862306a36Sopenharmony_ci "Error status block length is too long: %u for " 28962306a36Sopenharmony_ci "generic hardware error source: %d.\n", 29062306a36Sopenharmony_ci error_block_length, generic->header.source_id); 29162306a36Sopenharmony_ci error_block_length = GHES_ESTATUS_MAX_SIZE; 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci ghes->estatus = kmalloc(error_block_length, GFP_KERNEL); 29462306a36Sopenharmony_ci if (!ghes->estatus) { 29562306a36Sopenharmony_ci rc = -ENOMEM; 29662306a36Sopenharmony_ci goto err_unmap_status_addr; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci return ghes; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cierr_unmap_status_addr: 30262306a36Sopenharmony_ci apei_unmap_generic_address(&generic->error_status_address); 30362306a36Sopenharmony_cierr_unmap_read_ack_addr: 30462306a36Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) 30562306a36Sopenharmony_ci unmap_gen_v2(ghes); 30662306a36Sopenharmony_cierr_free: 30762306a36Sopenharmony_ci kfree(ghes); 30862306a36Sopenharmony_ci return ERR_PTR(rc); 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic void ghes_fini(struct ghes *ghes) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci kfree(ghes->estatus); 31462306a36Sopenharmony_ci apei_unmap_generic_address(&ghes->generic->error_status_address); 31562306a36Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) 31662306a36Sopenharmony_ci unmap_gen_v2(ghes); 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic inline int ghes_severity(int severity) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci switch (severity) { 32262306a36Sopenharmony_ci case CPER_SEV_INFORMATIONAL: 32362306a36Sopenharmony_ci return GHES_SEV_NO; 32462306a36Sopenharmony_ci case CPER_SEV_CORRECTED: 32562306a36Sopenharmony_ci return GHES_SEV_CORRECTED; 32662306a36Sopenharmony_ci case CPER_SEV_RECOVERABLE: 32762306a36Sopenharmony_ci return GHES_SEV_RECOVERABLE; 32862306a36Sopenharmony_ci case CPER_SEV_FATAL: 32962306a36Sopenharmony_ci return GHES_SEV_PANIC; 33062306a36Sopenharmony_ci default: 33162306a36Sopenharmony_ci /* Unknown, go panic */ 33262306a36Sopenharmony_ci return GHES_SEV_PANIC; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci} 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_cistatic void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, 33762306a36Sopenharmony_ci int from_phys, 33862306a36Sopenharmony_ci enum fixed_addresses fixmap_idx) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci void __iomem *vaddr; 34162306a36Sopenharmony_ci u64 offset; 34262306a36Sopenharmony_ci u32 trunk; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci while (len > 0) { 34562306a36Sopenharmony_ci offset = paddr - (paddr & PAGE_MASK); 34662306a36Sopenharmony_ci vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx); 34762306a36Sopenharmony_ci trunk = PAGE_SIZE - offset; 34862306a36Sopenharmony_ci trunk = min(trunk, len); 34962306a36Sopenharmony_ci if (from_phys) 35062306a36Sopenharmony_ci memcpy_fromio(buffer, vaddr + offset, trunk); 35162306a36Sopenharmony_ci else 35262306a36Sopenharmony_ci memcpy_toio(vaddr + offset, buffer, trunk); 35362306a36Sopenharmony_ci len -= trunk; 35462306a36Sopenharmony_ci paddr += trunk; 35562306a36Sopenharmony_ci buffer += trunk; 35662306a36Sopenharmony_ci ghes_unmap(vaddr, fixmap_idx); 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci/* Check the top-level record header has an appropriate size. */ 36162306a36Sopenharmony_cistatic int __ghes_check_estatus(struct ghes *ghes, 36262306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci u32 len = cper_estatus_len(estatus); 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (len < sizeof(*estatus)) { 36762306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status block!\n"); 36862306a36Sopenharmony_ci return -EIO; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if (len > ghes->generic->error_block_length) { 37262306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status block length!\n"); 37362306a36Sopenharmony_ci return -EIO; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci if (cper_estatus_check_header(estatus)) { 37762306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n"); 37862306a36Sopenharmony_ci return -EIO; 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci return 0; 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci/* Read the CPER block, returning its address, and header in estatus. */ 38562306a36Sopenharmony_cistatic int __ghes_peek_estatus(struct ghes *ghes, 38662306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus, 38762306a36Sopenharmony_ci u64 *buf_paddr, enum fixed_addresses fixmap_idx) 38862306a36Sopenharmony_ci{ 38962306a36Sopenharmony_ci struct acpi_hest_generic *g = ghes->generic; 39062306a36Sopenharmony_ci int rc; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci rc = apei_read(buf_paddr, &g->error_status_address); 39362306a36Sopenharmony_ci if (rc) { 39462306a36Sopenharmony_ci *buf_paddr = 0; 39562306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 39662306a36Sopenharmony_ci"Failed to read error status block address for hardware error source: %d.\n", 39762306a36Sopenharmony_ci g->header.source_id); 39862306a36Sopenharmony_ci return -EIO; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci if (!*buf_paddr) 40162306a36Sopenharmony_ci return -ENOENT; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1, 40462306a36Sopenharmony_ci fixmap_idx); 40562306a36Sopenharmony_ci if (!estatus->block_status) { 40662306a36Sopenharmony_ci *buf_paddr = 0; 40762306a36Sopenharmony_ci return -ENOENT; 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci return 0; 41162306a36Sopenharmony_ci} 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_cistatic int __ghes_read_estatus(struct acpi_hest_generic_status *estatus, 41462306a36Sopenharmony_ci u64 buf_paddr, enum fixed_addresses fixmap_idx, 41562306a36Sopenharmony_ci size_t buf_len) 41662306a36Sopenharmony_ci{ 41762306a36Sopenharmony_ci ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx); 41862306a36Sopenharmony_ci if (cper_estatus_check(estatus)) { 41962306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 42062306a36Sopenharmony_ci "Failed to read error status block!\n"); 42162306a36Sopenharmony_ci return -EIO; 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci return 0; 42562306a36Sopenharmony_ci} 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_cistatic int ghes_read_estatus(struct ghes *ghes, 42862306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus, 42962306a36Sopenharmony_ci u64 *buf_paddr, enum fixed_addresses fixmap_idx) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci int rc; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx); 43462306a36Sopenharmony_ci if (rc) 43562306a36Sopenharmony_ci return rc; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci rc = __ghes_check_estatus(ghes, estatus); 43862306a36Sopenharmony_ci if (rc) 43962306a36Sopenharmony_ci return rc; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx, 44262306a36Sopenharmony_ci cper_estatus_len(estatus)); 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_cistatic void ghes_clear_estatus(struct ghes *ghes, 44662306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus, 44762306a36Sopenharmony_ci u64 buf_paddr, enum fixed_addresses fixmap_idx) 44862306a36Sopenharmony_ci{ 44962306a36Sopenharmony_ci estatus->block_status = 0; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci if (!buf_paddr) 45262306a36Sopenharmony_ci return; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci ghes_copy_tofrom_phys(estatus, buf_paddr, 45562306a36Sopenharmony_ci sizeof(estatus->block_status), 0, 45662306a36Sopenharmony_ci fixmap_idx); 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci /* 45962306a36Sopenharmony_ci * GHESv2 type HEST entries introduce support for error acknowledgment, 46062306a36Sopenharmony_ci * so only acknowledge the error if this support is present. 46162306a36Sopenharmony_ci */ 46262306a36Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) 46362306a36Sopenharmony_ci ghes_ack_error(ghes->generic_v2); 46462306a36Sopenharmony_ci} 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci/* 46762306a36Sopenharmony_ci * Called as task_work before returning to user-space. 46862306a36Sopenharmony_ci * Ensure any queued work has been done before we return to the context that 46962306a36Sopenharmony_ci * triggered the notification. 47062306a36Sopenharmony_ci */ 47162306a36Sopenharmony_cistatic void ghes_kick_task_work(struct callback_head *head) 47262306a36Sopenharmony_ci{ 47362306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus; 47462306a36Sopenharmony_ci struct ghes_estatus_node *estatus_node; 47562306a36Sopenharmony_ci u32 node_len; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci estatus_node = container_of(head, struct ghes_estatus_node, task_work); 47862306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE)) 47962306a36Sopenharmony_ci memory_failure_queue_kick(estatus_node->task_work_cpu); 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 48262306a36Sopenharmony_ci node_len = GHES_ESTATUS_NODE_LEN(cper_estatus_len(estatus)); 48362306a36Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, node_len); 48462306a36Sopenharmony_ci} 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_cistatic bool ghes_do_memory_failure(u64 physical_addr, int flags) 48762306a36Sopenharmony_ci{ 48862306a36Sopenharmony_ci unsigned long pfn; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE)) 49162306a36Sopenharmony_ci return false; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci pfn = PHYS_PFN(physical_addr); 49462306a36Sopenharmony_ci if (!pfn_valid(pfn) && !arch_is_platform_page(physical_addr)) { 49562306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 49662306a36Sopenharmony_ci "Invalid address in generic error data: %#llx\n", 49762306a36Sopenharmony_ci physical_addr); 49862306a36Sopenharmony_ci return false; 49962306a36Sopenharmony_ci } 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci memory_failure_queue(pfn, flags); 50262306a36Sopenharmony_ci return true; 50362306a36Sopenharmony_ci} 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_cistatic bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, 50662306a36Sopenharmony_ci int sev, bool sync) 50762306a36Sopenharmony_ci{ 50862306a36Sopenharmony_ci int flags = -1; 50962306a36Sopenharmony_ci int sec_sev = ghes_severity(gdata->error_severity); 51062306a36Sopenharmony_ci struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci if (!(mem_err->validation_bits & CPER_MEM_VALID_PA)) 51362306a36Sopenharmony_ci return false; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci /* iff following two events can be handled properly by now */ 51662306a36Sopenharmony_ci if (sec_sev == GHES_SEV_CORRECTED && 51762306a36Sopenharmony_ci (gdata->flags & CPER_SEC_ERROR_THRESHOLD_EXCEEDED)) 51862306a36Sopenharmony_ci flags = MF_SOFT_OFFLINE; 51962306a36Sopenharmony_ci if (sev == GHES_SEV_RECOVERABLE && sec_sev == GHES_SEV_RECOVERABLE) 52062306a36Sopenharmony_ci flags = sync ? MF_ACTION_REQUIRED : 0; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci if (flags != -1) 52362306a36Sopenharmony_ci return ghes_do_memory_failure(mem_err->physical_addr, flags); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci return false; 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, 52962306a36Sopenharmony_ci int sev, bool sync) 53062306a36Sopenharmony_ci{ 53162306a36Sopenharmony_ci struct cper_sec_proc_arm *err = acpi_hest_get_payload(gdata); 53262306a36Sopenharmony_ci int flags = sync ? MF_ACTION_REQUIRED : 0; 53362306a36Sopenharmony_ci bool queued = false; 53462306a36Sopenharmony_ci int sec_sev, i; 53562306a36Sopenharmony_ci char *p; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci log_arm_hw_error(err); 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci sec_sev = ghes_severity(gdata->error_severity); 54062306a36Sopenharmony_ci if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE) 54162306a36Sopenharmony_ci return false; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci p = (char *)(err + 1); 54462306a36Sopenharmony_ci for (i = 0; i < err->err_info_num; i++) { 54562306a36Sopenharmony_ci struct cper_arm_err_info *err_info = (struct cper_arm_err_info *)p; 54662306a36Sopenharmony_ci bool is_cache = (err_info->type == CPER_ARM_CACHE_ERROR); 54762306a36Sopenharmony_ci bool has_pa = (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR); 54862306a36Sopenharmony_ci const char *error_type = "unknown error"; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* 55162306a36Sopenharmony_ci * The field (err_info->error_info & BIT(26)) is fixed to set to 55262306a36Sopenharmony_ci * 1 in some old firmware of HiSilicon Kunpeng920. We assume that 55362306a36Sopenharmony_ci * firmware won't mix corrected errors in an uncorrected section, 55462306a36Sopenharmony_ci * and don't filter out 'corrected' error here. 55562306a36Sopenharmony_ci */ 55662306a36Sopenharmony_ci if (is_cache && has_pa) { 55762306a36Sopenharmony_ci queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags); 55862306a36Sopenharmony_ci p += err_info->length; 55962306a36Sopenharmony_ci continue; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci if (err_info->type < ARRAY_SIZE(cper_proc_error_type_strs)) 56362306a36Sopenharmony_ci error_type = cper_proc_error_type_strs[err_info->type]; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 56662306a36Sopenharmony_ci "Unhandled processor error type: %s\n", 56762306a36Sopenharmony_ci error_type); 56862306a36Sopenharmony_ci p += err_info->length; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci return queued; 57262306a36Sopenharmony_ci} 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci/* 57562306a36Sopenharmony_ci * PCIe AER errors need to be sent to the AER driver for reporting and 57662306a36Sopenharmony_ci * recovery. The GHES severities map to the following AER severities and 57762306a36Sopenharmony_ci * require the following handling: 57862306a36Sopenharmony_ci * 57962306a36Sopenharmony_ci * GHES_SEV_CORRECTABLE -> AER_CORRECTABLE 58062306a36Sopenharmony_ci * These need to be reported by the AER driver but no recovery is 58162306a36Sopenharmony_ci * necessary. 58262306a36Sopenharmony_ci * GHES_SEV_RECOVERABLE -> AER_NONFATAL 58362306a36Sopenharmony_ci * GHES_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL 58462306a36Sopenharmony_ci * These both need to be reported and recovered from by the AER driver. 58562306a36Sopenharmony_ci * GHES_SEV_PANIC does not make it to this handling since the kernel must 58662306a36Sopenharmony_ci * panic. 58762306a36Sopenharmony_ci */ 58862306a36Sopenharmony_cistatic void ghes_handle_aer(struct acpi_hest_generic_data *gdata) 58962306a36Sopenharmony_ci{ 59062306a36Sopenharmony_ci#ifdef CONFIG_ACPI_APEI_PCIEAER 59162306a36Sopenharmony_ci struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && 59462306a36Sopenharmony_ci pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) { 59562306a36Sopenharmony_ci unsigned int devfn; 59662306a36Sopenharmony_ci int aer_severity; 59762306a36Sopenharmony_ci u8 *aer_info; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci devfn = PCI_DEVFN(pcie_err->device_id.device, 60062306a36Sopenharmony_ci pcie_err->device_id.function); 60162306a36Sopenharmony_ci aer_severity = cper_severity_to_aer(gdata->error_severity); 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci /* 60462306a36Sopenharmony_ci * If firmware reset the component to contain 60562306a36Sopenharmony_ci * the error, we must reinitialize it before 60662306a36Sopenharmony_ci * use, so treat it as a fatal AER error. 60762306a36Sopenharmony_ci */ 60862306a36Sopenharmony_ci if (gdata->flags & CPER_SEC_RESET) 60962306a36Sopenharmony_ci aer_severity = AER_FATAL; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci aer_info = (void *)gen_pool_alloc(ghes_estatus_pool, 61262306a36Sopenharmony_ci sizeof(struct aer_capability_regs)); 61362306a36Sopenharmony_ci if (!aer_info) 61462306a36Sopenharmony_ci return; 61562306a36Sopenharmony_ci memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs)); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci aer_recover_queue(pcie_err->device_id.segment, 61862306a36Sopenharmony_ci pcie_err->device_id.bus, 61962306a36Sopenharmony_ci devfn, aer_severity, 62062306a36Sopenharmony_ci (struct aer_capability_regs *) 62162306a36Sopenharmony_ci aer_info); 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci#endif 62462306a36Sopenharmony_ci} 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ciint ghes_register_vendor_record_notifier(struct notifier_block *nb) 62962306a36Sopenharmony_ci{ 63062306a36Sopenharmony_ci return blocking_notifier_chain_register(&vendor_record_notify_list, nb); 63162306a36Sopenharmony_ci} 63262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_register_vendor_record_notifier); 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_civoid ghes_unregister_vendor_record_notifier(struct notifier_block *nb) 63562306a36Sopenharmony_ci{ 63662306a36Sopenharmony_ci blocking_notifier_chain_unregister(&vendor_record_notify_list, nb); 63762306a36Sopenharmony_ci} 63862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier); 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_cistatic void ghes_vendor_record_work_func(struct work_struct *work) 64162306a36Sopenharmony_ci{ 64262306a36Sopenharmony_ci struct ghes_vendor_record_entry *entry; 64362306a36Sopenharmony_ci struct acpi_hest_generic_data *gdata; 64462306a36Sopenharmony_ci u32 len; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci entry = container_of(work, struct ghes_vendor_record_entry, work); 64762306a36Sopenharmony_ci gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry); 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci blocking_notifier_call_chain(&vendor_record_notify_list, 65062306a36Sopenharmony_ci entry->error_severity, gdata); 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata)); 65362306a36Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)entry, len); 65462306a36Sopenharmony_ci} 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_cistatic void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata, 65762306a36Sopenharmony_ci int sev) 65862306a36Sopenharmony_ci{ 65962306a36Sopenharmony_ci struct acpi_hest_generic_data *copied_gdata; 66062306a36Sopenharmony_ci struct ghes_vendor_record_entry *entry; 66162306a36Sopenharmony_ci u32 len; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata)); 66462306a36Sopenharmony_ci entry = (void *)gen_pool_alloc(ghes_estatus_pool, len); 66562306a36Sopenharmony_ci if (!entry) 66662306a36Sopenharmony_ci return; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci copied_gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry); 66962306a36Sopenharmony_ci memcpy(copied_gdata, gdata, acpi_hest_get_record_size(gdata)); 67062306a36Sopenharmony_ci entry->error_severity = sev; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci INIT_WORK(&entry->work, ghes_vendor_record_work_func); 67362306a36Sopenharmony_ci schedule_work(&entry->work); 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic bool ghes_do_proc(struct ghes *ghes, 67762306a36Sopenharmony_ci const struct acpi_hest_generic_status *estatus) 67862306a36Sopenharmony_ci{ 67962306a36Sopenharmony_ci int sev, sec_sev; 68062306a36Sopenharmony_ci struct acpi_hest_generic_data *gdata; 68162306a36Sopenharmony_ci guid_t *sec_type; 68262306a36Sopenharmony_ci const guid_t *fru_id = &guid_null; 68362306a36Sopenharmony_ci char *fru_text = ""; 68462306a36Sopenharmony_ci bool queued = false; 68562306a36Sopenharmony_ci bool sync = is_hest_sync_notify(ghes); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci sev = ghes_severity(estatus->error_severity); 68862306a36Sopenharmony_ci apei_estatus_for_each_section(estatus, gdata) { 68962306a36Sopenharmony_ci sec_type = (guid_t *)gdata->section_type; 69062306a36Sopenharmony_ci sec_sev = ghes_severity(gdata->error_severity); 69162306a36Sopenharmony_ci if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) 69262306a36Sopenharmony_ci fru_id = (guid_t *)gdata->fru_id; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) 69562306a36Sopenharmony_ci fru_text = gdata->fru_text; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { 69862306a36Sopenharmony_ci struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err); 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci arch_apei_report_mem_error(sev, mem_err); 70362306a36Sopenharmony_ci queued = ghes_handle_memory_failure(gdata, sev, sync); 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { 70662306a36Sopenharmony_ci ghes_handle_aer(gdata); 70762306a36Sopenharmony_ci } 70862306a36Sopenharmony_ci else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { 70962306a36Sopenharmony_ci queued = ghes_handle_arm_hw_error(gdata, sev, sync); 71062306a36Sopenharmony_ci } else { 71162306a36Sopenharmony_ci void *err = acpi_hest_get_payload(gdata); 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci ghes_defer_non_standard_event(gdata, sev); 71462306a36Sopenharmony_ci log_non_standard_event(sec_type, fru_id, fru_text, 71562306a36Sopenharmony_ci sec_sev, err, 71662306a36Sopenharmony_ci gdata->error_data_length); 71762306a36Sopenharmony_ci } 71862306a36Sopenharmony_ci } 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci return queued; 72162306a36Sopenharmony_ci} 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_cistatic void __ghes_print_estatus(const char *pfx, 72462306a36Sopenharmony_ci const struct acpi_hest_generic *generic, 72562306a36Sopenharmony_ci const struct acpi_hest_generic_status *estatus) 72662306a36Sopenharmony_ci{ 72762306a36Sopenharmony_ci static atomic_t seqno; 72862306a36Sopenharmony_ci unsigned int curr_seqno; 72962306a36Sopenharmony_ci char pfx_seq[64]; 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci if (pfx == NULL) { 73262306a36Sopenharmony_ci if (ghes_severity(estatus->error_severity) <= 73362306a36Sopenharmony_ci GHES_SEV_CORRECTED) 73462306a36Sopenharmony_ci pfx = KERN_WARNING; 73562306a36Sopenharmony_ci else 73662306a36Sopenharmony_ci pfx = KERN_ERR; 73762306a36Sopenharmony_ci } 73862306a36Sopenharmony_ci curr_seqno = atomic_inc_return(&seqno); 73962306a36Sopenharmony_ci snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno); 74062306a36Sopenharmony_ci printk("%s""Hardware error from APEI Generic Hardware Error Source: %d\n", 74162306a36Sopenharmony_ci pfx_seq, generic->header.source_id); 74262306a36Sopenharmony_ci cper_estatus_print(pfx_seq, estatus); 74362306a36Sopenharmony_ci} 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_cistatic int ghes_print_estatus(const char *pfx, 74662306a36Sopenharmony_ci const struct acpi_hest_generic *generic, 74762306a36Sopenharmony_ci const struct acpi_hest_generic_status *estatus) 74862306a36Sopenharmony_ci{ 74962306a36Sopenharmony_ci /* Not more than 2 messages every 5 seconds */ 75062306a36Sopenharmony_ci static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2); 75162306a36Sopenharmony_ci static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5*HZ, 2); 75262306a36Sopenharmony_ci struct ratelimit_state *ratelimit; 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci if (ghes_severity(estatus->error_severity) <= GHES_SEV_CORRECTED) 75562306a36Sopenharmony_ci ratelimit = &ratelimit_corrected; 75662306a36Sopenharmony_ci else 75762306a36Sopenharmony_ci ratelimit = &ratelimit_uncorrected; 75862306a36Sopenharmony_ci if (__ratelimit(ratelimit)) { 75962306a36Sopenharmony_ci __ghes_print_estatus(pfx, generic, estatus); 76062306a36Sopenharmony_ci return 1; 76162306a36Sopenharmony_ci } 76262306a36Sopenharmony_ci return 0; 76362306a36Sopenharmony_ci} 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci/* 76662306a36Sopenharmony_ci * GHES error status reporting throttle, to report more kinds of 76762306a36Sopenharmony_ci * errors, instead of just most frequently occurred errors. 76862306a36Sopenharmony_ci */ 76962306a36Sopenharmony_cistatic int ghes_estatus_cached(struct acpi_hest_generic_status *estatus) 77062306a36Sopenharmony_ci{ 77162306a36Sopenharmony_ci u32 len; 77262306a36Sopenharmony_ci int i, cached = 0; 77362306a36Sopenharmony_ci unsigned long long now; 77462306a36Sopenharmony_ci struct ghes_estatus_cache *cache; 77562306a36Sopenharmony_ci struct acpi_hest_generic_status *cache_estatus; 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci len = cper_estatus_len(estatus); 77862306a36Sopenharmony_ci rcu_read_lock(); 77962306a36Sopenharmony_ci for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) { 78062306a36Sopenharmony_ci cache = rcu_dereference(ghes_estatus_caches[i]); 78162306a36Sopenharmony_ci if (cache == NULL) 78262306a36Sopenharmony_ci continue; 78362306a36Sopenharmony_ci if (len != cache->estatus_len) 78462306a36Sopenharmony_ci continue; 78562306a36Sopenharmony_ci cache_estatus = GHES_ESTATUS_FROM_CACHE(cache); 78662306a36Sopenharmony_ci if (memcmp(estatus, cache_estatus, len)) 78762306a36Sopenharmony_ci continue; 78862306a36Sopenharmony_ci atomic_inc(&cache->count); 78962306a36Sopenharmony_ci now = sched_clock(); 79062306a36Sopenharmony_ci if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC) 79162306a36Sopenharmony_ci cached = 1; 79262306a36Sopenharmony_ci break; 79362306a36Sopenharmony_ci } 79462306a36Sopenharmony_ci rcu_read_unlock(); 79562306a36Sopenharmony_ci return cached; 79662306a36Sopenharmony_ci} 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_cistatic struct ghes_estatus_cache *ghes_estatus_cache_alloc( 79962306a36Sopenharmony_ci struct acpi_hest_generic *generic, 80062306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus) 80162306a36Sopenharmony_ci{ 80262306a36Sopenharmony_ci int alloced; 80362306a36Sopenharmony_ci u32 len, cache_len; 80462306a36Sopenharmony_ci struct ghes_estatus_cache *cache; 80562306a36Sopenharmony_ci struct acpi_hest_generic_status *cache_estatus; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci alloced = atomic_add_return(1, &ghes_estatus_cache_alloced); 80862306a36Sopenharmony_ci if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) { 80962306a36Sopenharmony_ci atomic_dec(&ghes_estatus_cache_alloced); 81062306a36Sopenharmony_ci return NULL; 81162306a36Sopenharmony_ci } 81262306a36Sopenharmony_ci len = cper_estatus_len(estatus); 81362306a36Sopenharmony_ci cache_len = GHES_ESTATUS_CACHE_LEN(len); 81462306a36Sopenharmony_ci cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len); 81562306a36Sopenharmony_ci if (!cache) { 81662306a36Sopenharmony_ci atomic_dec(&ghes_estatus_cache_alloced); 81762306a36Sopenharmony_ci return NULL; 81862306a36Sopenharmony_ci } 81962306a36Sopenharmony_ci cache_estatus = GHES_ESTATUS_FROM_CACHE(cache); 82062306a36Sopenharmony_ci memcpy(cache_estatus, estatus, len); 82162306a36Sopenharmony_ci cache->estatus_len = len; 82262306a36Sopenharmony_ci atomic_set(&cache->count, 0); 82362306a36Sopenharmony_ci cache->generic = generic; 82462306a36Sopenharmony_ci cache->time_in = sched_clock(); 82562306a36Sopenharmony_ci return cache; 82662306a36Sopenharmony_ci} 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_cistatic void ghes_estatus_cache_rcu_free(struct rcu_head *head) 82962306a36Sopenharmony_ci{ 83062306a36Sopenharmony_ci struct ghes_estatus_cache *cache; 83162306a36Sopenharmony_ci u32 len; 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci cache = container_of(head, struct ghes_estatus_cache, rcu); 83462306a36Sopenharmony_ci len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache)); 83562306a36Sopenharmony_ci len = GHES_ESTATUS_CACHE_LEN(len); 83662306a36Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len); 83762306a36Sopenharmony_ci atomic_dec(&ghes_estatus_cache_alloced); 83862306a36Sopenharmony_ci} 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_cistatic void 84162306a36Sopenharmony_cighes_estatus_cache_add(struct acpi_hest_generic *generic, 84262306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus) 84362306a36Sopenharmony_ci{ 84462306a36Sopenharmony_ci unsigned long long now, duration, period, max_period = 0; 84562306a36Sopenharmony_ci struct ghes_estatus_cache *cache, *new_cache; 84662306a36Sopenharmony_ci struct ghes_estatus_cache __rcu *victim; 84762306a36Sopenharmony_ci int i, slot = -1, count; 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci new_cache = ghes_estatus_cache_alloc(generic, estatus); 85062306a36Sopenharmony_ci if (!new_cache) 85162306a36Sopenharmony_ci return; 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_ci rcu_read_lock(); 85462306a36Sopenharmony_ci now = sched_clock(); 85562306a36Sopenharmony_ci for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) { 85662306a36Sopenharmony_ci cache = rcu_dereference(ghes_estatus_caches[i]); 85762306a36Sopenharmony_ci if (cache == NULL) { 85862306a36Sopenharmony_ci slot = i; 85962306a36Sopenharmony_ci break; 86062306a36Sopenharmony_ci } 86162306a36Sopenharmony_ci duration = now - cache->time_in; 86262306a36Sopenharmony_ci if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) { 86362306a36Sopenharmony_ci slot = i; 86462306a36Sopenharmony_ci break; 86562306a36Sopenharmony_ci } 86662306a36Sopenharmony_ci count = atomic_read(&cache->count); 86762306a36Sopenharmony_ci period = duration; 86862306a36Sopenharmony_ci do_div(period, (count + 1)); 86962306a36Sopenharmony_ci if (period > max_period) { 87062306a36Sopenharmony_ci max_period = period; 87162306a36Sopenharmony_ci slot = i; 87262306a36Sopenharmony_ci } 87362306a36Sopenharmony_ci } 87462306a36Sopenharmony_ci rcu_read_unlock(); 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci if (slot != -1) { 87762306a36Sopenharmony_ci /* 87862306a36Sopenharmony_ci * Use release semantics to ensure that ghes_estatus_cached() 87962306a36Sopenharmony_ci * running on another CPU will see the updated cache fields if 88062306a36Sopenharmony_ci * it can see the new value of the pointer. 88162306a36Sopenharmony_ci */ 88262306a36Sopenharmony_ci victim = xchg_release(&ghes_estatus_caches[slot], 88362306a36Sopenharmony_ci RCU_INITIALIZER(new_cache)); 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci /* 88662306a36Sopenharmony_ci * At this point, victim may point to a cached item different 88762306a36Sopenharmony_ci * from the one based on which we selected the slot. Instead of 88862306a36Sopenharmony_ci * going to the loop again to pick another slot, let's just 88962306a36Sopenharmony_ci * drop the other item anyway: this may cause a false cache 89062306a36Sopenharmony_ci * miss later on, but that won't cause any problems. 89162306a36Sopenharmony_ci */ 89262306a36Sopenharmony_ci if (victim) 89362306a36Sopenharmony_ci call_rcu(&unrcu_pointer(victim)->rcu, 89462306a36Sopenharmony_ci ghes_estatus_cache_rcu_free); 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci} 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_cistatic void __ghes_panic(struct ghes *ghes, 89962306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus, 90062306a36Sopenharmony_ci u64 buf_paddr, enum fixed_addresses fixmap_idx) 90162306a36Sopenharmony_ci{ 90262306a36Sopenharmony_ci __ghes_print_estatus(KERN_EMERG, ghes->generic, estatus); 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx); 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ci /* reboot to log the error! */ 90762306a36Sopenharmony_ci if (!panic_timeout) 90862306a36Sopenharmony_ci panic_timeout = ghes_panic_timeout; 90962306a36Sopenharmony_ci panic("Fatal hardware error!"); 91062306a36Sopenharmony_ci} 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_cistatic int ghes_proc(struct ghes *ghes) 91362306a36Sopenharmony_ci{ 91462306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus = ghes->estatus; 91562306a36Sopenharmony_ci u64 buf_paddr; 91662306a36Sopenharmony_ci int rc; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci rc = ghes_read_estatus(ghes, estatus, &buf_paddr, FIX_APEI_GHES_IRQ); 91962306a36Sopenharmony_ci if (rc) 92062306a36Sopenharmony_ci goto out; 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci if (ghes_severity(estatus->error_severity) >= GHES_SEV_PANIC) 92362306a36Sopenharmony_ci __ghes_panic(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ); 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci if (!ghes_estatus_cached(estatus)) { 92662306a36Sopenharmony_ci if (ghes_print_estatus(NULL, ghes->generic, estatus)) 92762306a36Sopenharmony_ci ghes_estatus_cache_add(ghes->generic, estatus); 92862306a36Sopenharmony_ci } 92962306a36Sopenharmony_ci ghes_do_proc(ghes, estatus); 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ciout: 93262306a36Sopenharmony_ci ghes_clear_estatus(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ); 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci return rc; 93562306a36Sopenharmony_ci} 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_cistatic void ghes_add_timer(struct ghes *ghes) 93862306a36Sopenharmony_ci{ 93962306a36Sopenharmony_ci struct acpi_hest_generic *g = ghes->generic; 94062306a36Sopenharmony_ci unsigned long expire; 94162306a36Sopenharmony_ci 94262306a36Sopenharmony_ci if (!g->notify.poll_interval) { 94362306a36Sopenharmony_ci pr_warn(FW_WARN GHES_PFX "Poll interval is 0 for generic hardware error source: %d, disabled.\n", 94462306a36Sopenharmony_ci g->header.source_id); 94562306a36Sopenharmony_ci return; 94662306a36Sopenharmony_ci } 94762306a36Sopenharmony_ci expire = jiffies + msecs_to_jiffies(g->notify.poll_interval); 94862306a36Sopenharmony_ci ghes->timer.expires = round_jiffies_relative(expire); 94962306a36Sopenharmony_ci add_timer(&ghes->timer); 95062306a36Sopenharmony_ci} 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_cistatic void ghes_poll_func(struct timer_list *t) 95362306a36Sopenharmony_ci{ 95462306a36Sopenharmony_ci struct ghes *ghes = from_timer(ghes, t, timer); 95562306a36Sopenharmony_ci unsigned long flags; 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 95862306a36Sopenharmony_ci ghes_proc(ghes); 95962306a36Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 96062306a36Sopenharmony_ci if (!(ghes->flags & GHES_EXITING)) 96162306a36Sopenharmony_ci ghes_add_timer(ghes); 96262306a36Sopenharmony_ci} 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_cistatic irqreturn_t ghes_irq_func(int irq, void *data) 96562306a36Sopenharmony_ci{ 96662306a36Sopenharmony_ci struct ghes *ghes = data; 96762306a36Sopenharmony_ci unsigned long flags; 96862306a36Sopenharmony_ci int rc; 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 97162306a36Sopenharmony_ci rc = ghes_proc(ghes); 97262306a36Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 97362306a36Sopenharmony_ci if (rc) 97462306a36Sopenharmony_ci return IRQ_NONE; 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci return IRQ_HANDLED; 97762306a36Sopenharmony_ci} 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_cistatic int ghes_notify_hed(struct notifier_block *this, unsigned long event, 98062306a36Sopenharmony_ci void *data) 98162306a36Sopenharmony_ci{ 98262306a36Sopenharmony_ci struct ghes *ghes; 98362306a36Sopenharmony_ci unsigned long flags; 98462306a36Sopenharmony_ci int ret = NOTIFY_DONE; 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 98762306a36Sopenharmony_ci rcu_read_lock(); 98862306a36Sopenharmony_ci list_for_each_entry_rcu(ghes, &ghes_hed, list) { 98962306a36Sopenharmony_ci if (!ghes_proc(ghes)) 99062306a36Sopenharmony_ci ret = NOTIFY_OK; 99162306a36Sopenharmony_ci } 99262306a36Sopenharmony_ci rcu_read_unlock(); 99362306a36Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci return ret; 99662306a36Sopenharmony_ci} 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_cistatic struct notifier_block ghes_notifier_hed = { 99962306a36Sopenharmony_ci .notifier_call = ghes_notify_hed, 100062306a36Sopenharmony_ci}; 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_ci/* 100362306a36Sopenharmony_ci * Handlers for CPER records may not be NMI safe. For example, 100462306a36Sopenharmony_ci * memory_failure_queue() takes spinlocks and calls schedule_work_on(). 100562306a36Sopenharmony_ci * In any NMI-like handler, memory from ghes_estatus_pool is used to save 100662306a36Sopenharmony_ci * estatus, and added to the ghes_estatus_llist. irq_work_queue() causes 100762306a36Sopenharmony_ci * ghes_proc_in_irq() to run in IRQ context where each estatus in 100862306a36Sopenharmony_ci * ghes_estatus_llist is processed. 100962306a36Sopenharmony_ci * 101062306a36Sopenharmony_ci * Memory from the ghes_estatus_pool is also used with the ghes_estatus_cache 101162306a36Sopenharmony_ci * to suppress frequent messages. 101262306a36Sopenharmony_ci */ 101362306a36Sopenharmony_cistatic struct llist_head ghes_estatus_llist; 101462306a36Sopenharmony_cistatic struct irq_work ghes_proc_irq_work; 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_cistatic void ghes_proc_in_irq(struct irq_work *irq_work) 101762306a36Sopenharmony_ci{ 101862306a36Sopenharmony_ci struct llist_node *llnode, *next; 101962306a36Sopenharmony_ci struct ghes_estatus_node *estatus_node; 102062306a36Sopenharmony_ci struct acpi_hest_generic *generic; 102162306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus; 102262306a36Sopenharmony_ci bool task_work_pending; 102362306a36Sopenharmony_ci u32 len, node_len; 102462306a36Sopenharmony_ci int ret; 102562306a36Sopenharmony_ci 102662306a36Sopenharmony_ci llnode = llist_del_all(&ghes_estatus_llist); 102762306a36Sopenharmony_ci /* 102862306a36Sopenharmony_ci * Because the time order of estatus in list is reversed, 102962306a36Sopenharmony_ci * revert it back to proper order. 103062306a36Sopenharmony_ci */ 103162306a36Sopenharmony_ci llnode = llist_reverse_order(llnode); 103262306a36Sopenharmony_ci while (llnode) { 103362306a36Sopenharmony_ci next = llnode->next; 103462306a36Sopenharmony_ci estatus_node = llist_entry(llnode, struct ghes_estatus_node, 103562306a36Sopenharmony_ci llnode); 103662306a36Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 103762306a36Sopenharmony_ci len = cper_estatus_len(estatus); 103862306a36Sopenharmony_ci node_len = GHES_ESTATUS_NODE_LEN(len); 103962306a36Sopenharmony_ci task_work_pending = ghes_do_proc(estatus_node->ghes, estatus); 104062306a36Sopenharmony_ci if (!ghes_estatus_cached(estatus)) { 104162306a36Sopenharmony_ci generic = estatus_node->generic; 104262306a36Sopenharmony_ci if (ghes_print_estatus(NULL, generic, estatus)) 104362306a36Sopenharmony_ci ghes_estatus_cache_add(generic, estatus); 104462306a36Sopenharmony_ci } 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ci if (task_work_pending && current->mm) { 104762306a36Sopenharmony_ci estatus_node->task_work.func = ghes_kick_task_work; 104862306a36Sopenharmony_ci estatus_node->task_work_cpu = smp_processor_id(); 104962306a36Sopenharmony_ci ret = task_work_add(current, &estatus_node->task_work, 105062306a36Sopenharmony_ci TWA_RESUME); 105162306a36Sopenharmony_ci if (ret) 105262306a36Sopenharmony_ci estatus_node->task_work.func = NULL; 105362306a36Sopenharmony_ci } 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci if (!estatus_node->task_work.func) 105662306a36Sopenharmony_ci gen_pool_free(ghes_estatus_pool, 105762306a36Sopenharmony_ci (unsigned long)estatus_node, node_len); 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci llnode = next; 106062306a36Sopenharmony_ci } 106162306a36Sopenharmony_ci} 106262306a36Sopenharmony_ci 106362306a36Sopenharmony_cistatic void ghes_print_queued_estatus(void) 106462306a36Sopenharmony_ci{ 106562306a36Sopenharmony_ci struct llist_node *llnode; 106662306a36Sopenharmony_ci struct ghes_estatus_node *estatus_node; 106762306a36Sopenharmony_ci struct acpi_hest_generic *generic; 106862306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus; 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_ci llnode = llist_del_all(&ghes_estatus_llist); 107162306a36Sopenharmony_ci /* 107262306a36Sopenharmony_ci * Because the time order of estatus in list is reversed, 107362306a36Sopenharmony_ci * revert it back to proper order. 107462306a36Sopenharmony_ci */ 107562306a36Sopenharmony_ci llnode = llist_reverse_order(llnode); 107662306a36Sopenharmony_ci while (llnode) { 107762306a36Sopenharmony_ci estatus_node = llist_entry(llnode, struct ghes_estatus_node, 107862306a36Sopenharmony_ci llnode); 107962306a36Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 108062306a36Sopenharmony_ci generic = estatus_node->generic; 108162306a36Sopenharmony_ci ghes_print_estatus(NULL, generic, estatus); 108262306a36Sopenharmony_ci llnode = llnode->next; 108362306a36Sopenharmony_ci } 108462306a36Sopenharmony_ci} 108562306a36Sopenharmony_ci 108662306a36Sopenharmony_cistatic int ghes_in_nmi_queue_one_entry(struct ghes *ghes, 108762306a36Sopenharmony_ci enum fixed_addresses fixmap_idx) 108862306a36Sopenharmony_ci{ 108962306a36Sopenharmony_ci struct acpi_hest_generic_status *estatus, tmp_header; 109062306a36Sopenharmony_ci struct ghes_estatus_node *estatus_node; 109162306a36Sopenharmony_ci u32 len, node_len; 109262306a36Sopenharmony_ci u64 buf_paddr; 109362306a36Sopenharmony_ci int sev, rc; 109462306a36Sopenharmony_ci 109562306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG)) 109662306a36Sopenharmony_ci return -EOPNOTSUPP; 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci rc = __ghes_peek_estatus(ghes, &tmp_header, &buf_paddr, fixmap_idx); 109962306a36Sopenharmony_ci if (rc) { 110062306a36Sopenharmony_ci ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); 110162306a36Sopenharmony_ci return rc; 110262306a36Sopenharmony_ci } 110362306a36Sopenharmony_ci 110462306a36Sopenharmony_ci rc = __ghes_check_estatus(ghes, &tmp_header); 110562306a36Sopenharmony_ci if (rc) { 110662306a36Sopenharmony_ci ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); 110762306a36Sopenharmony_ci return rc; 110862306a36Sopenharmony_ci } 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci len = cper_estatus_len(&tmp_header); 111162306a36Sopenharmony_ci node_len = GHES_ESTATUS_NODE_LEN(len); 111262306a36Sopenharmony_ci estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len); 111362306a36Sopenharmony_ci if (!estatus_node) 111462306a36Sopenharmony_ci return -ENOMEM; 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci estatus_node->ghes = ghes; 111762306a36Sopenharmony_ci estatus_node->generic = ghes->generic; 111862306a36Sopenharmony_ci estatus_node->task_work.func = NULL; 111962306a36Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 112062306a36Sopenharmony_ci 112162306a36Sopenharmony_ci if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) { 112262306a36Sopenharmony_ci ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx); 112362306a36Sopenharmony_ci rc = -ENOENT; 112462306a36Sopenharmony_ci goto no_work; 112562306a36Sopenharmony_ci } 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci sev = ghes_severity(estatus->error_severity); 112862306a36Sopenharmony_ci if (sev >= GHES_SEV_PANIC) { 112962306a36Sopenharmony_ci ghes_print_queued_estatus(); 113062306a36Sopenharmony_ci __ghes_panic(ghes, estatus, buf_paddr, fixmap_idx); 113162306a36Sopenharmony_ci } 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_ci ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); 113462306a36Sopenharmony_ci 113562306a36Sopenharmony_ci /* This error has been reported before, don't process it again. */ 113662306a36Sopenharmony_ci if (ghes_estatus_cached(estatus)) 113762306a36Sopenharmony_ci goto no_work; 113862306a36Sopenharmony_ci 113962306a36Sopenharmony_ci llist_add(&estatus_node->llnode, &ghes_estatus_llist); 114062306a36Sopenharmony_ci 114162306a36Sopenharmony_ci return rc; 114262306a36Sopenharmony_ci 114362306a36Sopenharmony_cino_work: 114462306a36Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, 114562306a36Sopenharmony_ci node_len); 114662306a36Sopenharmony_ci 114762306a36Sopenharmony_ci return rc; 114862306a36Sopenharmony_ci} 114962306a36Sopenharmony_ci 115062306a36Sopenharmony_cistatic int ghes_in_nmi_spool_from_list(struct list_head *rcu_list, 115162306a36Sopenharmony_ci enum fixed_addresses fixmap_idx) 115262306a36Sopenharmony_ci{ 115362306a36Sopenharmony_ci int ret = -ENOENT; 115462306a36Sopenharmony_ci struct ghes *ghes; 115562306a36Sopenharmony_ci 115662306a36Sopenharmony_ci rcu_read_lock(); 115762306a36Sopenharmony_ci list_for_each_entry_rcu(ghes, rcu_list, list) { 115862306a36Sopenharmony_ci if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx)) 115962306a36Sopenharmony_ci ret = 0; 116062306a36Sopenharmony_ci } 116162306a36Sopenharmony_ci rcu_read_unlock(); 116262306a36Sopenharmony_ci 116362306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG) && !ret) 116462306a36Sopenharmony_ci irq_work_queue(&ghes_proc_irq_work); 116562306a36Sopenharmony_ci 116662306a36Sopenharmony_ci return ret; 116762306a36Sopenharmony_ci} 116862306a36Sopenharmony_ci 116962306a36Sopenharmony_ci#ifdef CONFIG_ACPI_APEI_SEA 117062306a36Sopenharmony_cistatic LIST_HEAD(ghes_sea); 117162306a36Sopenharmony_ci 117262306a36Sopenharmony_ci/* 117362306a36Sopenharmony_ci * Return 0 only if one of the SEA error sources successfully reported an error 117462306a36Sopenharmony_ci * record sent from the firmware. 117562306a36Sopenharmony_ci */ 117662306a36Sopenharmony_ciint ghes_notify_sea(void) 117762306a36Sopenharmony_ci{ 117862306a36Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sea); 117962306a36Sopenharmony_ci int rv; 118062306a36Sopenharmony_ci 118162306a36Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_sea); 118262306a36Sopenharmony_ci rv = ghes_in_nmi_spool_from_list(&ghes_sea, FIX_APEI_GHES_SEA); 118362306a36Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_sea); 118462306a36Sopenharmony_ci 118562306a36Sopenharmony_ci return rv; 118662306a36Sopenharmony_ci} 118762306a36Sopenharmony_ci 118862306a36Sopenharmony_cistatic void ghes_sea_add(struct ghes *ghes) 118962306a36Sopenharmony_ci{ 119062306a36Sopenharmony_ci mutex_lock(&ghes_list_mutex); 119162306a36Sopenharmony_ci list_add_rcu(&ghes->list, &ghes_sea); 119262306a36Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 119362306a36Sopenharmony_ci} 119462306a36Sopenharmony_ci 119562306a36Sopenharmony_cistatic void ghes_sea_remove(struct ghes *ghes) 119662306a36Sopenharmony_ci{ 119762306a36Sopenharmony_ci mutex_lock(&ghes_list_mutex); 119862306a36Sopenharmony_ci list_del_rcu(&ghes->list); 119962306a36Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 120062306a36Sopenharmony_ci synchronize_rcu(); 120162306a36Sopenharmony_ci} 120262306a36Sopenharmony_ci#else /* CONFIG_ACPI_APEI_SEA */ 120362306a36Sopenharmony_cistatic inline void ghes_sea_add(struct ghes *ghes) { } 120462306a36Sopenharmony_cistatic inline void ghes_sea_remove(struct ghes *ghes) { } 120562306a36Sopenharmony_ci#endif /* CONFIG_ACPI_APEI_SEA */ 120662306a36Sopenharmony_ci 120762306a36Sopenharmony_ci#ifdef CONFIG_HAVE_ACPI_APEI_NMI 120862306a36Sopenharmony_ci/* 120962306a36Sopenharmony_ci * NMI may be triggered on any CPU, so ghes_in_nmi is used for 121062306a36Sopenharmony_ci * having only one concurrent reader. 121162306a36Sopenharmony_ci */ 121262306a36Sopenharmony_cistatic atomic_t ghes_in_nmi = ATOMIC_INIT(0); 121362306a36Sopenharmony_ci 121462306a36Sopenharmony_cistatic LIST_HEAD(ghes_nmi); 121562306a36Sopenharmony_ci 121662306a36Sopenharmony_cistatic int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs) 121762306a36Sopenharmony_ci{ 121862306a36Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_nmi); 121962306a36Sopenharmony_ci int ret = NMI_DONE; 122062306a36Sopenharmony_ci 122162306a36Sopenharmony_ci if (!atomic_add_unless(&ghes_in_nmi, 1, 1)) 122262306a36Sopenharmony_ci return ret; 122362306a36Sopenharmony_ci 122462306a36Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_nmi); 122562306a36Sopenharmony_ci if (!ghes_in_nmi_spool_from_list(&ghes_nmi, FIX_APEI_GHES_NMI)) 122662306a36Sopenharmony_ci ret = NMI_HANDLED; 122762306a36Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_nmi); 122862306a36Sopenharmony_ci 122962306a36Sopenharmony_ci atomic_dec(&ghes_in_nmi); 123062306a36Sopenharmony_ci return ret; 123162306a36Sopenharmony_ci} 123262306a36Sopenharmony_ci 123362306a36Sopenharmony_cistatic void ghes_nmi_add(struct ghes *ghes) 123462306a36Sopenharmony_ci{ 123562306a36Sopenharmony_ci mutex_lock(&ghes_list_mutex); 123662306a36Sopenharmony_ci if (list_empty(&ghes_nmi)) 123762306a36Sopenharmony_ci register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes"); 123862306a36Sopenharmony_ci list_add_rcu(&ghes->list, &ghes_nmi); 123962306a36Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 124062306a36Sopenharmony_ci} 124162306a36Sopenharmony_ci 124262306a36Sopenharmony_cistatic void ghes_nmi_remove(struct ghes *ghes) 124362306a36Sopenharmony_ci{ 124462306a36Sopenharmony_ci mutex_lock(&ghes_list_mutex); 124562306a36Sopenharmony_ci list_del_rcu(&ghes->list); 124662306a36Sopenharmony_ci if (list_empty(&ghes_nmi)) 124762306a36Sopenharmony_ci unregister_nmi_handler(NMI_LOCAL, "ghes"); 124862306a36Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 124962306a36Sopenharmony_ci /* 125062306a36Sopenharmony_ci * To synchronize with NMI handler, ghes can only be 125162306a36Sopenharmony_ci * freed after NMI handler finishes. 125262306a36Sopenharmony_ci */ 125362306a36Sopenharmony_ci synchronize_rcu(); 125462306a36Sopenharmony_ci} 125562306a36Sopenharmony_ci#else /* CONFIG_HAVE_ACPI_APEI_NMI */ 125662306a36Sopenharmony_cistatic inline void ghes_nmi_add(struct ghes *ghes) { } 125762306a36Sopenharmony_cistatic inline void ghes_nmi_remove(struct ghes *ghes) { } 125862306a36Sopenharmony_ci#endif /* CONFIG_HAVE_ACPI_APEI_NMI */ 125962306a36Sopenharmony_ci 126062306a36Sopenharmony_cistatic void ghes_nmi_init_cxt(void) 126162306a36Sopenharmony_ci{ 126262306a36Sopenharmony_ci init_irq_work(&ghes_proc_irq_work, ghes_proc_in_irq); 126362306a36Sopenharmony_ci} 126462306a36Sopenharmony_ci 126562306a36Sopenharmony_cistatic int __ghes_sdei_callback(struct ghes *ghes, 126662306a36Sopenharmony_ci enum fixed_addresses fixmap_idx) 126762306a36Sopenharmony_ci{ 126862306a36Sopenharmony_ci if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx)) { 126962306a36Sopenharmony_ci irq_work_queue(&ghes_proc_irq_work); 127062306a36Sopenharmony_ci 127162306a36Sopenharmony_ci return 0; 127262306a36Sopenharmony_ci } 127362306a36Sopenharmony_ci 127462306a36Sopenharmony_ci return -ENOENT; 127562306a36Sopenharmony_ci} 127662306a36Sopenharmony_ci 127762306a36Sopenharmony_cistatic int ghes_sdei_normal_callback(u32 event_num, struct pt_regs *regs, 127862306a36Sopenharmony_ci void *arg) 127962306a36Sopenharmony_ci{ 128062306a36Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sdei_normal); 128162306a36Sopenharmony_ci struct ghes *ghes = arg; 128262306a36Sopenharmony_ci int err; 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_sdei_normal); 128562306a36Sopenharmony_ci err = __ghes_sdei_callback(ghes, FIX_APEI_GHES_SDEI_NORMAL); 128662306a36Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_sdei_normal); 128762306a36Sopenharmony_ci 128862306a36Sopenharmony_ci return err; 128962306a36Sopenharmony_ci} 129062306a36Sopenharmony_ci 129162306a36Sopenharmony_cistatic int ghes_sdei_critical_callback(u32 event_num, struct pt_regs *regs, 129262306a36Sopenharmony_ci void *arg) 129362306a36Sopenharmony_ci{ 129462306a36Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sdei_critical); 129562306a36Sopenharmony_ci struct ghes *ghes = arg; 129662306a36Sopenharmony_ci int err; 129762306a36Sopenharmony_ci 129862306a36Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_sdei_critical); 129962306a36Sopenharmony_ci err = __ghes_sdei_callback(ghes, FIX_APEI_GHES_SDEI_CRITICAL); 130062306a36Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_sdei_critical); 130162306a36Sopenharmony_ci 130262306a36Sopenharmony_ci return err; 130362306a36Sopenharmony_ci} 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_cistatic int apei_sdei_register_ghes(struct ghes *ghes) 130662306a36Sopenharmony_ci{ 130762306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) 130862306a36Sopenharmony_ci return -EOPNOTSUPP; 130962306a36Sopenharmony_ci 131062306a36Sopenharmony_ci return sdei_register_ghes(ghes, ghes_sdei_normal_callback, 131162306a36Sopenharmony_ci ghes_sdei_critical_callback); 131262306a36Sopenharmony_ci} 131362306a36Sopenharmony_ci 131462306a36Sopenharmony_cistatic int apei_sdei_unregister_ghes(struct ghes *ghes) 131562306a36Sopenharmony_ci{ 131662306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) 131762306a36Sopenharmony_ci return -EOPNOTSUPP; 131862306a36Sopenharmony_ci 131962306a36Sopenharmony_ci return sdei_unregister_ghes(ghes); 132062306a36Sopenharmony_ci} 132162306a36Sopenharmony_ci 132262306a36Sopenharmony_cistatic int ghes_probe(struct platform_device *ghes_dev) 132362306a36Sopenharmony_ci{ 132462306a36Sopenharmony_ci struct acpi_hest_generic *generic; 132562306a36Sopenharmony_ci struct ghes *ghes = NULL; 132662306a36Sopenharmony_ci unsigned long flags; 132762306a36Sopenharmony_ci 132862306a36Sopenharmony_ci int rc = -EINVAL; 132962306a36Sopenharmony_ci 133062306a36Sopenharmony_ci generic = *(struct acpi_hest_generic **)ghes_dev->dev.platform_data; 133162306a36Sopenharmony_ci if (!generic->enabled) 133262306a36Sopenharmony_ci return -ENODEV; 133362306a36Sopenharmony_ci 133462306a36Sopenharmony_ci switch (generic->notify.type) { 133562306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_POLLED: 133662306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_EXTERNAL: 133762306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SCI: 133862306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_GSIV: 133962306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_GPIO: 134062306a36Sopenharmony_ci break; 134162306a36Sopenharmony_ci 134262306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SEA: 134362306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ACPI_APEI_SEA)) { 134462306a36Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via SEA is not supported\n", 134562306a36Sopenharmony_ci generic->header.source_id); 134662306a36Sopenharmony_ci rc = -ENOTSUPP; 134762306a36Sopenharmony_ci goto err; 134862306a36Sopenharmony_ci } 134962306a36Sopenharmony_ci break; 135062306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_NMI: 135162306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_HAVE_ACPI_APEI_NMI)) { 135262306a36Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via NMI interrupt is not supported!\n", 135362306a36Sopenharmony_ci generic->header.source_id); 135462306a36Sopenharmony_ci goto err; 135562306a36Sopenharmony_ci } 135662306a36Sopenharmony_ci break; 135762306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 135862306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) { 135962306a36Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via SDE Interface is not supported!\n", 136062306a36Sopenharmony_ci generic->header.source_id); 136162306a36Sopenharmony_ci goto err; 136262306a36Sopenharmony_ci } 136362306a36Sopenharmony_ci break; 136462306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_LOCAL: 136562306a36Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via local interrupt is not supported!\n", 136662306a36Sopenharmony_ci generic->header.source_id); 136762306a36Sopenharmony_ci goto err; 136862306a36Sopenharmony_ci default: 136962306a36Sopenharmony_ci pr_warn(FW_WARN GHES_PFX "Unknown notification type: %u for generic hardware error source: %d\n", 137062306a36Sopenharmony_ci generic->notify.type, generic->header.source_id); 137162306a36Sopenharmony_ci goto err; 137262306a36Sopenharmony_ci } 137362306a36Sopenharmony_ci 137462306a36Sopenharmony_ci rc = -EIO; 137562306a36Sopenharmony_ci if (generic->error_block_length < 137662306a36Sopenharmony_ci sizeof(struct acpi_hest_generic_status)) { 137762306a36Sopenharmony_ci pr_warn(FW_BUG GHES_PFX "Invalid error block length: %u for generic hardware error source: %d\n", 137862306a36Sopenharmony_ci generic->error_block_length, generic->header.source_id); 137962306a36Sopenharmony_ci goto err; 138062306a36Sopenharmony_ci } 138162306a36Sopenharmony_ci ghes = ghes_new(generic); 138262306a36Sopenharmony_ci if (IS_ERR(ghes)) { 138362306a36Sopenharmony_ci rc = PTR_ERR(ghes); 138462306a36Sopenharmony_ci ghes = NULL; 138562306a36Sopenharmony_ci goto err; 138662306a36Sopenharmony_ci } 138762306a36Sopenharmony_ci 138862306a36Sopenharmony_ci switch (generic->notify.type) { 138962306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_POLLED: 139062306a36Sopenharmony_ci timer_setup(&ghes->timer, ghes_poll_func, 0); 139162306a36Sopenharmony_ci ghes_add_timer(ghes); 139262306a36Sopenharmony_ci break; 139362306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_EXTERNAL: 139462306a36Sopenharmony_ci /* External interrupt vector is GSI */ 139562306a36Sopenharmony_ci rc = acpi_gsi_to_irq(generic->notify.vector, &ghes->irq); 139662306a36Sopenharmony_ci if (rc) { 139762306a36Sopenharmony_ci pr_err(GHES_PFX "Failed to map GSI to IRQ for generic hardware error source: %d\n", 139862306a36Sopenharmony_ci generic->header.source_id); 139962306a36Sopenharmony_ci goto err; 140062306a36Sopenharmony_ci } 140162306a36Sopenharmony_ci rc = request_irq(ghes->irq, ghes_irq_func, IRQF_SHARED, 140262306a36Sopenharmony_ci "GHES IRQ", ghes); 140362306a36Sopenharmony_ci if (rc) { 140462306a36Sopenharmony_ci pr_err(GHES_PFX "Failed to register IRQ for generic hardware error source: %d\n", 140562306a36Sopenharmony_ci generic->header.source_id); 140662306a36Sopenharmony_ci goto err; 140762306a36Sopenharmony_ci } 140862306a36Sopenharmony_ci break; 140962306a36Sopenharmony_ci 141062306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SCI: 141162306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_GSIV: 141262306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_GPIO: 141362306a36Sopenharmony_ci mutex_lock(&ghes_list_mutex); 141462306a36Sopenharmony_ci if (list_empty(&ghes_hed)) 141562306a36Sopenharmony_ci register_acpi_hed_notifier(&ghes_notifier_hed); 141662306a36Sopenharmony_ci list_add_rcu(&ghes->list, &ghes_hed); 141762306a36Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 141862306a36Sopenharmony_ci break; 141962306a36Sopenharmony_ci 142062306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SEA: 142162306a36Sopenharmony_ci ghes_sea_add(ghes); 142262306a36Sopenharmony_ci break; 142362306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_NMI: 142462306a36Sopenharmony_ci ghes_nmi_add(ghes); 142562306a36Sopenharmony_ci break; 142662306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 142762306a36Sopenharmony_ci rc = apei_sdei_register_ghes(ghes); 142862306a36Sopenharmony_ci if (rc) 142962306a36Sopenharmony_ci goto err; 143062306a36Sopenharmony_ci break; 143162306a36Sopenharmony_ci default: 143262306a36Sopenharmony_ci BUG(); 143362306a36Sopenharmony_ci } 143462306a36Sopenharmony_ci 143562306a36Sopenharmony_ci platform_set_drvdata(ghes_dev, ghes); 143662306a36Sopenharmony_ci 143762306a36Sopenharmony_ci ghes->dev = &ghes_dev->dev; 143862306a36Sopenharmony_ci 143962306a36Sopenharmony_ci mutex_lock(&ghes_devs_mutex); 144062306a36Sopenharmony_ci list_add_tail(&ghes->elist, &ghes_devs); 144162306a36Sopenharmony_ci mutex_unlock(&ghes_devs_mutex); 144262306a36Sopenharmony_ci 144362306a36Sopenharmony_ci /* Handle any pending errors right away */ 144462306a36Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 144562306a36Sopenharmony_ci ghes_proc(ghes); 144662306a36Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 144762306a36Sopenharmony_ci 144862306a36Sopenharmony_ci return 0; 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_cierr: 145162306a36Sopenharmony_ci if (ghes) { 145262306a36Sopenharmony_ci ghes_fini(ghes); 145362306a36Sopenharmony_ci kfree(ghes); 145462306a36Sopenharmony_ci } 145562306a36Sopenharmony_ci return rc; 145662306a36Sopenharmony_ci} 145762306a36Sopenharmony_ci 145862306a36Sopenharmony_cistatic int ghes_remove(struct platform_device *ghes_dev) 145962306a36Sopenharmony_ci{ 146062306a36Sopenharmony_ci int rc; 146162306a36Sopenharmony_ci struct ghes *ghes; 146262306a36Sopenharmony_ci struct acpi_hest_generic *generic; 146362306a36Sopenharmony_ci 146462306a36Sopenharmony_ci ghes = platform_get_drvdata(ghes_dev); 146562306a36Sopenharmony_ci generic = ghes->generic; 146662306a36Sopenharmony_ci 146762306a36Sopenharmony_ci ghes->flags |= GHES_EXITING; 146862306a36Sopenharmony_ci switch (generic->notify.type) { 146962306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_POLLED: 147062306a36Sopenharmony_ci timer_shutdown_sync(&ghes->timer); 147162306a36Sopenharmony_ci break; 147262306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_EXTERNAL: 147362306a36Sopenharmony_ci free_irq(ghes->irq, ghes); 147462306a36Sopenharmony_ci break; 147562306a36Sopenharmony_ci 147662306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SCI: 147762306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_GSIV: 147862306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_GPIO: 147962306a36Sopenharmony_ci mutex_lock(&ghes_list_mutex); 148062306a36Sopenharmony_ci list_del_rcu(&ghes->list); 148162306a36Sopenharmony_ci if (list_empty(&ghes_hed)) 148262306a36Sopenharmony_ci unregister_acpi_hed_notifier(&ghes_notifier_hed); 148362306a36Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 148462306a36Sopenharmony_ci synchronize_rcu(); 148562306a36Sopenharmony_ci break; 148662306a36Sopenharmony_ci 148762306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SEA: 148862306a36Sopenharmony_ci ghes_sea_remove(ghes); 148962306a36Sopenharmony_ci break; 149062306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_NMI: 149162306a36Sopenharmony_ci ghes_nmi_remove(ghes); 149262306a36Sopenharmony_ci break; 149362306a36Sopenharmony_ci case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 149462306a36Sopenharmony_ci rc = apei_sdei_unregister_ghes(ghes); 149562306a36Sopenharmony_ci if (rc) 149662306a36Sopenharmony_ci return rc; 149762306a36Sopenharmony_ci break; 149862306a36Sopenharmony_ci default: 149962306a36Sopenharmony_ci BUG(); 150062306a36Sopenharmony_ci break; 150162306a36Sopenharmony_ci } 150262306a36Sopenharmony_ci 150362306a36Sopenharmony_ci ghes_fini(ghes); 150462306a36Sopenharmony_ci 150562306a36Sopenharmony_ci mutex_lock(&ghes_devs_mutex); 150662306a36Sopenharmony_ci list_del(&ghes->elist); 150762306a36Sopenharmony_ci mutex_unlock(&ghes_devs_mutex); 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci kfree(ghes); 151062306a36Sopenharmony_ci 151162306a36Sopenharmony_ci return 0; 151262306a36Sopenharmony_ci} 151362306a36Sopenharmony_ci 151462306a36Sopenharmony_cistatic struct platform_driver ghes_platform_driver = { 151562306a36Sopenharmony_ci .driver = { 151662306a36Sopenharmony_ci .name = "GHES", 151762306a36Sopenharmony_ci }, 151862306a36Sopenharmony_ci .probe = ghes_probe, 151962306a36Sopenharmony_ci .remove = ghes_remove, 152062306a36Sopenharmony_ci}; 152162306a36Sopenharmony_ci 152262306a36Sopenharmony_civoid __init acpi_ghes_init(void) 152362306a36Sopenharmony_ci{ 152462306a36Sopenharmony_ci int rc; 152562306a36Sopenharmony_ci 152662306a36Sopenharmony_ci sdei_init(); 152762306a36Sopenharmony_ci 152862306a36Sopenharmony_ci if (acpi_disabled) 152962306a36Sopenharmony_ci return; 153062306a36Sopenharmony_ci 153162306a36Sopenharmony_ci switch (hest_disable) { 153262306a36Sopenharmony_ci case HEST_NOT_FOUND: 153362306a36Sopenharmony_ci return; 153462306a36Sopenharmony_ci case HEST_DISABLED: 153562306a36Sopenharmony_ci pr_info(GHES_PFX "HEST is not enabled!\n"); 153662306a36Sopenharmony_ci return; 153762306a36Sopenharmony_ci default: 153862306a36Sopenharmony_ci break; 153962306a36Sopenharmony_ci } 154062306a36Sopenharmony_ci 154162306a36Sopenharmony_ci if (ghes_disable) { 154262306a36Sopenharmony_ci pr_info(GHES_PFX "GHES is not enabled!\n"); 154362306a36Sopenharmony_ci return; 154462306a36Sopenharmony_ci } 154562306a36Sopenharmony_ci 154662306a36Sopenharmony_ci ghes_nmi_init_cxt(); 154762306a36Sopenharmony_ci 154862306a36Sopenharmony_ci rc = platform_driver_register(&ghes_platform_driver); 154962306a36Sopenharmony_ci if (rc) 155062306a36Sopenharmony_ci return; 155162306a36Sopenharmony_ci 155262306a36Sopenharmony_ci rc = apei_osc_setup(); 155362306a36Sopenharmony_ci if (rc == 0 && osc_sb_apei_support_acked) 155462306a36Sopenharmony_ci pr_info(GHES_PFX "APEI firmware first mode is enabled by APEI bit and WHEA _OSC.\n"); 155562306a36Sopenharmony_ci else if (rc == 0 && !osc_sb_apei_support_acked) 155662306a36Sopenharmony_ci pr_info(GHES_PFX "APEI firmware first mode is enabled by WHEA _OSC.\n"); 155762306a36Sopenharmony_ci else if (rc && osc_sb_apei_support_acked) 155862306a36Sopenharmony_ci pr_info(GHES_PFX "APEI firmware first mode is enabled by APEI bit.\n"); 155962306a36Sopenharmony_ci else 156062306a36Sopenharmony_ci pr_info(GHES_PFX "Failed to enable APEI firmware first mode.\n"); 156162306a36Sopenharmony_ci} 156262306a36Sopenharmony_ci 156362306a36Sopenharmony_ci/* 156462306a36Sopenharmony_ci * Known x86 systems that prefer GHES error reporting: 156562306a36Sopenharmony_ci */ 156662306a36Sopenharmony_cistatic struct acpi_platform_list plat_list[] = { 156762306a36Sopenharmony_ci {"HPE ", "Server ", 0, ACPI_SIG_FADT, all_versions}, 156862306a36Sopenharmony_ci { } /* End */ 156962306a36Sopenharmony_ci}; 157062306a36Sopenharmony_ci 157162306a36Sopenharmony_cistruct list_head *ghes_get_devices(void) 157262306a36Sopenharmony_ci{ 157362306a36Sopenharmony_ci int idx = -1; 157462306a36Sopenharmony_ci 157562306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_X86)) { 157662306a36Sopenharmony_ci idx = acpi_match_platform_list(plat_list); 157762306a36Sopenharmony_ci if (idx < 0) { 157862306a36Sopenharmony_ci if (!ghes_edac_force_enable) 157962306a36Sopenharmony_ci return NULL; 158062306a36Sopenharmony_ci 158162306a36Sopenharmony_ci pr_warn_once("Force-loading ghes_edac on an unsupported platform. You're on your own!\n"); 158262306a36Sopenharmony_ci } 158362306a36Sopenharmony_ci } else if (list_empty(&ghes_devs)) { 158462306a36Sopenharmony_ci return NULL; 158562306a36Sopenharmony_ci } 158662306a36Sopenharmony_ci 158762306a36Sopenharmony_ci return &ghes_devs; 158862306a36Sopenharmony_ci} 158962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_get_devices); 159062306a36Sopenharmony_ci 159162306a36Sopenharmony_civoid ghes_register_report_chain(struct notifier_block *nb) 159262306a36Sopenharmony_ci{ 159362306a36Sopenharmony_ci atomic_notifier_chain_register(&ghes_report_chain, nb); 159462306a36Sopenharmony_ci} 159562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_register_report_chain); 159662306a36Sopenharmony_ci 159762306a36Sopenharmony_civoid ghes_unregister_report_chain(struct notifier_block *nb) 159862306a36Sopenharmony_ci{ 159962306a36Sopenharmony_ci atomic_notifier_chain_unregister(&ghes_report_chain, nb); 160062306a36Sopenharmony_ci} 160162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_unregister_report_chain); 1602