18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * APEI Generic Hardware Error Source support 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Generic Hardware Error Source provides a way to report platform 68c2ecf20Sopenharmony_ci * hardware errors (such as that from chipset). It works in so called 78c2ecf20Sopenharmony_ci * "Firmware First" mode, that is, hardware errors are reported to 88c2ecf20Sopenharmony_ci * firmware firstly, then reported to Linux by firmware. This way, 98c2ecf20Sopenharmony_ci * some non-standard hardware error registers or non-standard hardware 108c2ecf20Sopenharmony_ci * link can be checked by firmware to produce more hardware error 118c2ecf20Sopenharmony_ci * information for Linux. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * For more information about Generic Hardware Error Source, please 148c2ecf20Sopenharmony_ci * refer to ACPI Specification version 4.0, section 17.3.2.6 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * Copyright 2010,2011 Intel Corp. 178c2ecf20Sopenharmony_ci * Author: Huang Ying <ying.huang@intel.com> 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/arm_sdei.h> 218c2ecf20Sopenharmony_ci#include <linux/kernel.h> 228c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 238c2ecf20Sopenharmony_ci#include <linux/init.h> 248c2ecf20Sopenharmony_ci#include <linux/acpi.h> 258c2ecf20Sopenharmony_ci#include <linux/io.h> 268c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 278c2ecf20Sopenharmony_ci#include <linux/timer.h> 288c2ecf20Sopenharmony_ci#include <linux/cper.h> 298c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 308c2ecf20Sopenharmony_ci#include <linux/mutex.h> 318c2ecf20Sopenharmony_ci#include <linux/ratelimit.h> 328c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 338c2ecf20Sopenharmony_ci#include <linux/irq_work.h> 348c2ecf20Sopenharmony_ci#include <linux/llist.h> 358c2ecf20Sopenharmony_ci#include <linux/genalloc.h> 368c2ecf20Sopenharmony_ci#include <linux/pci.h> 378c2ecf20Sopenharmony_ci#include <linux/pfn.h> 388c2ecf20Sopenharmony_ci#include <linux/aer.h> 398c2ecf20Sopenharmony_ci#include <linux/nmi.h> 408c2ecf20Sopenharmony_ci#include <linux/sched/clock.h> 418c2ecf20Sopenharmony_ci#include <linux/uuid.h> 428c2ecf20Sopenharmony_ci#include <linux/ras.h> 438c2ecf20Sopenharmony_ci#include <linux/task_work.h> 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#include <acpi/actbl1.h> 468c2ecf20Sopenharmony_ci#include <acpi/ghes.h> 478c2ecf20Sopenharmony_ci#include <acpi/apei.h> 488c2ecf20Sopenharmony_ci#include <asm/fixmap.h> 498c2ecf20Sopenharmony_ci#include <asm/tlbflush.h> 508c2ecf20Sopenharmony_ci#include <ras/ras_event.h> 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#include "apei-internal.h" 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci#define GHES_PFX "GHES: " 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci#define GHES_ESTATUS_MAX_SIZE 65536 578c2ecf20Sopenharmony_ci#define GHES_ESOURCE_PREALLOC_MAX_SIZE 65536 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci#define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER 3 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/* This is just an estimation for memory pool allocation */ 628c2ecf20Sopenharmony_ci#define GHES_ESTATUS_CACHE_AVG_SIZE 512 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci#define GHES_ESTATUS_CACHES_SIZE 4 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci#define GHES_ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL 678c2ecf20Sopenharmony_ci/* Prevent too many caches are allocated because of RCU */ 688c2ecf20Sopenharmony_ci#define GHES_ESTATUS_CACHE_ALLOCED_MAX (GHES_ESTATUS_CACHES_SIZE * 3 / 2) 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci#define GHES_ESTATUS_CACHE_LEN(estatus_len) \ 718c2ecf20Sopenharmony_ci (sizeof(struct ghes_estatus_cache) + (estatus_len)) 728c2ecf20Sopenharmony_ci#define GHES_ESTATUS_FROM_CACHE(estatus_cache) \ 738c2ecf20Sopenharmony_ci ((struct acpi_hest_generic_status *) \ 748c2ecf20Sopenharmony_ci ((struct ghes_estatus_cache *)(estatus_cache) + 1)) 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci#define GHES_ESTATUS_NODE_LEN(estatus_len) \ 778c2ecf20Sopenharmony_ci (sizeof(struct ghes_estatus_node) + (estatus_len)) 788c2ecf20Sopenharmony_ci#define GHES_ESTATUS_FROM_NODE(estatus_node) \ 798c2ecf20Sopenharmony_ci ((struct acpi_hest_generic_status *) \ 808c2ecf20Sopenharmony_ci ((struct ghes_estatus_node *)(estatus_node) + 1)) 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci#define GHES_VENDOR_ENTRY_LEN(gdata_len) \ 838c2ecf20Sopenharmony_ci (sizeof(struct ghes_vendor_record_entry) + (gdata_len)) 848c2ecf20Sopenharmony_ci#define GHES_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \ 858c2ecf20Sopenharmony_ci ((struct acpi_hest_generic_data *) \ 868c2ecf20Sopenharmony_ci ((struct ghes_vendor_record_entry *)(vendor_entry) + 1)) 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci/* 898c2ecf20Sopenharmony_ci * NMI-like notifications vary by architecture, before the compiler can prune 908c2ecf20Sopenharmony_ci * unused static functions it needs a value for these enums. 918c2ecf20Sopenharmony_ci */ 928c2ecf20Sopenharmony_ci#ifndef CONFIG_ARM_SDE_INTERFACE 938c2ecf20Sopenharmony_ci#define FIX_APEI_GHES_SDEI_NORMAL __end_of_fixed_addresses 948c2ecf20Sopenharmony_ci#define FIX_APEI_GHES_SDEI_CRITICAL __end_of_fixed_addresses 958c2ecf20Sopenharmony_ci#endif 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic inline bool is_hest_type_generic_v2(struct ghes *ghes) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci/* 1038c2ecf20Sopenharmony_ci * A platform may describe one error source for the handling of synchronous 1048c2ecf20Sopenharmony_ci * errors (e.g. MCE or SEA), or for handling asynchronous errors (e.g. SCI 1058c2ecf20Sopenharmony_ci * or External Interrupt). On x86, the HEST notifications are always 1068c2ecf20Sopenharmony_ci * asynchronous, so only SEA on ARM is delivered as a synchronous 1078c2ecf20Sopenharmony_ci * notification. 1088c2ecf20Sopenharmony_ci */ 1098c2ecf20Sopenharmony_cistatic inline bool is_hest_sync_notify(struct ghes *ghes) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci u8 notify_type = ghes->generic->notify.type; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci return notify_type == ACPI_HEST_NOTIFY_SEA; 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci/* 1178c2ecf20Sopenharmony_ci * This driver isn't really modular, however for the time being, 1188c2ecf20Sopenharmony_ci * continuing to use module_param is the easiest way to remain 1198c2ecf20Sopenharmony_ci * compatible with existing boot arg use cases. 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_cibool ghes_disable; 1228c2ecf20Sopenharmony_cimodule_param_named(disable, ghes_disable, bool, 0); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci/* 1258c2ecf20Sopenharmony_ci * All error sources notified with HED (Hardware Error Device) share a 1268c2ecf20Sopenharmony_ci * single notifier callback, so they need to be linked and checked one 1278c2ecf20Sopenharmony_ci * by one. This holds true for NMI too. 1288c2ecf20Sopenharmony_ci * 1298c2ecf20Sopenharmony_ci * RCU is used for these lists, so ghes_list_mutex is only used for 1308c2ecf20Sopenharmony_ci * list changing, not for traversing. 1318c2ecf20Sopenharmony_ci */ 1328c2ecf20Sopenharmony_cistatic LIST_HEAD(ghes_hed); 1338c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(ghes_list_mutex); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci/* 1368c2ecf20Sopenharmony_ci * Because the memory area used to transfer hardware error information 1378c2ecf20Sopenharmony_ci * from BIOS to Linux can be determined only in NMI, IRQ or timer 1388c2ecf20Sopenharmony_ci * handler, but general ioremap can not be used in atomic context, so 1398c2ecf20Sopenharmony_ci * the fixmap is used instead. 1408c2ecf20Sopenharmony_ci * 1418c2ecf20Sopenharmony_ci * This spinlock is used to prevent the fixmap entry from being used 1428c2ecf20Sopenharmony_ci * simultaneously. 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(ghes_notify_lock_irq); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistruct ghes_vendor_record_entry { 1478c2ecf20Sopenharmony_ci struct work_struct work; 1488c2ecf20Sopenharmony_ci int error_severity; 1498c2ecf20Sopenharmony_ci char vendor_record[]; 1508c2ecf20Sopenharmony_ci}; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic struct gen_pool *ghes_estatus_pool; 1538c2ecf20Sopenharmony_cistatic unsigned long ghes_estatus_pool_size_request; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic struct ghes_estatus_cache *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE]; 1568c2ecf20Sopenharmony_cistatic atomic_t ghes_estatus_cache_alloced; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic int ghes_panic_timeout __read_mostly = 30; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci phys_addr_t paddr; 1638c2ecf20Sopenharmony_ci pgprot_t prot; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci paddr = PFN_PHYS(pfn); 1668c2ecf20Sopenharmony_ci prot = arch_apei_get_mem_attribute(paddr); 1678c2ecf20Sopenharmony_ci __set_fixmap(fixmap_idx, paddr, prot); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci return (void __iomem *) __fix_to_virt(fixmap_idx); 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci int _idx = virt_to_fix((unsigned long)vaddr); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci WARN_ON_ONCE(fixmap_idx != _idx); 1778c2ecf20Sopenharmony_ci clear_fixmap(fixmap_idx); 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ciint ghes_estatus_pool_init(unsigned int num_ghes) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci unsigned long addr, len; 1838c2ecf20Sopenharmony_ci int rc; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci ghes_estatus_pool = gen_pool_create(GHES_ESTATUS_POOL_MIN_ALLOC_ORDER, -1); 1868c2ecf20Sopenharmony_ci if (!ghes_estatus_pool) 1878c2ecf20Sopenharmony_ci return -ENOMEM; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci len = GHES_ESTATUS_CACHE_AVG_SIZE * GHES_ESTATUS_CACHE_ALLOCED_MAX; 1908c2ecf20Sopenharmony_ci len += (num_ghes * GHES_ESOURCE_PREALLOC_MAX_SIZE); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci ghes_estatus_pool_size_request = PAGE_ALIGN(len); 1938c2ecf20Sopenharmony_ci addr = (unsigned long)vmalloc(PAGE_ALIGN(len)); 1948c2ecf20Sopenharmony_ci if (!addr) 1958c2ecf20Sopenharmony_ci goto err_pool_alloc; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci rc = gen_pool_add(ghes_estatus_pool, addr, PAGE_ALIGN(len), -1); 1988c2ecf20Sopenharmony_ci if (rc) 1998c2ecf20Sopenharmony_ci goto err_pool_add; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci return 0; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cierr_pool_add: 2048c2ecf20Sopenharmony_ci vfree((void *)addr); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cierr_pool_alloc: 2078c2ecf20Sopenharmony_ci gen_pool_destroy(ghes_estatus_pool); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci return -ENOMEM; 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic int map_gen_v2(struct ghes *ghes) 2138c2ecf20Sopenharmony_ci{ 2148c2ecf20Sopenharmony_ci return apei_map_generic_address(&ghes->generic_v2->read_ack_register); 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic void unmap_gen_v2(struct ghes *ghes) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci apei_unmap_generic_address(&ghes->generic_v2->read_ack_register); 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic void ghes_ack_error(struct acpi_hest_generic_v2 *gv2) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci int rc; 2258c2ecf20Sopenharmony_ci u64 val = 0; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci rc = apei_read(&val, &gv2->read_ack_register); 2288c2ecf20Sopenharmony_ci if (rc) 2298c2ecf20Sopenharmony_ci return; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset; 2328c2ecf20Sopenharmony_ci val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci apei_write(val, &gv2->read_ack_register); 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic struct ghes *ghes_new(struct acpi_hest_generic *generic) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci struct ghes *ghes; 2408c2ecf20Sopenharmony_ci unsigned int error_block_length; 2418c2ecf20Sopenharmony_ci int rc; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci ghes = kzalloc(sizeof(*ghes), GFP_KERNEL); 2448c2ecf20Sopenharmony_ci if (!ghes) 2458c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci ghes->generic = generic; 2488c2ecf20Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) { 2498c2ecf20Sopenharmony_ci rc = map_gen_v2(ghes); 2508c2ecf20Sopenharmony_ci if (rc) 2518c2ecf20Sopenharmony_ci goto err_free; 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci rc = apei_map_generic_address(&generic->error_status_address); 2558c2ecf20Sopenharmony_ci if (rc) 2568c2ecf20Sopenharmony_ci goto err_unmap_read_ack_addr; 2578c2ecf20Sopenharmony_ci error_block_length = generic->error_block_length; 2588c2ecf20Sopenharmony_ci if (error_block_length > GHES_ESTATUS_MAX_SIZE) { 2598c2ecf20Sopenharmony_ci pr_warn(FW_WARN GHES_PFX 2608c2ecf20Sopenharmony_ci "Error status block length is too long: %u for " 2618c2ecf20Sopenharmony_ci "generic hardware error source: %d.\n", 2628c2ecf20Sopenharmony_ci error_block_length, generic->header.source_id); 2638c2ecf20Sopenharmony_ci error_block_length = GHES_ESTATUS_MAX_SIZE; 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci ghes->estatus = kmalloc(error_block_length, GFP_KERNEL); 2668c2ecf20Sopenharmony_ci if (!ghes->estatus) { 2678c2ecf20Sopenharmony_ci rc = -ENOMEM; 2688c2ecf20Sopenharmony_ci goto err_unmap_status_addr; 2698c2ecf20Sopenharmony_ci } 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci return ghes; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cierr_unmap_status_addr: 2748c2ecf20Sopenharmony_ci apei_unmap_generic_address(&generic->error_status_address); 2758c2ecf20Sopenharmony_cierr_unmap_read_ack_addr: 2768c2ecf20Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) 2778c2ecf20Sopenharmony_ci unmap_gen_v2(ghes); 2788c2ecf20Sopenharmony_cierr_free: 2798c2ecf20Sopenharmony_ci kfree(ghes); 2808c2ecf20Sopenharmony_ci return ERR_PTR(rc); 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic void ghes_fini(struct ghes *ghes) 2848c2ecf20Sopenharmony_ci{ 2858c2ecf20Sopenharmony_ci kfree(ghes->estatus); 2868c2ecf20Sopenharmony_ci apei_unmap_generic_address(&ghes->generic->error_status_address); 2878c2ecf20Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) 2888c2ecf20Sopenharmony_ci unmap_gen_v2(ghes); 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic inline int ghes_severity(int severity) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci switch (severity) { 2948c2ecf20Sopenharmony_ci case CPER_SEV_INFORMATIONAL: 2958c2ecf20Sopenharmony_ci return GHES_SEV_NO; 2968c2ecf20Sopenharmony_ci case CPER_SEV_CORRECTED: 2978c2ecf20Sopenharmony_ci return GHES_SEV_CORRECTED; 2988c2ecf20Sopenharmony_ci case CPER_SEV_RECOVERABLE: 2998c2ecf20Sopenharmony_ci return GHES_SEV_RECOVERABLE; 3008c2ecf20Sopenharmony_ci case CPER_SEV_FATAL: 3018c2ecf20Sopenharmony_ci return GHES_SEV_PANIC; 3028c2ecf20Sopenharmony_ci default: 3038c2ecf20Sopenharmony_ci /* Unknown, go panic */ 3048c2ecf20Sopenharmony_ci return GHES_SEV_PANIC; 3058c2ecf20Sopenharmony_ci } 3068c2ecf20Sopenharmony_ci} 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_cistatic void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, 3098c2ecf20Sopenharmony_ci int from_phys, 3108c2ecf20Sopenharmony_ci enum fixed_addresses fixmap_idx) 3118c2ecf20Sopenharmony_ci{ 3128c2ecf20Sopenharmony_ci void __iomem *vaddr; 3138c2ecf20Sopenharmony_ci u64 offset; 3148c2ecf20Sopenharmony_ci u32 trunk; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci while (len > 0) { 3178c2ecf20Sopenharmony_ci offset = paddr - (paddr & PAGE_MASK); 3188c2ecf20Sopenharmony_ci vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx); 3198c2ecf20Sopenharmony_ci trunk = PAGE_SIZE - offset; 3208c2ecf20Sopenharmony_ci trunk = min(trunk, len); 3218c2ecf20Sopenharmony_ci if (from_phys) 3228c2ecf20Sopenharmony_ci memcpy_fromio(buffer, vaddr + offset, trunk); 3238c2ecf20Sopenharmony_ci else 3248c2ecf20Sopenharmony_ci memcpy_toio(vaddr + offset, buffer, trunk); 3258c2ecf20Sopenharmony_ci len -= trunk; 3268c2ecf20Sopenharmony_ci paddr += trunk; 3278c2ecf20Sopenharmony_ci buffer += trunk; 3288c2ecf20Sopenharmony_ci ghes_unmap(vaddr, fixmap_idx); 3298c2ecf20Sopenharmony_ci } 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci/* Check the top-level record header has an appropriate size. */ 3338c2ecf20Sopenharmony_cistatic int __ghes_check_estatus(struct ghes *ghes, 3348c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus) 3358c2ecf20Sopenharmony_ci{ 3368c2ecf20Sopenharmony_ci u32 len = cper_estatus_len(estatus); 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci if (len < sizeof(*estatus)) { 3398c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status block!\n"); 3408c2ecf20Sopenharmony_ci return -EIO; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci if (len > ghes->generic->error_block_length) { 3448c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status block length!\n"); 3458c2ecf20Sopenharmony_ci return -EIO; 3468c2ecf20Sopenharmony_ci } 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci if (cper_estatus_check_header(estatus)) { 3498c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n"); 3508c2ecf20Sopenharmony_ci return -EIO; 3518c2ecf20Sopenharmony_ci } 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci return 0; 3548c2ecf20Sopenharmony_ci} 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci/* Read the CPER block, returning its address, and header in estatus. */ 3578c2ecf20Sopenharmony_cistatic int __ghes_peek_estatus(struct ghes *ghes, 3588c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus, 3598c2ecf20Sopenharmony_ci u64 *buf_paddr, enum fixed_addresses fixmap_idx) 3608c2ecf20Sopenharmony_ci{ 3618c2ecf20Sopenharmony_ci struct acpi_hest_generic *g = ghes->generic; 3628c2ecf20Sopenharmony_ci int rc; 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci rc = apei_read(buf_paddr, &g->error_status_address); 3658c2ecf20Sopenharmony_ci if (rc) { 3668c2ecf20Sopenharmony_ci *buf_paddr = 0; 3678c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 3688c2ecf20Sopenharmony_ci"Failed to read error status block address for hardware error source: %d.\n", 3698c2ecf20Sopenharmony_ci g->header.source_id); 3708c2ecf20Sopenharmony_ci return -EIO; 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci if (!*buf_paddr) 3738c2ecf20Sopenharmony_ci return -ENOENT; 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1, 3768c2ecf20Sopenharmony_ci fixmap_idx); 3778c2ecf20Sopenharmony_ci if (!estatus->block_status) { 3788c2ecf20Sopenharmony_ci *buf_paddr = 0; 3798c2ecf20Sopenharmony_ci return -ENOENT; 3808c2ecf20Sopenharmony_ci } 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci return 0; 3838c2ecf20Sopenharmony_ci} 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_cistatic int __ghes_read_estatus(struct acpi_hest_generic_status *estatus, 3868c2ecf20Sopenharmony_ci u64 buf_paddr, enum fixed_addresses fixmap_idx, 3878c2ecf20Sopenharmony_ci size_t buf_len) 3888c2ecf20Sopenharmony_ci{ 3898c2ecf20Sopenharmony_ci ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx); 3908c2ecf20Sopenharmony_ci if (cper_estatus_check(estatus)) { 3918c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 3928c2ecf20Sopenharmony_ci "Failed to read error status block!\n"); 3938c2ecf20Sopenharmony_ci return -EIO; 3948c2ecf20Sopenharmony_ci } 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci return 0; 3978c2ecf20Sopenharmony_ci} 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_cistatic int ghes_read_estatus(struct ghes *ghes, 4008c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus, 4018c2ecf20Sopenharmony_ci u64 *buf_paddr, enum fixed_addresses fixmap_idx) 4028c2ecf20Sopenharmony_ci{ 4038c2ecf20Sopenharmony_ci int rc; 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx); 4068c2ecf20Sopenharmony_ci if (rc) 4078c2ecf20Sopenharmony_ci return rc; 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci rc = __ghes_check_estatus(ghes, estatus); 4108c2ecf20Sopenharmony_ci if (rc) 4118c2ecf20Sopenharmony_ci return rc; 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx, 4148c2ecf20Sopenharmony_ci cper_estatus_len(estatus)); 4158c2ecf20Sopenharmony_ci} 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic void ghes_clear_estatus(struct ghes *ghes, 4188c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus, 4198c2ecf20Sopenharmony_ci u64 buf_paddr, enum fixed_addresses fixmap_idx) 4208c2ecf20Sopenharmony_ci{ 4218c2ecf20Sopenharmony_ci estatus->block_status = 0; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci if (!buf_paddr) 4248c2ecf20Sopenharmony_ci return; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci ghes_copy_tofrom_phys(estatus, buf_paddr, 4278c2ecf20Sopenharmony_ci sizeof(estatus->block_status), 0, 4288c2ecf20Sopenharmony_ci fixmap_idx); 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci /* 4318c2ecf20Sopenharmony_ci * GHESv2 type HEST entries introduce support for error acknowledgment, 4328c2ecf20Sopenharmony_ci * so only acknowledge the error if this support is present. 4338c2ecf20Sopenharmony_ci */ 4348c2ecf20Sopenharmony_ci if (is_hest_type_generic_v2(ghes)) 4358c2ecf20Sopenharmony_ci ghes_ack_error(ghes->generic_v2); 4368c2ecf20Sopenharmony_ci} 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci/* 4398c2ecf20Sopenharmony_ci * Called as task_work before returning to user-space. 4408c2ecf20Sopenharmony_ci * Ensure any queued work has been done before we return to the context that 4418c2ecf20Sopenharmony_ci * triggered the notification. 4428c2ecf20Sopenharmony_ci */ 4438c2ecf20Sopenharmony_cistatic void ghes_kick_task_work(struct callback_head *head) 4448c2ecf20Sopenharmony_ci{ 4458c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus; 4468c2ecf20Sopenharmony_ci struct ghes_estatus_node *estatus_node; 4478c2ecf20Sopenharmony_ci u32 node_len; 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci estatus_node = container_of(head, struct ghes_estatus_node, task_work); 4508c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE)) 4518c2ecf20Sopenharmony_ci memory_failure_queue_kick(estatus_node->task_work_cpu); 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 4548c2ecf20Sopenharmony_ci node_len = GHES_ESTATUS_NODE_LEN(cper_estatus_len(estatus)); 4558c2ecf20Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, node_len); 4568c2ecf20Sopenharmony_ci} 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_cistatic bool ghes_do_memory_failure(u64 physical_addr, int flags) 4598c2ecf20Sopenharmony_ci{ 4608c2ecf20Sopenharmony_ci unsigned long pfn; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE)) 4638c2ecf20Sopenharmony_ci return false; 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci pfn = PHYS_PFN(physical_addr); 4668c2ecf20Sopenharmony_ci if (!pfn_valid(pfn)) { 4678c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 4688c2ecf20Sopenharmony_ci "Invalid address in generic error data: %#llx\n", 4698c2ecf20Sopenharmony_ci physical_addr); 4708c2ecf20Sopenharmony_ci return false; 4718c2ecf20Sopenharmony_ci } 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci memory_failure_queue(pfn, flags); 4748c2ecf20Sopenharmony_ci return true; 4758c2ecf20Sopenharmony_ci} 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_cistatic bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, 4788c2ecf20Sopenharmony_ci int sev, bool sync) 4798c2ecf20Sopenharmony_ci{ 4808c2ecf20Sopenharmony_ci int flags = -1; 4818c2ecf20Sopenharmony_ci int sec_sev = ghes_severity(gdata->error_severity); 4828c2ecf20Sopenharmony_ci struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci if (!(mem_err->validation_bits & CPER_MEM_VALID_PA)) 4858c2ecf20Sopenharmony_ci return false; 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci /* iff following two events can be handled properly by now */ 4888c2ecf20Sopenharmony_ci if (sec_sev == GHES_SEV_CORRECTED && 4898c2ecf20Sopenharmony_ci (gdata->flags & CPER_SEC_ERROR_THRESHOLD_EXCEEDED)) 4908c2ecf20Sopenharmony_ci flags = MF_SOFT_OFFLINE; 4918c2ecf20Sopenharmony_ci if (sev == GHES_SEV_RECOVERABLE && sec_sev == GHES_SEV_RECOVERABLE) 4928c2ecf20Sopenharmony_ci flags = sync ? MF_ACTION_REQUIRED : 0; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci if (flags != -1) 4958c2ecf20Sopenharmony_ci return ghes_do_memory_failure(mem_err->physical_addr, flags); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci return false; 4988c2ecf20Sopenharmony_ci} 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_cistatic bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, 5018c2ecf20Sopenharmony_ci int sev, bool sync) 5028c2ecf20Sopenharmony_ci{ 5038c2ecf20Sopenharmony_ci struct cper_sec_proc_arm *err = acpi_hest_get_payload(gdata); 5048c2ecf20Sopenharmony_ci int flags = sync ? MF_ACTION_REQUIRED : 0; 5058c2ecf20Sopenharmony_ci bool queued = false; 5068c2ecf20Sopenharmony_ci int sec_sev, i; 5078c2ecf20Sopenharmony_ci char *p; 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci log_arm_hw_error(err); 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci sec_sev = ghes_severity(gdata->error_severity); 5128c2ecf20Sopenharmony_ci if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE) 5138c2ecf20Sopenharmony_ci return false; 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci p = (char *)(err + 1); 5168c2ecf20Sopenharmony_ci for (i = 0; i < err->err_info_num; i++) { 5178c2ecf20Sopenharmony_ci struct cper_arm_err_info *err_info = (struct cper_arm_err_info *)p; 5188c2ecf20Sopenharmony_ci bool is_cache = (err_info->type == CPER_ARM_CACHE_ERROR); 5198c2ecf20Sopenharmony_ci bool has_pa = (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR); 5208c2ecf20Sopenharmony_ci const char *error_type = "unknown error"; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci /* 5238c2ecf20Sopenharmony_ci * The field (err_info->error_info & BIT(26)) is fixed to set to 5248c2ecf20Sopenharmony_ci * 1 in some old firmware of HiSilicon Kunpeng920. We assume that 5258c2ecf20Sopenharmony_ci * firmware won't mix corrected errors in an uncorrected section, 5268c2ecf20Sopenharmony_ci * and don't filter out 'corrected' error here. 5278c2ecf20Sopenharmony_ci */ 5288c2ecf20Sopenharmony_ci if (is_cache && has_pa) { 5298c2ecf20Sopenharmony_ci queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags); 5308c2ecf20Sopenharmony_ci p += err_info->length; 5318c2ecf20Sopenharmony_ci continue; 5328c2ecf20Sopenharmony_ci } 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci if (err_info->type < ARRAY_SIZE(cper_proc_error_type_strs)) 5358c2ecf20Sopenharmony_ci error_type = cper_proc_error_type_strs[err_info->type]; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci pr_warn_ratelimited(FW_WARN GHES_PFX 5388c2ecf20Sopenharmony_ci "Unhandled processor error type: %s\n", 5398c2ecf20Sopenharmony_ci error_type); 5408c2ecf20Sopenharmony_ci p += err_info->length; 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci return queued; 5448c2ecf20Sopenharmony_ci} 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci/* 5478c2ecf20Sopenharmony_ci * PCIe AER errors need to be sent to the AER driver for reporting and 5488c2ecf20Sopenharmony_ci * recovery. The GHES severities map to the following AER severities and 5498c2ecf20Sopenharmony_ci * require the following handling: 5508c2ecf20Sopenharmony_ci * 5518c2ecf20Sopenharmony_ci * GHES_SEV_CORRECTABLE -> AER_CORRECTABLE 5528c2ecf20Sopenharmony_ci * These need to be reported by the AER driver but no recovery is 5538c2ecf20Sopenharmony_ci * necessary. 5548c2ecf20Sopenharmony_ci * GHES_SEV_RECOVERABLE -> AER_NONFATAL 5558c2ecf20Sopenharmony_ci * GHES_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL 5568c2ecf20Sopenharmony_ci * These both need to be reported and recovered from by the AER driver. 5578c2ecf20Sopenharmony_ci * GHES_SEV_PANIC does not make it to this handling since the kernel must 5588c2ecf20Sopenharmony_ci * panic. 5598c2ecf20Sopenharmony_ci */ 5608c2ecf20Sopenharmony_cistatic void ghes_handle_aer(struct acpi_hest_generic_data *gdata) 5618c2ecf20Sopenharmony_ci{ 5628c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI_APEI_PCIEAER 5638c2ecf20Sopenharmony_ci struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_ci if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && 5668c2ecf20Sopenharmony_ci pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) { 5678c2ecf20Sopenharmony_ci unsigned int devfn; 5688c2ecf20Sopenharmony_ci int aer_severity; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci devfn = PCI_DEVFN(pcie_err->device_id.device, 5718c2ecf20Sopenharmony_ci pcie_err->device_id.function); 5728c2ecf20Sopenharmony_ci aer_severity = cper_severity_to_aer(gdata->error_severity); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci /* 5758c2ecf20Sopenharmony_ci * If firmware reset the component to contain 5768c2ecf20Sopenharmony_ci * the error, we must reinitialize it before 5778c2ecf20Sopenharmony_ci * use, so treat it as a fatal AER error. 5788c2ecf20Sopenharmony_ci */ 5798c2ecf20Sopenharmony_ci if (gdata->flags & CPER_SEC_RESET) 5808c2ecf20Sopenharmony_ci aer_severity = AER_FATAL; 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci aer_recover_queue(pcie_err->device_id.segment, 5838c2ecf20Sopenharmony_ci pcie_err->device_id.bus, 5848c2ecf20Sopenharmony_ci devfn, aer_severity, 5858c2ecf20Sopenharmony_ci (struct aer_capability_regs *) 5868c2ecf20Sopenharmony_ci pcie_err->aer_info); 5878c2ecf20Sopenharmony_ci } 5888c2ecf20Sopenharmony_ci#endif 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list); 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ciint ghes_register_vendor_record_notifier(struct notifier_block *nb) 5948c2ecf20Sopenharmony_ci{ 5958c2ecf20Sopenharmony_ci return blocking_notifier_chain_register(&vendor_record_notify_list, nb); 5968c2ecf20Sopenharmony_ci} 5978c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_register_vendor_record_notifier); 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_civoid ghes_unregister_vendor_record_notifier(struct notifier_block *nb) 6008c2ecf20Sopenharmony_ci{ 6018c2ecf20Sopenharmony_ci blocking_notifier_chain_unregister(&vendor_record_notify_list, nb); 6028c2ecf20Sopenharmony_ci} 6038c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier); 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_cistatic void ghes_vendor_record_work_func(struct work_struct *work) 6068c2ecf20Sopenharmony_ci{ 6078c2ecf20Sopenharmony_ci struct ghes_vendor_record_entry *entry; 6088c2ecf20Sopenharmony_ci struct acpi_hest_generic_data *gdata; 6098c2ecf20Sopenharmony_ci u32 len; 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ci entry = container_of(work, struct ghes_vendor_record_entry, work); 6128c2ecf20Sopenharmony_ci gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry); 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci blocking_notifier_call_chain(&vendor_record_notify_list, 6158c2ecf20Sopenharmony_ci entry->error_severity, gdata); 6168c2ecf20Sopenharmony_ci 6178c2ecf20Sopenharmony_ci len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata)); 6188c2ecf20Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)entry, len); 6198c2ecf20Sopenharmony_ci} 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_cistatic void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata, 6228c2ecf20Sopenharmony_ci int sev) 6238c2ecf20Sopenharmony_ci{ 6248c2ecf20Sopenharmony_ci struct acpi_hest_generic_data *copied_gdata; 6258c2ecf20Sopenharmony_ci struct ghes_vendor_record_entry *entry; 6268c2ecf20Sopenharmony_ci u32 len; 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata)); 6298c2ecf20Sopenharmony_ci entry = (void *)gen_pool_alloc(ghes_estatus_pool, len); 6308c2ecf20Sopenharmony_ci if (!entry) 6318c2ecf20Sopenharmony_ci return; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci copied_gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry); 6348c2ecf20Sopenharmony_ci memcpy(copied_gdata, gdata, acpi_hest_get_record_size(gdata)); 6358c2ecf20Sopenharmony_ci entry->error_severity = sev; 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci INIT_WORK(&entry->work, ghes_vendor_record_work_func); 6388c2ecf20Sopenharmony_ci schedule_work(&entry->work); 6398c2ecf20Sopenharmony_ci} 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_cistatic bool ghes_do_proc(struct ghes *ghes, 6428c2ecf20Sopenharmony_ci const struct acpi_hest_generic_status *estatus) 6438c2ecf20Sopenharmony_ci{ 6448c2ecf20Sopenharmony_ci int sev, sec_sev; 6458c2ecf20Sopenharmony_ci struct acpi_hest_generic_data *gdata; 6468c2ecf20Sopenharmony_ci guid_t *sec_type; 6478c2ecf20Sopenharmony_ci const guid_t *fru_id = &guid_null; 6488c2ecf20Sopenharmony_ci char *fru_text = ""; 6498c2ecf20Sopenharmony_ci bool queued = false; 6508c2ecf20Sopenharmony_ci bool sync = is_hest_sync_notify(ghes); 6518c2ecf20Sopenharmony_ci 6528c2ecf20Sopenharmony_ci sev = ghes_severity(estatus->error_severity); 6538c2ecf20Sopenharmony_ci apei_estatus_for_each_section(estatus, gdata) { 6548c2ecf20Sopenharmony_ci sec_type = (guid_t *)gdata->section_type; 6558c2ecf20Sopenharmony_ci sec_sev = ghes_severity(gdata->error_severity); 6568c2ecf20Sopenharmony_ci if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) 6578c2ecf20Sopenharmony_ci fru_id = (guid_t *)gdata->fru_id; 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) 6608c2ecf20Sopenharmony_ci fru_text = gdata->fru_text; 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { 6638c2ecf20Sopenharmony_ci struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); 6648c2ecf20Sopenharmony_ci 6658c2ecf20Sopenharmony_ci ghes_edac_report_mem_error(sev, mem_err); 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_ci arch_apei_report_mem_error(sev, mem_err); 6688c2ecf20Sopenharmony_ci queued = ghes_handle_memory_failure(gdata, sev, sync); 6698c2ecf20Sopenharmony_ci } 6708c2ecf20Sopenharmony_ci else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { 6718c2ecf20Sopenharmony_ci ghes_handle_aer(gdata); 6728c2ecf20Sopenharmony_ci } 6738c2ecf20Sopenharmony_ci else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { 6748c2ecf20Sopenharmony_ci queued = ghes_handle_arm_hw_error(gdata, sev, sync); 6758c2ecf20Sopenharmony_ci } else { 6768c2ecf20Sopenharmony_ci void *err = acpi_hest_get_payload(gdata); 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ci ghes_defer_non_standard_event(gdata, sev); 6798c2ecf20Sopenharmony_ci log_non_standard_event(sec_type, fru_id, fru_text, 6808c2ecf20Sopenharmony_ci sec_sev, err, 6818c2ecf20Sopenharmony_ci gdata->error_data_length); 6828c2ecf20Sopenharmony_ci } 6838c2ecf20Sopenharmony_ci } 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci return queued; 6868c2ecf20Sopenharmony_ci} 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_cistatic void __ghes_print_estatus(const char *pfx, 6898c2ecf20Sopenharmony_ci const struct acpi_hest_generic *generic, 6908c2ecf20Sopenharmony_ci const struct acpi_hest_generic_status *estatus) 6918c2ecf20Sopenharmony_ci{ 6928c2ecf20Sopenharmony_ci static atomic_t seqno; 6938c2ecf20Sopenharmony_ci unsigned int curr_seqno; 6948c2ecf20Sopenharmony_ci char pfx_seq[64]; 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_ci if (pfx == NULL) { 6978c2ecf20Sopenharmony_ci if (ghes_severity(estatus->error_severity) <= 6988c2ecf20Sopenharmony_ci GHES_SEV_CORRECTED) 6998c2ecf20Sopenharmony_ci pfx = KERN_WARNING; 7008c2ecf20Sopenharmony_ci else 7018c2ecf20Sopenharmony_ci pfx = KERN_ERR; 7028c2ecf20Sopenharmony_ci } 7038c2ecf20Sopenharmony_ci curr_seqno = atomic_inc_return(&seqno); 7048c2ecf20Sopenharmony_ci snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno); 7058c2ecf20Sopenharmony_ci printk("%s""Hardware error from APEI Generic Hardware Error Source: %d\n", 7068c2ecf20Sopenharmony_ci pfx_seq, generic->header.source_id); 7078c2ecf20Sopenharmony_ci cper_estatus_print(pfx_seq, estatus); 7088c2ecf20Sopenharmony_ci} 7098c2ecf20Sopenharmony_ci 7108c2ecf20Sopenharmony_cistatic int ghes_print_estatus(const char *pfx, 7118c2ecf20Sopenharmony_ci const struct acpi_hest_generic *generic, 7128c2ecf20Sopenharmony_ci const struct acpi_hest_generic_status *estatus) 7138c2ecf20Sopenharmony_ci{ 7148c2ecf20Sopenharmony_ci /* Not more than 2 messages every 5 seconds */ 7158c2ecf20Sopenharmony_ci static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2); 7168c2ecf20Sopenharmony_ci static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5*HZ, 2); 7178c2ecf20Sopenharmony_ci struct ratelimit_state *ratelimit; 7188c2ecf20Sopenharmony_ci 7198c2ecf20Sopenharmony_ci if (ghes_severity(estatus->error_severity) <= GHES_SEV_CORRECTED) 7208c2ecf20Sopenharmony_ci ratelimit = &ratelimit_corrected; 7218c2ecf20Sopenharmony_ci else 7228c2ecf20Sopenharmony_ci ratelimit = &ratelimit_uncorrected; 7238c2ecf20Sopenharmony_ci if (__ratelimit(ratelimit)) { 7248c2ecf20Sopenharmony_ci __ghes_print_estatus(pfx, generic, estatus); 7258c2ecf20Sopenharmony_ci return 1; 7268c2ecf20Sopenharmony_ci } 7278c2ecf20Sopenharmony_ci return 0; 7288c2ecf20Sopenharmony_ci} 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci/* 7318c2ecf20Sopenharmony_ci * GHES error status reporting throttle, to report more kinds of 7328c2ecf20Sopenharmony_ci * errors, instead of just most frequently occurred errors. 7338c2ecf20Sopenharmony_ci */ 7348c2ecf20Sopenharmony_cistatic int ghes_estatus_cached(struct acpi_hest_generic_status *estatus) 7358c2ecf20Sopenharmony_ci{ 7368c2ecf20Sopenharmony_ci u32 len; 7378c2ecf20Sopenharmony_ci int i, cached = 0; 7388c2ecf20Sopenharmony_ci unsigned long long now; 7398c2ecf20Sopenharmony_ci struct ghes_estatus_cache *cache; 7408c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *cache_estatus; 7418c2ecf20Sopenharmony_ci 7428c2ecf20Sopenharmony_ci len = cper_estatus_len(estatus); 7438c2ecf20Sopenharmony_ci rcu_read_lock(); 7448c2ecf20Sopenharmony_ci for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) { 7458c2ecf20Sopenharmony_ci cache = rcu_dereference(ghes_estatus_caches[i]); 7468c2ecf20Sopenharmony_ci if (cache == NULL) 7478c2ecf20Sopenharmony_ci continue; 7488c2ecf20Sopenharmony_ci if (len != cache->estatus_len) 7498c2ecf20Sopenharmony_ci continue; 7508c2ecf20Sopenharmony_ci cache_estatus = GHES_ESTATUS_FROM_CACHE(cache); 7518c2ecf20Sopenharmony_ci if (memcmp(estatus, cache_estatus, len)) 7528c2ecf20Sopenharmony_ci continue; 7538c2ecf20Sopenharmony_ci atomic_inc(&cache->count); 7548c2ecf20Sopenharmony_ci now = sched_clock(); 7558c2ecf20Sopenharmony_ci if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC) 7568c2ecf20Sopenharmony_ci cached = 1; 7578c2ecf20Sopenharmony_ci break; 7588c2ecf20Sopenharmony_ci } 7598c2ecf20Sopenharmony_ci rcu_read_unlock(); 7608c2ecf20Sopenharmony_ci return cached; 7618c2ecf20Sopenharmony_ci} 7628c2ecf20Sopenharmony_ci 7638c2ecf20Sopenharmony_cistatic struct ghes_estatus_cache *ghes_estatus_cache_alloc( 7648c2ecf20Sopenharmony_ci struct acpi_hest_generic *generic, 7658c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus) 7668c2ecf20Sopenharmony_ci{ 7678c2ecf20Sopenharmony_ci int alloced; 7688c2ecf20Sopenharmony_ci u32 len, cache_len; 7698c2ecf20Sopenharmony_ci struct ghes_estatus_cache *cache; 7708c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *cache_estatus; 7718c2ecf20Sopenharmony_ci 7728c2ecf20Sopenharmony_ci alloced = atomic_add_return(1, &ghes_estatus_cache_alloced); 7738c2ecf20Sopenharmony_ci if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) { 7748c2ecf20Sopenharmony_ci atomic_dec(&ghes_estatus_cache_alloced); 7758c2ecf20Sopenharmony_ci return NULL; 7768c2ecf20Sopenharmony_ci } 7778c2ecf20Sopenharmony_ci len = cper_estatus_len(estatus); 7788c2ecf20Sopenharmony_ci cache_len = GHES_ESTATUS_CACHE_LEN(len); 7798c2ecf20Sopenharmony_ci cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len); 7808c2ecf20Sopenharmony_ci if (!cache) { 7818c2ecf20Sopenharmony_ci atomic_dec(&ghes_estatus_cache_alloced); 7828c2ecf20Sopenharmony_ci return NULL; 7838c2ecf20Sopenharmony_ci } 7848c2ecf20Sopenharmony_ci cache_estatus = GHES_ESTATUS_FROM_CACHE(cache); 7858c2ecf20Sopenharmony_ci memcpy(cache_estatus, estatus, len); 7868c2ecf20Sopenharmony_ci cache->estatus_len = len; 7878c2ecf20Sopenharmony_ci atomic_set(&cache->count, 0); 7888c2ecf20Sopenharmony_ci cache->generic = generic; 7898c2ecf20Sopenharmony_ci cache->time_in = sched_clock(); 7908c2ecf20Sopenharmony_ci return cache; 7918c2ecf20Sopenharmony_ci} 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_cistatic void ghes_estatus_cache_free(struct ghes_estatus_cache *cache) 7948c2ecf20Sopenharmony_ci{ 7958c2ecf20Sopenharmony_ci u32 len; 7968c2ecf20Sopenharmony_ci 7978c2ecf20Sopenharmony_ci len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache)); 7988c2ecf20Sopenharmony_ci len = GHES_ESTATUS_CACHE_LEN(len); 7998c2ecf20Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len); 8008c2ecf20Sopenharmony_ci atomic_dec(&ghes_estatus_cache_alloced); 8018c2ecf20Sopenharmony_ci} 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_cistatic void ghes_estatus_cache_rcu_free(struct rcu_head *head) 8048c2ecf20Sopenharmony_ci{ 8058c2ecf20Sopenharmony_ci struct ghes_estatus_cache *cache; 8068c2ecf20Sopenharmony_ci 8078c2ecf20Sopenharmony_ci cache = container_of(head, struct ghes_estatus_cache, rcu); 8088c2ecf20Sopenharmony_ci ghes_estatus_cache_free(cache); 8098c2ecf20Sopenharmony_ci} 8108c2ecf20Sopenharmony_ci 8118c2ecf20Sopenharmony_cistatic void ghes_estatus_cache_add( 8128c2ecf20Sopenharmony_ci struct acpi_hest_generic *generic, 8138c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus) 8148c2ecf20Sopenharmony_ci{ 8158c2ecf20Sopenharmony_ci int i, slot = -1, count; 8168c2ecf20Sopenharmony_ci unsigned long long now, duration, period, max_period = 0; 8178c2ecf20Sopenharmony_ci struct ghes_estatus_cache *cache, *slot_cache = NULL, *new_cache; 8188c2ecf20Sopenharmony_ci 8198c2ecf20Sopenharmony_ci new_cache = ghes_estatus_cache_alloc(generic, estatus); 8208c2ecf20Sopenharmony_ci if (new_cache == NULL) 8218c2ecf20Sopenharmony_ci return; 8228c2ecf20Sopenharmony_ci rcu_read_lock(); 8238c2ecf20Sopenharmony_ci now = sched_clock(); 8248c2ecf20Sopenharmony_ci for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) { 8258c2ecf20Sopenharmony_ci cache = rcu_dereference(ghes_estatus_caches[i]); 8268c2ecf20Sopenharmony_ci if (cache == NULL) { 8278c2ecf20Sopenharmony_ci slot = i; 8288c2ecf20Sopenharmony_ci slot_cache = NULL; 8298c2ecf20Sopenharmony_ci break; 8308c2ecf20Sopenharmony_ci } 8318c2ecf20Sopenharmony_ci duration = now - cache->time_in; 8328c2ecf20Sopenharmony_ci if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) { 8338c2ecf20Sopenharmony_ci slot = i; 8348c2ecf20Sopenharmony_ci slot_cache = cache; 8358c2ecf20Sopenharmony_ci break; 8368c2ecf20Sopenharmony_ci } 8378c2ecf20Sopenharmony_ci count = atomic_read(&cache->count); 8388c2ecf20Sopenharmony_ci period = duration; 8398c2ecf20Sopenharmony_ci do_div(period, (count + 1)); 8408c2ecf20Sopenharmony_ci if (period > max_period) { 8418c2ecf20Sopenharmony_ci max_period = period; 8428c2ecf20Sopenharmony_ci slot = i; 8438c2ecf20Sopenharmony_ci slot_cache = cache; 8448c2ecf20Sopenharmony_ci } 8458c2ecf20Sopenharmony_ci } 8468c2ecf20Sopenharmony_ci /* new_cache must be put into array after its contents are written */ 8478c2ecf20Sopenharmony_ci smp_wmb(); 8488c2ecf20Sopenharmony_ci if (slot != -1 && cmpxchg(ghes_estatus_caches + slot, 8498c2ecf20Sopenharmony_ci slot_cache, new_cache) == slot_cache) { 8508c2ecf20Sopenharmony_ci if (slot_cache) 8518c2ecf20Sopenharmony_ci call_rcu(&slot_cache->rcu, ghes_estatus_cache_rcu_free); 8528c2ecf20Sopenharmony_ci } else 8538c2ecf20Sopenharmony_ci ghes_estatus_cache_free(new_cache); 8548c2ecf20Sopenharmony_ci rcu_read_unlock(); 8558c2ecf20Sopenharmony_ci} 8568c2ecf20Sopenharmony_ci 8578c2ecf20Sopenharmony_cistatic void __ghes_panic(struct ghes *ghes, 8588c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus, 8598c2ecf20Sopenharmony_ci u64 buf_paddr, enum fixed_addresses fixmap_idx) 8608c2ecf20Sopenharmony_ci{ 8618c2ecf20Sopenharmony_ci __ghes_print_estatus(KERN_EMERG, ghes->generic, estatus); 8628c2ecf20Sopenharmony_ci 8638c2ecf20Sopenharmony_ci ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx); 8648c2ecf20Sopenharmony_ci 8658c2ecf20Sopenharmony_ci /* reboot to log the error! */ 8668c2ecf20Sopenharmony_ci if (!panic_timeout) 8678c2ecf20Sopenharmony_ci panic_timeout = ghes_panic_timeout; 8688c2ecf20Sopenharmony_ci panic("Fatal hardware error!"); 8698c2ecf20Sopenharmony_ci} 8708c2ecf20Sopenharmony_ci 8718c2ecf20Sopenharmony_cistatic int ghes_proc(struct ghes *ghes) 8728c2ecf20Sopenharmony_ci{ 8738c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus = ghes->estatus; 8748c2ecf20Sopenharmony_ci u64 buf_paddr; 8758c2ecf20Sopenharmony_ci int rc; 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_ci rc = ghes_read_estatus(ghes, estatus, &buf_paddr, FIX_APEI_GHES_IRQ); 8788c2ecf20Sopenharmony_ci if (rc) 8798c2ecf20Sopenharmony_ci goto out; 8808c2ecf20Sopenharmony_ci 8818c2ecf20Sopenharmony_ci if (ghes_severity(estatus->error_severity) >= GHES_SEV_PANIC) 8828c2ecf20Sopenharmony_ci __ghes_panic(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ); 8838c2ecf20Sopenharmony_ci 8848c2ecf20Sopenharmony_ci if (!ghes_estatus_cached(estatus)) { 8858c2ecf20Sopenharmony_ci if (ghes_print_estatus(NULL, ghes->generic, estatus)) 8868c2ecf20Sopenharmony_ci ghes_estatus_cache_add(ghes->generic, estatus); 8878c2ecf20Sopenharmony_ci } 8888c2ecf20Sopenharmony_ci ghes_do_proc(ghes, estatus); 8898c2ecf20Sopenharmony_ci 8908c2ecf20Sopenharmony_ciout: 8918c2ecf20Sopenharmony_ci ghes_clear_estatus(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ); 8928c2ecf20Sopenharmony_ci 8938c2ecf20Sopenharmony_ci return rc; 8948c2ecf20Sopenharmony_ci} 8958c2ecf20Sopenharmony_ci 8968c2ecf20Sopenharmony_cistatic void ghes_add_timer(struct ghes *ghes) 8978c2ecf20Sopenharmony_ci{ 8988c2ecf20Sopenharmony_ci struct acpi_hest_generic *g = ghes->generic; 8998c2ecf20Sopenharmony_ci unsigned long expire; 9008c2ecf20Sopenharmony_ci 9018c2ecf20Sopenharmony_ci if (!g->notify.poll_interval) { 9028c2ecf20Sopenharmony_ci pr_warn(FW_WARN GHES_PFX "Poll interval is 0 for generic hardware error source: %d, disabled.\n", 9038c2ecf20Sopenharmony_ci g->header.source_id); 9048c2ecf20Sopenharmony_ci return; 9058c2ecf20Sopenharmony_ci } 9068c2ecf20Sopenharmony_ci expire = jiffies + msecs_to_jiffies(g->notify.poll_interval); 9078c2ecf20Sopenharmony_ci ghes->timer.expires = round_jiffies_relative(expire); 9088c2ecf20Sopenharmony_ci add_timer(&ghes->timer); 9098c2ecf20Sopenharmony_ci} 9108c2ecf20Sopenharmony_ci 9118c2ecf20Sopenharmony_cistatic void ghes_poll_func(struct timer_list *t) 9128c2ecf20Sopenharmony_ci{ 9138c2ecf20Sopenharmony_ci struct ghes *ghes = from_timer(ghes, t, timer); 9148c2ecf20Sopenharmony_ci unsigned long flags; 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 9178c2ecf20Sopenharmony_ci ghes_proc(ghes); 9188c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 9198c2ecf20Sopenharmony_ci if (!(ghes->flags & GHES_EXITING)) 9208c2ecf20Sopenharmony_ci ghes_add_timer(ghes); 9218c2ecf20Sopenharmony_ci} 9228c2ecf20Sopenharmony_ci 9238c2ecf20Sopenharmony_cistatic irqreturn_t ghes_irq_func(int irq, void *data) 9248c2ecf20Sopenharmony_ci{ 9258c2ecf20Sopenharmony_ci struct ghes *ghes = data; 9268c2ecf20Sopenharmony_ci unsigned long flags; 9278c2ecf20Sopenharmony_ci int rc; 9288c2ecf20Sopenharmony_ci 9298c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 9308c2ecf20Sopenharmony_ci rc = ghes_proc(ghes); 9318c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 9328c2ecf20Sopenharmony_ci if (rc) 9338c2ecf20Sopenharmony_ci return IRQ_NONE; 9348c2ecf20Sopenharmony_ci 9358c2ecf20Sopenharmony_ci return IRQ_HANDLED; 9368c2ecf20Sopenharmony_ci} 9378c2ecf20Sopenharmony_ci 9388c2ecf20Sopenharmony_cistatic int ghes_notify_hed(struct notifier_block *this, unsigned long event, 9398c2ecf20Sopenharmony_ci void *data) 9408c2ecf20Sopenharmony_ci{ 9418c2ecf20Sopenharmony_ci struct ghes *ghes; 9428c2ecf20Sopenharmony_ci unsigned long flags; 9438c2ecf20Sopenharmony_ci int ret = NOTIFY_DONE; 9448c2ecf20Sopenharmony_ci 9458c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 9468c2ecf20Sopenharmony_ci rcu_read_lock(); 9478c2ecf20Sopenharmony_ci list_for_each_entry_rcu(ghes, &ghes_hed, list) { 9488c2ecf20Sopenharmony_ci if (!ghes_proc(ghes)) 9498c2ecf20Sopenharmony_ci ret = NOTIFY_OK; 9508c2ecf20Sopenharmony_ci } 9518c2ecf20Sopenharmony_ci rcu_read_unlock(); 9528c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 9538c2ecf20Sopenharmony_ci 9548c2ecf20Sopenharmony_ci return ret; 9558c2ecf20Sopenharmony_ci} 9568c2ecf20Sopenharmony_ci 9578c2ecf20Sopenharmony_cistatic struct notifier_block ghes_notifier_hed = { 9588c2ecf20Sopenharmony_ci .notifier_call = ghes_notify_hed, 9598c2ecf20Sopenharmony_ci}; 9608c2ecf20Sopenharmony_ci 9618c2ecf20Sopenharmony_ci/* 9628c2ecf20Sopenharmony_ci * Handlers for CPER records may not be NMI safe. For example, 9638c2ecf20Sopenharmony_ci * memory_failure_queue() takes spinlocks and calls schedule_work_on(). 9648c2ecf20Sopenharmony_ci * In any NMI-like handler, memory from ghes_estatus_pool is used to save 9658c2ecf20Sopenharmony_ci * estatus, and added to the ghes_estatus_llist. irq_work_queue() causes 9668c2ecf20Sopenharmony_ci * ghes_proc_in_irq() to run in IRQ context where each estatus in 9678c2ecf20Sopenharmony_ci * ghes_estatus_llist is processed. 9688c2ecf20Sopenharmony_ci * 9698c2ecf20Sopenharmony_ci * Memory from the ghes_estatus_pool is also used with the ghes_estatus_cache 9708c2ecf20Sopenharmony_ci * to suppress frequent messages. 9718c2ecf20Sopenharmony_ci */ 9728c2ecf20Sopenharmony_cistatic struct llist_head ghes_estatus_llist; 9738c2ecf20Sopenharmony_cistatic struct irq_work ghes_proc_irq_work; 9748c2ecf20Sopenharmony_ci 9758c2ecf20Sopenharmony_cistatic void ghes_proc_in_irq(struct irq_work *irq_work) 9768c2ecf20Sopenharmony_ci{ 9778c2ecf20Sopenharmony_ci struct llist_node *llnode, *next; 9788c2ecf20Sopenharmony_ci struct ghes_estatus_node *estatus_node; 9798c2ecf20Sopenharmony_ci struct acpi_hest_generic *generic; 9808c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus; 9818c2ecf20Sopenharmony_ci bool task_work_pending; 9828c2ecf20Sopenharmony_ci u32 len, node_len; 9838c2ecf20Sopenharmony_ci int ret; 9848c2ecf20Sopenharmony_ci 9858c2ecf20Sopenharmony_ci llnode = llist_del_all(&ghes_estatus_llist); 9868c2ecf20Sopenharmony_ci /* 9878c2ecf20Sopenharmony_ci * Because the time order of estatus in list is reversed, 9888c2ecf20Sopenharmony_ci * revert it back to proper order. 9898c2ecf20Sopenharmony_ci */ 9908c2ecf20Sopenharmony_ci llnode = llist_reverse_order(llnode); 9918c2ecf20Sopenharmony_ci while (llnode) { 9928c2ecf20Sopenharmony_ci next = llnode->next; 9938c2ecf20Sopenharmony_ci estatus_node = llist_entry(llnode, struct ghes_estatus_node, 9948c2ecf20Sopenharmony_ci llnode); 9958c2ecf20Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 9968c2ecf20Sopenharmony_ci len = cper_estatus_len(estatus); 9978c2ecf20Sopenharmony_ci node_len = GHES_ESTATUS_NODE_LEN(len); 9988c2ecf20Sopenharmony_ci task_work_pending = ghes_do_proc(estatus_node->ghes, estatus); 9998c2ecf20Sopenharmony_ci if (!ghes_estatus_cached(estatus)) { 10008c2ecf20Sopenharmony_ci generic = estatus_node->generic; 10018c2ecf20Sopenharmony_ci if (ghes_print_estatus(NULL, generic, estatus)) 10028c2ecf20Sopenharmony_ci ghes_estatus_cache_add(generic, estatus); 10038c2ecf20Sopenharmony_ci } 10048c2ecf20Sopenharmony_ci 10058c2ecf20Sopenharmony_ci if (task_work_pending && current->mm) { 10068c2ecf20Sopenharmony_ci estatus_node->task_work.func = ghes_kick_task_work; 10078c2ecf20Sopenharmony_ci estatus_node->task_work_cpu = smp_processor_id(); 10088c2ecf20Sopenharmony_ci ret = task_work_add(current, &estatus_node->task_work, 10098c2ecf20Sopenharmony_ci TWA_RESUME); 10108c2ecf20Sopenharmony_ci if (ret) 10118c2ecf20Sopenharmony_ci estatus_node->task_work.func = NULL; 10128c2ecf20Sopenharmony_ci } 10138c2ecf20Sopenharmony_ci 10148c2ecf20Sopenharmony_ci if (!estatus_node->task_work.func) 10158c2ecf20Sopenharmony_ci gen_pool_free(ghes_estatus_pool, 10168c2ecf20Sopenharmony_ci (unsigned long)estatus_node, node_len); 10178c2ecf20Sopenharmony_ci 10188c2ecf20Sopenharmony_ci llnode = next; 10198c2ecf20Sopenharmony_ci } 10208c2ecf20Sopenharmony_ci} 10218c2ecf20Sopenharmony_ci 10228c2ecf20Sopenharmony_cistatic void ghes_print_queued_estatus(void) 10238c2ecf20Sopenharmony_ci{ 10248c2ecf20Sopenharmony_ci struct llist_node *llnode; 10258c2ecf20Sopenharmony_ci struct ghes_estatus_node *estatus_node; 10268c2ecf20Sopenharmony_ci struct acpi_hest_generic *generic; 10278c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus; 10288c2ecf20Sopenharmony_ci 10298c2ecf20Sopenharmony_ci llnode = llist_del_all(&ghes_estatus_llist); 10308c2ecf20Sopenharmony_ci /* 10318c2ecf20Sopenharmony_ci * Because the time order of estatus in list is reversed, 10328c2ecf20Sopenharmony_ci * revert it back to proper order. 10338c2ecf20Sopenharmony_ci */ 10348c2ecf20Sopenharmony_ci llnode = llist_reverse_order(llnode); 10358c2ecf20Sopenharmony_ci while (llnode) { 10368c2ecf20Sopenharmony_ci estatus_node = llist_entry(llnode, struct ghes_estatus_node, 10378c2ecf20Sopenharmony_ci llnode); 10388c2ecf20Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 10398c2ecf20Sopenharmony_ci generic = estatus_node->generic; 10408c2ecf20Sopenharmony_ci ghes_print_estatus(NULL, generic, estatus); 10418c2ecf20Sopenharmony_ci llnode = llnode->next; 10428c2ecf20Sopenharmony_ci } 10438c2ecf20Sopenharmony_ci} 10448c2ecf20Sopenharmony_ci 10458c2ecf20Sopenharmony_cistatic int ghes_in_nmi_queue_one_entry(struct ghes *ghes, 10468c2ecf20Sopenharmony_ci enum fixed_addresses fixmap_idx) 10478c2ecf20Sopenharmony_ci{ 10488c2ecf20Sopenharmony_ci struct acpi_hest_generic_status *estatus, tmp_header; 10498c2ecf20Sopenharmony_ci struct ghes_estatus_node *estatus_node; 10508c2ecf20Sopenharmony_ci u32 len, node_len; 10518c2ecf20Sopenharmony_ci u64 buf_paddr; 10528c2ecf20Sopenharmony_ci int sev, rc; 10538c2ecf20Sopenharmony_ci 10548c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG)) 10558c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 10568c2ecf20Sopenharmony_ci 10578c2ecf20Sopenharmony_ci rc = __ghes_peek_estatus(ghes, &tmp_header, &buf_paddr, fixmap_idx); 10588c2ecf20Sopenharmony_ci if (rc) { 10598c2ecf20Sopenharmony_ci ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); 10608c2ecf20Sopenharmony_ci return rc; 10618c2ecf20Sopenharmony_ci } 10628c2ecf20Sopenharmony_ci 10638c2ecf20Sopenharmony_ci rc = __ghes_check_estatus(ghes, &tmp_header); 10648c2ecf20Sopenharmony_ci if (rc) { 10658c2ecf20Sopenharmony_ci ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); 10668c2ecf20Sopenharmony_ci return rc; 10678c2ecf20Sopenharmony_ci } 10688c2ecf20Sopenharmony_ci 10698c2ecf20Sopenharmony_ci len = cper_estatus_len(&tmp_header); 10708c2ecf20Sopenharmony_ci node_len = GHES_ESTATUS_NODE_LEN(len); 10718c2ecf20Sopenharmony_ci estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len); 10728c2ecf20Sopenharmony_ci if (!estatus_node) 10738c2ecf20Sopenharmony_ci return -ENOMEM; 10748c2ecf20Sopenharmony_ci 10758c2ecf20Sopenharmony_ci estatus_node->ghes = ghes; 10768c2ecf20Sopenharmony_ci estatus_node->generic = ghes->generic; 10778c2ecf20Sopenharmony_ci estatus_node->task_work.func = NULL; 10788c2ecf20Sopenharmony_ci estatus = GHES_ESTATUS_FROM_NODE(estatus_node); 10798c2ecf20Sopenharmony_ci 10808c2ecf20Sopenharmony_ci if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) { 10818c2ecf20Sopenharmony_ci ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx); 10828c2ecf20Sopenharmony_ci rc = -ENOENT; 10838c2ecf20Sopenharmony_ci goto no_work; 10848c2ecf20Sopenharmony_ci } 10858c2ecf20Sopenharmony_ci 10868c2ecf20Sopenharmony_ci sev = ghes_severity(estatus->error_severity); 10878c2ecf20Sopenharmony_ci if (sev >= GHES_SEV_PANIC) { 10888c2ecf20Sopenharmony_ci ghes_print_queued_estatus(); 10898c2ecf20Sopenharmony_ci __ghes_panic(ghes, estatus, buf_paddr, fixmap_idx); 10908c2ecf20Sopenharmony_ci } 10918c2ecf20Sopenharmony_ci 10928c2ecf20Sopenharmony_ci ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); 10938c2ecf20Sopenharmony_ci 10948c2ecf20Sopenharmony_ci /* This error has been reported before, don't process it again. */ 10958c2ecf20Sopenharmony_ci if (ghes_estatus_cached(estatus)) 10968c2ecf20Sopenharmony_ci goto no_work; 10978c2ecf20Sopenharmony_ci 10988c2ecf20Sopenharmony_ci llist_add(&estatus_node->llnode, &ghes_estatus_llist); 10998c2ecf20Sopenharmony_ci 11008c2ecf20Sopenharmony_ci return rc; 11018c2ecf20Sopenharmony_ci 11028c2ecf20Sopenharmony_cino_work: 11038c2ecf20Sopenharmony_ci gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, 11048c2ecf20Sopenharmony_ci node_len); 11058c2ecf20Sopenharmony_ci 11068c2ecf20Sopenharmony_ci return rc; 11078c2ecf20Sopenharmony_ci} 11088c2ecf20Sopenharmony_ci 11098c2ecf20Sopenharmony_cistatic int ghes_in_nmi_spool_from_list(struct list_head *rcu_list, 11108c2ecf20Sopenharmony_ci enum fixed_addresses fixmap_idx) 11118c2ecf20Sopenharmony_ci{ 11128c2ecf20Sopenharmony_ci int ret = -ENOENT; 11138c2ecf20Sopenharmony_ci struct ghes *ghes; 11148c2ecf20Sopenharmony_ci 11158c2ecf20Sopenharmony_ci rcu_read_lock(); 11168c2ecf20Sopenharmony_ci list_for_each_entry_rcu(ghes, rcu_list, list) { 11178c2ecf20Sopenharmony_ci if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx)) 11188c2ecf20Sopenharmony_ci ret = 0; 11198c2ecf20Sopenharmony_ci } 11208c2ecf20Sopenharmony_ci rcu_read_unlock(); 11218c2ecf20Sopenharmony_ci 11228c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG) && !ret) 11238c2ecf20Sopenharmony_ci irq_work_queue(&ghes_proc_irq_work); 11248c2ecf20Sopenharmony_ci 11258c2ecf20Sopenharmony_ci return ret; 11268c2ecf20Sopenharmony_ci} 11278c2ecf20Sopenharmony_ci 11288c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI_APEI_SEA 11298c2ecf20Sopenharmony_cistatic LIST_HEAD(ghes_sea); 11308c2ecf20Sopenharmony_ci 11318c2ecf20Sopenharmony_ci/* 11328c2ecf20Sopenharmony_ci * Return 0 only if one of the SEA error sources successfully reported an error 11338c2ecf20Sopenharmony_ci * record sent from the firmware. 11348c2ecf20Sopenharmony_ci */ 11358c2ecf20Sopenharmony_ciint ghes_notify_sea(void) 11368c2ecf20Sopenharmony_ci{ 11378c2ecf20Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sea); 11388c2ecf20Sopenharmony_ci int rv; 11398c2ecf20Sopenharmony_ci 11408c2ecf20Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_sea); 11418c2ecf20Sopenharmony_ci rv = ghes_in_nmi_spool_from_list(&ghes_sea, FIX_APEI_GHES_SEA); 11428c2ecf20Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_sea); 11438c2ecf20Sopenharmony_ci 11448c2ecf20Sopenharmony_ci return rv; 11458c2ecf20Sopenharmony_ci} 11468c2ecf20Sopenharmony_ci 11478c2ecf20Sopenharmony_cistatic void ghes_sea_add(struct ghes *ghes) 11488c2ecf20Sopenharmony_ci{ 11498c2ecf20Sopenharmony_ci mutex_lock(&ghes_list_mutex); 11508c2ecf20Sopenharmony_ci list_add_rcu(&ghes->list, &ghes_sea); 11518c2ecf20Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 11528c2ecf20Sopenharmony_ci} 11538c2ecf20Sopenharmony_ci 11548c2ecf20Sopenharmony_cistatic void ghes_sea_remove(struct ghes *ghes) 11558c2ecf20Sopenharmony_ci{ 11568c2ecf20Sopenharmony_ci mutex_lock(&ghes_list_mutex); 11578c2ecf20Sopenharmony_ci list_del_rcu(&ghes->list); 11588c2ecf20Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 11598c2ecf20Sopenharmony_ci synchronize_rcu(); 11608c2ecf20Sopenharmony_ci} 11618c2ecf20Sopenharmony_ci#else /* CONFIG_ACPI_APEI_SEA */ 11628c2ecf20Sopenharmony_cistatic inline void ghes_sea_add(struct ghes *ghes) { } 11638c2ecf20Sopenharmony_cistatic inline void ghes_sea_remove(struct ghes *ghes) { } 11648c2ecf20Sopenharmony_ci#endif /* CONFIG_ACPI_APEI_SEA */ 11658c2ecf20Sopenharmony_ci 11668c2ecf20Sopenharmony_ci#ifdef CONFIG_HAVE_ACPI_APEI_NMI 11678c2ecf20Sopenharmony_ci/* 11688c2ecf20Sopenharmony_ci * NMI may be triggered on any CPU, so ghes_in_nmi is used for 11698c2ecf20Sopenharmony_ci * having only one concurrent reader. 11708c2ecf20Sopenharmony_ci */ 11718c2ecf20Sopenharmony_cistatic atomic_t ghes_in_nmi = ATOMIC_INIT(0); 11728c2ecf20Sopenharmony_ci 11738c2ecf20Sopenharmony_cistatic LIST_HEAD(ghes_nmi); 11748c2ecf20Sopenharmony_ci 11758c2ecf20Sopenharmony_cistatic int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs) 11768c2ecf20Sopenharmony_ci{ 11778c2ecf20Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_nmi); 11788c2ecf20Sopenharmony_ci int ret = NMI_DONE; 11798c2ecf20Sopenharmony_ci 11808c2ecf20Sopenharmony_ci if (!atomic_add_unless(&ghes_in_nmi, 1, 1)) 11818c2ecf20Sopenharmony_ci return ret; 11828c2ecf20Sopenharmony_ci 11838c2ecf20Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_nmi); 11848c2ecf20Sopenharmony_ci if (!ghes_in_nmi_spool_from_list(&ghes_nmi, FIX_APEI_GHES_NMI)) 11858c2ecf20Sopenharmony_ci ret = NMI_HANDLED; 11868c2ecf20Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_nmi); 11878c2ecf20Sopenharmony_ci 11888c2ecf20Sopenharmony_ci atomic_dec(&ghes_in_nmi); 11898c2ecf20Sopenharmony_ci return ret; 11908c2ecf20Sopenharmony_ci} 11918c2ecf20Sopenharmony_ci 11928c2ecf20Sopenharmony_cistatic void ghes_nmi_add(struct ghes *ghes) 11938c2ecf20Sopenharmony_ci{ 11948c2ecf20Sopenharmony_ci mutex_lock(&ghes_list_mutex); 11958c2ecf20Sopenharmony_ci if (list_empty(&ghes_nmi)) 11968c2ecf20Sopenharmony_ci register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes"); 11978c2ecf20Sopenharmony_ci list_add_rcu(&ghes->list, &ghes_nmi); 11988c2ecf20Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 11998c2ecf20Sopenharmony_ci} 12008c2ecf20Sopenharmony_ci 12018c2ecf20Sopenharmony_cistatic void ghes_nmi_remove(struct ghes *ghes) 12028c2ecf20Sopenharmony_ci{ 12038c2ecf20Sopenharmony_ci mutex_lock(&ghes_list_mutex); 12048c2ecf20Sopenharmony_ci list_del_rcu(&ghes->list); 12058c2ecf20Sopenharmony_ci if (list_empty(&ghes_nmi)) 12068c2ecf20Sopenharmony_ci unregister_nmi_handler(NMI_LOCAL, "ghes"); 12078c2ecf20Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 12088c2ecf20Sopenharmony_ci /* 12098c2ecf20Sopenharmony_ci * To synchronize with NMI handler, ghes can only be 12108c2ecf20Sopenharmony_ci * freed after NMI handler finishes. 12118c2ecf20Sopenharmony_ci */ 12128c2ecf20Sopenharmony_ci synchronize_rcu(); 12138c2ecf20Sopenharmony_ci} 12148c2ecf20Sopenharmony_ci#else /* CONFIG_HAVE_ACPI_APEI_NMI */ 12158c2ecf20Sopenharmony_cistatic inline void ghes_nmi_add(struct ghes *ghes) { } 12168c2ecf20Sopenharmony_cistatic inline void ghes_nmi_remove(struct ghes *ghes) { } 12178c2ecf20Sopenharmony_ci#endif /* CONFIG_HAVE_ACPI_APEI_NMI */ 12188c2ecf20Sopenharmony_ci 12198c2ecf20Sopenharmony_cistatic void ghes_nmi_init_cxt(void) 12208c2ecf20Sopenharmony_ci{ 12218c2ecf20Sopenharmony_ci init_irq_work(&ghes_proc_irq_work, ghes_proc_in_irq); 12228c2ecf20Sopenharmony_ci} 12238c2ecf20Sopenharmony_ci 12248c2ecf20Sopenharmony_cistatic int __ghes_sdei_callback(struct ghes *ghes, 12258c2ecf20Sopenharmony_ci enum fixed_addresses fixmap_idx) 12268c2ecf20Sopenharmony_ci{ 12278c2ecf20Sopenharmony_ci if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx)) { 12288c2ecf20Sopenharmony_ci irq_work_queue(&ghes_proc_irq_work); 12298c2ecf20Sopenharmony_ci 12308c2ecf20Sopenharmony_ci return 0; 12318c2ecf20Sopenharmony_ci } 12328c2ecf20Sopenharmony_ci 12338c2ecf20Sopenharmony_ci return -ENOENT; 12348c2ecf20Sopenharmony_ci} 12358c2ecf20Sopenharmony_ci 12368c2ecf20Sopenharmony_cistatic int ghes_sdei_normal_callback(u32 event_num, struct pt_regs *regs, 12378c2ecf20Sopenharmony_ci void *arg) 12388c2ecf20Sopenharmony_ci{ 12398c2ecf20Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sdei_normal); 12408c2ecf20Sopenharmony_ci struct ghes *ghes = arg; 12418c2ecf20Sopenharmony_ci int err; 12428c2ecf20Sopenharmony_ci 12438c2ecf20Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_sdei_normal); 12448c2ecf20Sopenharmony_ci err = __ghes_sdei_callback(ghes, FIX_APEI_GHES_SDEI_NORMAL); 12458c2ecf20Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_sdei_normal); 12468c2ecf20Sopenharmony_ci 12478c2ecf20Sopenharmony_ci return err; 12488c2ecf20Sopenharmony_ci} 12498c2ecf20Sopenharmony_ci 12508c2ecf20Sopenharmony_cistatic int ghes_sdei_critical_callback(u32 event_num, struct pt_regs *regs, 12518c2ecf20Sopenharmony_ci void *arg) 12528c2ecf20Sopenharmony_ci{ 12538c2ecf20Sopenharmony_ci static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sdei_critical); 12548c2ecf20Sopenharmony_ci struct ghes *ghes = arg; 12558c2ecf20Sopenharmony_ci int err; 12568c2ecf20Sopenharmony_ci 12578c2ecf20Sopenharmony_ci raw_spin_lock(&ghes_notify_lock_sdei_critical); 12588c2ecf20Sopenharmony_ci err = __ghes_sdei_callback(ghes, FIX_APEI_GHES_SDEI_CRITICAL); 12598c2ecf20Sopenharmony_ci raw_spin_unlock(&ghes_notify_lock_sdei_critical); 12608c2ecf20Sopenharmony_ci 12618c2ecf20Sopenharmony_ci return err; 12628c2ecf20Sopenharmony_ci} 12638c2ecf20Sopenharmony_ci 12648c2ecf20Sopenharmony_cistatic int apei_sdei_register_ghes(struct ghes *ghes) 12658c2ecf20Sopenharmony_ci{ 12668c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) 12678c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 12688c2ecf20Sopenharmony_ci 12698c2ecf20Sopenharmony_ci return sdei_register_ghes(ghes, ghes_sdei_normal_callback, 12708c2ecf20Sopenharmony_ci ghes_sdei_critical_callback); 12718c2ecf20Sopenharmony_ci} 12728c2ecf20Sopenharmony_ci 12738c2ecf20Sopenharmony_cistatic int apei_sdei_unregister_ghes(struct ghes *ghes) 12748c2ecf20Sopenharmony_ci{ 12758c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) 12768c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 12778c2ecf20Sopenharmony_ci 12788c2ecf20Sopenharmony_ci return sdei_unregister_ghes(ghes); 12798c2ecf20Sopenharmony_ci} 12808c2ecf20Sopenharmony_ci 12818c2ecf20Sopenharmony_cistatic int ghes_probe(struct platform_device *ghes_dev) 12828c2ecf20Sopenharmony_ci{ 12838c2ecf20Sopenharmony_ci struct acpi_hest_generic *generic; 12848c2ecf20Sopenharmony_ci struct ghes *ghes = NULL; 12858c2ecf20Sopenharmony_ci unsigned long flags; 12868c2ecf20Sopenharmony_ci 12878c2ecf20Sopenharmony_ci int rc = -EINVAL; 12888c2ecf20Sopenharmony_ci 12898c2ecf20Sopenharmony_ci generic = *(struct acpi_hest_generic **)ghes_dev->dev.platform_data; 12908c2ecf20Sopenharmony_ci if (!generic->enabled) 12918c2ecf20Sopenharmony_ci return -ENODEV; 12928c2ecf20Sopenharmony_ci 12938c2ecf20Sopenharmony_ci switch (generic->notify.type) { 12948c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_POLLED: 12958c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_EXTERNAL: 12968c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SCI: 12978c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_GSIV: 12988c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_GPIO: 12998c2ecf20Sopenharmony_ci break; 13008c2ecf20Sopenharmony_ci 13018c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SEA: 13028c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ACPI_APEI_SEA)) { 13038c2ecf20Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via SEA is not supported\n", 13048c2ecf20Sopenharmony_ci generic->header.source_id); 13058c2ecf20Sopenharmony_ci rc = -ENOTSUPP; 13068c2ecf20Sopenharmony_ci goto err; 13078c2ecf20Sopenharmony_ci } 13088c2ecf20Sopenharmony_ci break; 13098c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_NMI: 13108c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_HAVE_ACPI_APEI_NMI)) { 13118c2ecf20Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via NMI interrupt is not supported!\n", 13128c2ecf20Sopenharmony_ci generic->header.source_id); 13138c2ecf20Sopenharmony_ci goto err; 13148c2ecf20Sopenharmony_ci } 13158c2ecf20Sopenharmony_ci break; 13168c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 13178c2ecf20Sopenharmony_ci if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) { 13188c2ecf20Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via SDE Interface is not supported!\n", 13198c2ecf20Sopenharmony_ci generic->header.source_id); 13208c2ecf20Sopenharmony_ci goto err; 13218c2ecf20Sopenharmony_ci } 13228c2ecf20Sopenharmony_ci break; 13238c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_LOCAL: 13248c2ecf20Sopenharmony_ci pr_warn(GHES_PFX "Generic hardware error source: %d notified via local interrupt is not supported!\n", 13258c2ecf20Sopenharmony_ci generic->header.source_id); 13268c2ecf20Sopenharmony_ci goto err; 13278c2ecf20Sopenharmony_ci default: 13288c2ecf20Sopenharmony_ci pr_warn(FW_WARN GHES_PFX "Unknown notification type: %u for generic hardware error source: %d\n", 13298c2ecf20Sopenharmony_ci generic->notify.type, generic->header.source_id); 13308c2ecf20Sopenharmony_ci goto err; 13318c2ecf20Sopenharmony_ci } 13328c2ecf20Sopenharmony_ci 13338c2ecf20Sopenharmony_ci rc = -EIO; 13348c2ecf20Sopenharmony_ci if (generic->error_block_length < 13358c2ecf20Sopenharmony_ci sizeof(struct acpi_hest_generic_status)) { 13368c2ecf20Sopenharmony_ci pr_warn(FW_BUG GHES_PFX "Invalid error block length: %u for generic hardware error source: %d\n", 13378c2ecf20Sopenharmony_ci generic->error_block_length, generic->header.source_id); 13388c2ecf20Sopenharmony_ci goto err; 13398c2ecf20Sopenharmony_ci } 13408c2ecf20Sopenharmony_ci ghes = ghes_new(generic); 13418c2ecf20Sopenharmony_ci if (IS_ERR(ghes)) { 13428c2ecf20Sopenharmony_ci rc = PTR_ERR(ghes); 13438c2ecf20Sopenharmony_ci ghes = NULL; 13448c2ecf20Sopenharmony_ci goto err; 13458c2ecf20Sopenharmony_ci } 13468c2ecf20Sopenharmony_ci 13478c2ecf20Sopenharmony_ci switch (generic->notify.type) { 13488c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_POLLED: 13498c2ecf20Sopenharmony_ci timer_setup(&ghes->timer, ghes_poll_func, 0); 13508c2ecf20Sopenharmony_ci ghes_add_timer(ghes); 13518c2ecf20Sopenharmony_ci break; 13528c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_EXTERNAL: 13538c2ecf20Sopenharmony_ci /* External interrupt vector is GSI */ 13548c2ecf20Sopenharmony_ci rc = acpi_gsi_to_irq(generic->notify.vector, &ghes->irq); 13558c2ecf20Sopenharmony_ci if (rc) { 13568c2ecf20Sopenharmony_ci pr_err(GHES_PFX "Failed to map GSI to IRQ for generic hardware error source: %d\n", 13578c2ecf20Sopenharmony_ci generic->header.source_id); 13588c2ecf20Sopenharmony_ci goto err; 13598c2ecf20Sopenharmony_ci } 13608c2ecf20Sopenharmony_ci rc = request_irq(ghes->irq, ghes_irq_func, IRQF_SHARED, 13618c2ecf20Sopenharmony_ci "GHES IRQ", ghes); 13628c2ecf20Sopenharmony_ci if (rc) { 13638c2ecf20Sopenharmony_ci pr_err(GHES_PFX "Failed to register IRQ for generic hardware error source: %d\n", 13648c2ecf20Sopenharmony_ci generic->header.source_id); 13658c2ecf20Sopenharmony_ci goto err; 13668c2ecf20Sopenharmony_ci } 13678c2ecf20Sopenharmony_ci break; 13688c2ecf20Sopenharmony_ci 13698c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SCI: 13708c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_GSIV: 13718c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_GPIO: 13728c2ecf20Sopenharmony_ci mutex_lock(&ghes_list_mutex); 13738c2ecf20Sopenharmony_ci if (list_empty(&ghes_hed)) 13748c2ecf20Sopenharmony_ci register_acpi_hed_notifier(&ghes_notifier_hed); 13758c2ecf20Sopenharmony_ci list_add_rcu(&ghes->list, &ghes_hed); 13768c2ecf20Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 13778c2ecf20Sopenharmony_ci break; 13788c2ecf20Sopenharmony_ci 13798c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SEA: 13808c2ecf20Sopenharmony_ci ghes_sea_add(ghes); 13818c2ecf20Sopenharmony_ci break; 13828c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_NMI: 13838c2ecf20Sopenharmony_ci ghes_nmi_add(ghes); 13848c2ecf20Sopenharmony_ci break; 13858c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 13868c2ecf20Sopenharmony_ci rc = apei_sdei_register_ghes(ghes); 13878c2ecf20Sopenharmony_ci if (rc) 13888c2ecf20Sopenharmony_ci goto err; 13898c2ecf20Sopenharmony_ci break; 13908c2ecf20Sopenharmony_ci default: 13918c2ecf20Sopenharmony_ci BUG(); 13928c2ecf20Sopenharmony_ci } 13938c2ecf20Sopenharmony_ci 13948c2ecf20Sopenharmony_ci platform_set_drvdata(ghes_dev, ghes); 13958c2ecf20Sopenharmony_ci 13968c2ecf20Sopenharmony_ci ghes_edac_register(ghes, &ghes_dev->dev); 13978c2ecf20Sopenharmony_ci 13988c2ecf20Sopenharmony_ci /* Handle any pending errors right away */ 13998c2ecf20Sopenharmony_ci spin_lock_irqsave(&ghes_notify_lock_irq, flags); 14008c2ecf20Sopenharmony_ci ghes_proc(ghes); 14018c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); 14028c2ecf20Sopenharmony_ci 14038c2ecf20Sopenharmony_ci return 0; 14048c2ecf20Sopenharmony_ci 14058c2ecf20Sopenharmony_cierr: 14068c2ecf20Sopenharmony_ci if (ghes) { 14078c2ecf20Sopenharmony_ci ghes_fini(ghes); 14088c2ecf20Sopenharmony_ci kfree(ghes); 14098c2ecf20Sopenharmony_ci } 14108c2ecf20Sopenharmony_ci return rc; 14118c2ecf20Sopenharmony_ci} 14128c2ecf20Sopenharmony_ci 14138c2ecf20Sopenharmony_cistatic int ghes_remove(struct platform_device *ghes_dev) 14148c2ecf20Sopenharmony_ci{ 14158c2ecf20Sopenharmony_ci int rc; 14168c2ecf20Sopenharmony_ci struct ghes *ghes; 14178c2ecf20Sopenharmony_ci struct acpi_hest_generic *generic; 14188c2ecf20Sopenharmony_ci 14198c2ecf20Sopenharmony_ci ghes = platform_get_drvdata(ghes_dev); 14208c2ecf20Sopenharmony_ci generic = ghes->generic; 14218c2ecf20Sopenharmony_ci 14228c2ecf20Sopenharmony_ci ghes->flags |= GHES_EXITING; 14238c2ecf20Sopenharmony_ci switch (generic->notify.type) { 14248c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_POLLED: 14258c2ecf20Sopenharmony_ci del_timer_sync(&ghes->timer); 14268c2ecf20Sopenharmony_ci break; 14278c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_EXTERNAL: 14288c2ecf20Sopenharmony_ci free_irq(ghes->irq, ghes); 14298c2ecf20Sopenharmony_ci break; 14308c2ecf20Sopenharmony_ci 14318c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SCI: 14328c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_GSIV: 14338c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_GPIO: 14348c2ecf20Sopenharmony_ci mutex_lock(&ghes_list_mutex); 14358c2ecf20Sopenharmony_ci list_del_rcu(&ghes->list); 14368c2ecf20Sopenharmony_ci if (list_empty(&ghes_hed)) 14378c2ecf20Sopenharmony_ci unregister_acpi_hed_notifier(&ghes_notifier_hed); 14388c2ecf20Sopenharmony_ci mutex_unlock(&ghes_list_mutex); 14398c2ecf20Sopenharmony_ci synchronize_rcu(); 14408c2ecf20Sopenharmony_ci break; 14418c2ecf20Sopenharmony_ci 14428c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SEA: 14438c2ecf20Sopenharmony_ci ghes_sea_remove(ghes); 14448c2ecf20Sopenharmony_ci break; 14458c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_NMI: 14468c2ecf20Sopenharmony_ci ghes_nmi_remove(ghes); 14478c2ecf20Sopenharmony_ci break; 14488c2ecf20Sopenharmony_ci case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 14498c2ecf20Sopenharmony_ci rc = apei_sdei_unregister_ghes(ghes); 14508c2ecf20Sopenharmony_ci if (rc) 14518c2ecf20Sopenharmony_ci return rc; 14528c2ecf20Sopenharmony_ci break; 14538c2ecf20Sopenharmony_ci default: 14548c2ecf20Sopenharmony_ci BUG(); 14558c2ecf20Sopenharmony_ci break; 14568c2ecf20Sopenharmony_ci } 14578c2ecf20Sopenharmony_ci 14588c2ecf20Sopenharmony_ci ghes_fini(ghes); 14598c2ecf20Sopenharmony_ci 14608c2ecf20Sopenharmony_ci ghes_edac_unregister(ghes); 14618c2ecf20Sopenharmony_ci 14628c2ecf20Sopenharmony_ci kfree(ghes); 14638c2ecf20Sopenharmony_ci 14648c2ecf20Sopenharmony_ci platform_set_drvdata(ghes_dev, NULL); 14658c2ecf20Sopenharmony_ci 14668c2ecf20Sopenharmony_ci return 0; 14678c2ecf20Sopenharmony_ci} 14688c2ecf20Sopenharmony_ci 14698c2ecf20Sopenharmony_cistatic struct platform_driver ghes_platform_driver = { 14708c2ecf20Sopenharmony_ci .driver = { 14718c2ecf20Sopenharmony_ci .name = "GHES", 14728c2ecf20Sopenharmony_ci }, 14738c2ecf20Sopenharmony_ci .probe = ghes_probe, 14748c2ecf20Sopenharmony_ci .remove = ghes_remove, 14758c2ecf20Sopenharmony_ci}; 14768c2ecf20Sopenharmony_ci 14778c2ecf20Sopenharmony_civoid __init ghes_init(void) 14788c2ecf20Sopenharmony_ci{ 14798c2ecf20Sopenharmony_ci int rc; 14808c2ecf20Sopenharmony_ci 14818c2ecf20Sopenharmony_ci sdei_init(); 14828c2ecf20Sopenharmony_ci 14838c2ecf20Sopenharmony_ci if (acpi_disabled) 14848c2ecf20Sopenharmony_ci return; 14858c2ecf20Sopenharmony_ci 14868c2ecf20Sopenharmony_ci switch (hest_disable) { 14878c2ecf20Sopenharmony_ci case HEST_NOT_FOUND: 14888c2ecf20Sopenharmony_ci return; 14898c2ecf20Sopenharmony_ci case HEST_DISABLED: 14908c2ecf20Sopenharmony_ci pr_info(GHES_PFX "HEST is not enabled!\n"); 14918c2ecf20Sopenharmony_ci return; 14928c2ecf20Sopenharmony_ci default: 14938c2ecf20Sopenharmony_ci break; 14948c2ecf20Sopenharmony_ci } 14958c2ecf20Sopenharmony_ci 14968c2ecf20Sopenharmony_ci if (ghes_disable) { 14978c2ecf20Sopenharmony_ci pr_info(GHES_PFX "GHES is not enabled!\n"); 14988c2ecf20Sopenharmony_ci return; 14998c2ecf20Sopenharmony_ci } 15008c2ecf20Sopenharmony_ci 15018c2ecf20Sopenharmony_ci ghes_nmi_init_cxt(); 15028c2ecf20Sopenharmony_ci 15038c2ecf20Sopenharmony_ci rc = platform_driver_register(&ghes_platform_driver); 15048c2ecf20Sopenharmony_ci if (rc) 15058c2ecf20Sopenharmony_ci return; 15068c2ecf20Sopenharmony_ci 15078c2ecf20Sopenharmony_ci rc = apei_osc_setup(); 15088c2ecf20Sopenharmony_ci if (rc == 0 && osc_sb_apei_support_acked) 15098c2ecf20Sopenharmony_ci pr_info(GHES_PFX "APEI firmware first mode is enabled by APEI bit and WHEA _OSC.\n"); 15108c2ecf20Sopenharmony_ci else if (rc == 0 && !osc_sb_apei_support_acked) 15118c2ecf20Sopenharmony_ci pr_info(GHES_PFX "APEI firmware first mode is enabled by WHEA _OSC.\n"); 15128c2ecf20Sopenharmony_ci else if (rc && osc_sb_apei_support_acked) 15138c2ecf20Sopenharmony_ci pr_info(GHES_PFX "APEI firmware first mode is enabled by APEI bit.\n"); 15148c2ecf20Sopenharmony_ci else 15158c2ecf20Sopenharmony_ci pr_info(GHES_PFX "Failed to enable APEI firmware first mode.\n"); 15168c2ecf20Sopenharmony_ci} 1517