11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2d334a491SHuang Ying /*
3d334a491SHuang Ying * APEI Generic Hardware Error Source support
4d334a491SHuang Ying *
5d334a491SHuang Ying * Generic Hardware Error Source provides a way to report platform
6d334a491SHuang Ying * hardware errors (such as that from chipset). It works in so called
7d334a491SHuang Ying * "Firmware First" mode, that is, hardware errors are reported to
8d334a491SHuang Ying * firmware firstly, then reported to Linux by firmware. This way,
9d334a491SHuang Ying * some non-standard hardware error registers or non-standard hardware
10d334a491SHuang Ying * link can be checked by firmware to produce more hardware error
11d334a491SHuang Ying * information for Linux.
12d334a491SHuang Ying *
13d334a491SHuang Ying * For more information about Generic Hardware Error Source, please
14d334a491SHuang Ying * refer to ACPI Specification version 4.0, section 17.3.2.6
15d334a491SHuang Ying *
1667eb2e99SHuang Ying * Copyright 2010,2011 Intel Corp.
17d334a491SHuang Ying * Author: Huang Ying <ying.huang@intel.com>
18d334a491SHuang Ying */
19d334a491SHuang Ying
20f9f05395SJames Morse #include <linux/arm_sdei.h>
21d334a491SHuang Ying #include <linux/kernel.h>
22020bf066SPaul Gortmaker #include <linux/moduleparam.h>
23d334a491SHuang Ying #include <linux/init.h>
24d334a491SHuang Ying #include <linux/acpi.h>
25d334a491SHuang Ying #include <linux/io.h>
26d334a491SHuang Ying #include <linux/interrupt.h>
2781e88fdcSHuang Ying #include <linux/timer.h>
28d334a491SHuang Ying #include <linux/cper.h>
297ad6e943SHuang Ying #include <linux/platform_device.h>
307ad6e943SHuang Ying #include <linux/mutex.h>
3132c361f5SHuang Ying #include <linux/ratelimit.h>
3281e88fdcSHuang Ying #include <linux/vmalloc.h>
3367eb2e99SHuang Ying #include <linux/irq_work.h>
3467eb2e99SHuang Ying #include <linux/llist.h>
3567eb2e99SHuang Ying #include <linux/genalloc.h>
36a654e5eeSHuang Ying #include <linux/pci.h>
37b484079bSJames Morse #include <linux/pfn.h>
38a654e5eeSHuang Ying #include <linux/aer.h>
3944a69f61STomasz Nowicki #include <linux/nmi.h>
40e6017571SIngo Molnar #include <linux/sched/clock.h>
41297b64c7STyler Baicar #include <linux/uuid.h>
42297b64c7STyler Baicar #include <linux/ras.h>
437f17b4a1SJames Morse #include <linux/task_work.h>
4440e06415SMauro Carvalho Chehab
4542aa5604STyler Baicar #include <acpi/actbl1.h>
4640e06415SMauro Carvalho Chehab #include <acpi/ghes.h>
479dae3d0dSTomasz Nowicki #include <acpi/apei.h>
484f89fa28SJames Morse #include <asm/fixmap.h>
4981e88fdcSHuang Ying #include <asm/tlbflush.h>
50297b64c7STyler Baicar #include <ras/ras_event.h>
51d334a491SHuang Ying
52d334a491SHuang Ying #include "apei-internal.h"
53d334a491SHuang Ying
54d334a491SHuang Ying #define GHES_PFX "GHES: "
55d334a491SHuang Ying
56d334a491SHuang Ying #define GHES_ESTATUS_MAX_SIZE 65536
5767eb2e99SHuang Ying #define GHES_ESOURCE_PREALLOC_MAX_SIZE 65536
5867eb2e99SHuang Ying
5967eb2e99SHuang Ying #define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER 3
6067eb2e99SHuang Ying
61152cef40SHuang Ying /* This is just an estimation for memory pool allocation */
62152cef40SHuang Ying #define GHES_ESTATUS_CACHE_AVG_SIZE 512
63152cef40SHuang Ying
64152cef40SHuang Ying #define GHES_ESTATUS_CACHES_SIZE 4
65152cef40SHuang Ying
6670cb6e1dSLen Brown #define GHES_ESTATUS_IN_CACHE_MAX_NSEC 10000000000ULL
67152cef40SHuang Ying /* Prevent too many caches are allocated because of RCU */
68152cef40SHuang Ying #define GHES_ESTATUS_CACHE_ALLOCED_MAX (GHES_ESTATUS_CACHES_SIZE * 3 / 2)
69152cef40SHuang Ying
70152cef40SHuang Ying #define GHES_ESTATUS_CACHE_LEN(estatus_len) \
71152cef40SHuang Ying (sizeof(struct ghes_estatus_cache) + (estatus_len))
72152cef40SHuang Ying #define GHES_ESTATUS_FROM_CACHE(estatus_cache) \
730a00fd5eSLv Zheng ((struct acpi_hest_generic_status *) \
74152cef40SHuang Ying ((struct ghes_estatus_cache *)(estatus_cache) + 1))
75152cef40SHuang Ying
7667eb2e99SHuang Ying #define GHES_ESTATUS_NODE_LEN(estatus_len) \
7767eb2e99SHuang Ying (sizeof(struct ghes_estatus_node) + (estatus_len))
7867eb2e99SHuang Ying #define GHES_ESTATUS_FROM_NODE(estatus_node) \
790a00fd5eSLv Zheng ((struct acpi_hest_generic_status *) \
8067eb2e99SHuang Ying ((struct ghes_estatus_node *)(estatus_node) + 1))
81d334a491SHuang Ying
829aa9cf3eSShiju Jose #define GHES_VENDOR_ENTRY_LEN(gdata_len) \
839aa9cf3eSShiju Jose (sizeof(struct ghes_vendor_record_entry) + (gdata_len))
849aa9cf3eSShiju Jose #define GHES_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \
859aa9cf3eSShiju Jose ((struct acpi_hest_generic_data *) \
869aa9cf3eSShiju Jose ((struct ghes_vendor_record_entry *)(vendor_entry) + 1))
879aa9cf3eSShiju Jose
88f9f05395SJames Morse /*
89f9f05395SJames Morse * NMI-like notifications vary by architecture, before the compiler can prune
90f9f05395SJames Morse * unused static functions it needs a value for these enums.
91f9f05395SJames Morse */
92f9f05395SJames Morse #ifndef CONFIG_ARM_SDE_INTERFACE
93f9f05395SJames Morse #define FIX_APEI_GHES_SDEI_NORMAL __end_of_fixed_addresses
94f9f05395SJames Morse #define FIX_APEI_GHES_SDEI_CRITICAL __end_of_fixed_addresses
95f9f05395SJames Morse #endif
96f9f05395SJames Morse
978e40612fSJia He static ATOMIC_NOTIFIER_HEAD(ghes_report_chain);
988e40612fSJia He
is_hest_type_generic_v2(struct ghes * ghes)9942aa5604STyler Baicar static inline bool is_hest_type_generic_v2(struct ghes *ghes)
10042aa5604STyler Baicar {
10142aa5604STyler Baicar return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;
10242aa5604STyler Baicar }
10342aa5604STyler Baicar
104020bf066SPaul Gortmaker /*
105*410063c9SShuai Xue * A platform may describe one error source for the handling of synchronous
106*410063c9SShuai Xue * errors (e.g. MCE or SEA), or for handling asynchronous errors (e.g. SCI
107*410063c9SShuai Xue * or External Interrupt). On x86, the HEST notifications are always
108*410063c9SShuai Xue * asynchronous, so only SEA on ARM is delivered as a synchronous
109*410063c9SShuai Xue * notification.
110*410063c9SShuai Xue */
is_hest_sync_notify(struct ghes * ghes)111*410063c9SShuai Xue static inline bool is_hest_sync_notify(struct ghes *ghes)
112*410063c9SShuai Xue {
113*410063c9SShuai Xue u8 notify_type = ghes->generic->notify.type;
114*410063c9SShuai Xue
115*410063c9SShuai Xue return notify_type == ACPI_HEST_NOTIFY_SEA;
116*410063c9SShuai Xue }
117*410063c9SShuai Xue
118*410063c9SShuai Xue /*
119020bf066SPaul Gortmaker * This driver isn't really modular, however for the time being,
120020bf066SPaul Gortmaker * continuing to use module_param is the easiest way to remain
121020bf066SPaul Gortmaker * compatible with existing boot arg use cases.
122020bf066SPaul Gortmaker */
12390ab5ee9SRusty Russell bool ghes_disable;
124b6a95016SHuang Ying module_param_named(disable, ghes_disable, bool, 0);
125b6a95016SHuang Ying
126d334a491SHuang Ying /*
1279057a3f7SJia He * "ghes.edac_force_enable" forcibly enables ghes_edac and skips the platform
1289057a3f7SJia He * check.
1299057a3f7SJia He */
1309057a3f7SJia He static bool ghes_edac_force_enable;
1319057a3f7SJia He module_param_named(edac_force_enable, ghes_edac_force_enable, bool, 0);
1329057a3f7SJia He
1339057a3f7SJia He /*
1347bf130e4SShiju Jose * All error sources notified with HED (Hardware Error Device) share a
1357bf130e4SShiju Jose * single notifier callback, so they need to be linked and checked one
1367bf130e4SShiju Jose * by one. This holds true for NMI too.
137d334a491SHuang Ying *
13881e88fdcSHuang Ying * RCU is used for these lists, so ghes_list_mutex is only used for
13981e88fdcSHuang Ying * list changing, not for traversing.
140d334a491SHuang Ying */
1417bf130e4SShiju Jose static LIST_HEAD(ghes_hed);
1427ad6e943SHuang Ying static DEFINE_MUTEX(ghes_list_mutex);
143d334a491SHuang Ying
14481e88fdcSHuang Ying /*
1459057a3f7SJia He * A list of GHES devices which are given to the corresponding EDAC driver
1469057a3f7SJia He * ghes_edac for further use.
1479057a3f7SJia He */
1489057a3f7SJia He static LIST_HEAD(ghes_devs);
1499057a3f7SJia He static DEFINE_MUTEX(ghes_devs_mutex);
1509057a3f7SJia He
1519057a3f7SJia He /*
15281e88fdcSHuang Ying * Because the memory area used to transfer hardware error information
15381e88fdcSHuang Ying * from BIOS to Linux can be determined only in NMI, IRQ or timer
15481e88fdcSHuang Ying * handler, but general ioremap can not be used in atomic context, so
1554f89fa28SJames Morse * the fixmap is used instead.
156520e18a5SJames Morse *
1573b880cbeSJames Morse * This spinlock is used to prevent the fixmap entry from being used
1584f89fa28SJames Morse * simultaneously.
15981e88fdcSHuang Ying */
1603b880cbeSJames Morse static DEFINE_SPINLOCK(ghes_notify_lock_irq);
16181e88fdcSHuang Ying
1629aa9cf3eSShiju Jose struct ghes_vendor_record_entry {
1639aa9cf3eSShiju Jose struct work_struct work;
1649aa9cf3eSShiju Jose int error_severity;
1659aa9cf3eSShiju Jose char vendor_record[];
1669aa9cf3eSShiju Jose };
1679aa9cf3eSShiju Jose
16867eb2e99SHuang Ying static struct gen_pool *ghes_estatus_pool;
16967eb2e99SHuang Ying
170dd3fa54bSArd Biesheuvel static struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
171152cef40SHuang Ying static atomic_t ghes_estatus_cache_alloced;
172152cef40SHuang Ying
1732fb5853eSJonathan (Zhixiong) Zhang static int ghes_panic_timeout __read_mostly = 30;
1742fb5853eSJonathan (Zhixiong) Zhang
ghes_map(u64 pfn,enum fixed_addresses fixmap_idx)175b484079bSJames Morse static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
17681e88fdcSHuang Ying {
1777edda088STyler Baicar phys_addr_t paddr;
1787edda088STyler Baicar pgprot_t prot;
17981e88fdcSHuang Ying
180b484079bSJames Morse paddr = PFN_PHYS(pfn);
1817edda088STyler Baicar prot = arch_apei_get_mem_attribute(paddr);
182b484079bSJames Morse __set_fixmap(fixmap_idx, paddr, prot);
18381e88fdcSHuang Ying
184b484079bSJames Morse return (void __iomem *) __fix_to_virt(fixmap_idx);
18581e88fdcSHuang Ying }
18681e88fdcSHuang Ying
ghes_unmap(void __iomem * vaddr,enum fixed_addresses fixmap_idx)187b484079bSJames Morse static void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx)
18881e88fdcSHuang Ying {
189b484079bSJames Morse int _idx = virt_to_fix((unsigned long)vaddr);
19081e88fdcSHuang Ying
191b484079bSJames Morse WARN_ON_ONCE(fixmap_idx != _idx);
192b484079bSJames Morse clear_fixmap(fixmap_idx);
19381e88fdcSHuang Ying }
19481e88fdcSHuang Ying
ghes_estatus_pool_init(unsigned int num_ghes)19543d27483SAshish Kalra int ghes_estatus_pool_init(unsigned int num_ghes)
19667eb2e99SHuang Ying {
197fb7be08fSJames Morse unsigned long addr, len;
1986abc7622SLiguang Zhang int rc;
199fb7be08fSJames Morse
20067eb2e99SHuang Ying ghes_estatus_pool = gen_pool_create(GHES_ESTATUS_POOL_MIN_ALLOC_ORDER, -1);
20167eb2e99SHuang Ying if (!ghes_estatus_pool)
20267eb2e99SHuang Ying return -ENOMEM;
20367eb2e99SHuang Ying
204fb7be08fSJames Morse len = GHES_ESTATUS_CACHE_AVG_SIZE * GHES_ESTATUS_CACHE_ALLOCED_MAX;
205fb7be08fSJames Morse len += (num_ghes * GHES_ESOURCE_PREALLOC_MAX_SIZE);
20667eb2e99SHuang Ying
2070ac234beSJames Morse addr = (unsigned long)vmalloc(PAGE_ALIGN(len));
20867eb2e99SHuang Ying if (!addr)
2096abc7622SLiguang Zhang goto err_pool_alloc;
21067eb2e99SHuang Ying
2116abc7622SLiguang Zhang rc = gen_pool_add(ghes_estatus_pool, addr, PAGE_ALIGN(len), -1);
2126abc7622SLiguang Zhang if (rc)
2136abc7622SLiguang Zhang goto err_pool_add;
2146abc7622SLiguang Zhang
2156abc7622SLiguang Zhang return 0;
2166abc7622SLiguang Zhang
2176abc7622SLiguang Zhang err_pool_add:
2186abc7622SLiguang Zhang vfree((void *)addr);
2196abc7622SLiguang Zhang
2206abc7622SLiguang Zhang err_pool_alloc:
2216abc7622SLiguang Zhang gen_pool_destroy(ghes_estatus_pool);
2226abc7622SLiguang Zhang
2236abc7622SLiguang Zhang return -ENOMEM;
22467eb2e99SHuang Ying }
22567eb2e99SHuang Ying
226b7765b0aSShiju Jose /**
227b7765b0aSShiju Jose * ghes_estatus_pool_region_free - free previously allocated memory
228b7765b0aSShiju Jose * from the ghes_estatus_pool.
229b7765b0aSShiju Jose * @addr: address of memory to free.
230b7765b0aSShiju Jose * @size: size of memory to free.
231b7765b0aSShiju Jose *
232b7765b0aSShiju Jose * Returns none.
233b7765b0aSShiju Jose */
ghes_estatus_pool_region_free(unsigned long addr,u32 size)234b7765b0aSShiju Jose void ghes_estatus_pool_region_free(unsigned long addr, u32 size)
235b7765b0aSShiju Jose {
236b7765b0aSShiju Jose gen_pool_free(ghes_estatus_pool, addr, size);
237b7765b0aSShiju Jose }
238b7765b0aSShiju Jose EXPORT_SYMBOL_GPL(ghes_estatus_pool_region_free);
239b7765b0aSShiju Jose
map_gen_v2(struct ghes * ghes)24042aa5604STyler Baicar static int map_gen_v2(struct ghes *ghes)
24142aa5604STyler Baicar {
24242aa5604STyler Baicar return apei_map_generic_address(&ghes->generic_v2->read_ack_register);
24342aa5604STyler Baicar }
24442aa5604STyler Baicar
unmap_gen_v2(struct ghes * ghes)24542aa5604STyler Baicar static void unmap_gen_v2(struct ghes *ghes)
24642aa5604STyler Baicar {
24742aa5604STyler Baicar apei_unmap_generic_address(&ghes->generic_v2->read_ack_register);
24842aa5604STyler Baicar }
24942aa5604STyler Baicar
ghes_ack_error(struct acpi_hest_generic_v2 * gv2)25006ddeadcSJames Morse static void ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
25106ddeadcSJames Morse {
25206ddeadcSJames Morse int rc;
25306ddeadcSJames Morse u64 val = 0;
25406ddeadcSJames Morse
25506ddeadcSJames Morse rc = apei_read(&val, &gv2->read_ack_register);
25606ddeadcSJames Morse if (rc)
25706ddeadcSJames Morse return;
25806ddeadcSJames Morse
25906ddeadcSJames Morse val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset;
26006ddeadcSJames Morse val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset;
26106ddeadcSJames Morse
26206ddeadcSJames Morse apei_write(val, &gv2->read_ack_register);
26306ddeadcSJames Morse }
26406ddeadcSJames Morse
ghes_new(struct acpi_hest_generic * generic)265d334a491SHuang Ying static struct ghes *ghes_new(struct acpi_hest_generic *generic)
266d334a491SHuang Ying {
267d334a491SHuang Ying struct ghes *ghes;
268d334a491SHuang Ying unsigned int error_block_length;
269d334a491SHuang Ying int rc;
270d334a491SHuang Ying
271d334a491SHuang Ying ghes = kzalloc(sizeof(*ghes), GFP_KERNEL);
272d334a491SHuang Ying if (!ghes)
273d334a491SHuang Ying return ERR_PTR(-ENOMEM);
27442aa5604STyler Baicar
275d334a491SHuang Ying ghes->generic = generic;
27642aa5604STyler Baicar if (is_hest_type_generic_v2(ghes)) {
27742aa5604STyler Baicar rc = map_gen_v2(ghes);
278d334a491SHuang Ying if (rc)
279d334a491SHuang Ying goto err_free;
28042aa5604STyler Baicar }
28142aa5604STyler Baicar
28242aa5604STyler Baicar rc = apei_map_generic_address(&generic->error_status_address);
28342aa5604STyler Baicar if (rc)
28442aa5604STyler Baicar goto err_unmap_read_ack_addr;
285d334a491SHuang Ying error_block_length = generic->error_block_length;
286d334a491SHuang Ying if (error_block_length > GHES_ESTATUS_MAX_SIZE) {
287933ca4e3SKefeng Wang pr_warn(FW_WARN GHES_PFX
288d334a491SHuang Ying "Error status block length is too long: %u for "
289d334a491SHuang Ying "generic hardware error source: %d.\n",
290d334a491SHuang Ying error_block_length, generic->header.source_id);
291d334a491SHuang Ying error_block_length = GHES_ESTATUS_MAX_SIZE;
292d334a491SHuang Ying }
293d334a491SHuang Ying ghes->estatus = kmalloc(error_block_length, GFP_KERNEL);
294d334a491SHuang Ying if (!ghes->estatus) {
295d334a491SHuang Ying rc = -ENOMEM;
29642aa5604STyler Baicar goto err_unmap_status_addr;
297d334a491SHuang Ying }
298d334a491SHuang Ying
299d334a491SHuang Ying return ghes;
300d334a491SHuang Ying
30142aa5604STyler Baicar err_unmap_status_addr:
30234ddeb03SHuang Ying apei_unmap_generic_address(&generic->error_status_address);
30342aa5604STyler Baicar err_unmap_read_ack_addr:
30442aa5604STyler Baicar if (is_hest_type_generic_v2(ghes))
30542aa5604STyler Baicar unmap_gen_v2(ghes);
306d334a491SHuang Ying err_free:
307d334a491SHuang Ying kfree(ghes);
308d334a491SHuang Ying return ERR_PTR(rc);
309d334a491SHuang Ying }
310d334a491SHuang Ying
ghes_fini(struct ghes * ghes)311d334a491SHuang Ying static void ghes_fini(struct ghes *ghes)
312d334a491SHuang Ying {
313d334a491SHuang Ying kfree(ghes->estatus);
31434ddeb03SHuang Ying apei_unmap_generic_address(&ghes->generic->error_status_address);
31542aa5604STyler Baicar if (is_hest_type_generic_v2(ghes))
31642aa5604STyler Baicar unmap_gen_v2(ghes);
317d334a491SHuang Ying }
318d334a491SHuang Ying
ghes_severity(int severity)319d334a491SHuang Ying static inline int ghes_severity(int severity)
320d334a491SHuang Ying {
321d334a491SHuang Ying switch (severity) {
322ad4ecef2SHuang Ying case CPER_SEV_INFORMATIONAL:
323ad4ecef2SHuang Ying return GHES_SEV_NO;
324ad4ecef2SHuang Ying case CPER_SEV_CORRECTED:
325ad4ecef2SHuang Ying return GHES_SEV_CORRECTED;
326ad4ecef2SHuang Ying case CPER_SEV_RECOVERABLE:
327ad4ecef2SHuang Ying return GHES_SEV_RECOVERABLE;
328ad4ecef2SHuang Ying case CPER_SEV_FATAL:
329ad4ecef2SHuang Ying return GHES_SEV_PANIC;
330d334a491SHuang Ying default:
33125985edcSLucas De Marchi /* Unknown, go panic */
332ad4ecef2SHuang Ying return GHES_SEV_PANIC;
333d334a491SHuang Ying }
334d334a491SHuang Ying }
335d334a491SHuang Ying
ghes_copy_tofrom_phys(void * buffer,u64 paddr,u32 len,int from_phys,enum fixed_addresses fixmap_idx)33681e88fdcSHuang Ying static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
337b484079bSJames Morse int from_phys,
338b484079bSJames Morse enum fixed_addresses fixmap_idx)
339d334a491SHuang Ying {
34081e88fdcSHuang Ying void __iomem *vaddr;
34181e88fdcSHuang Ying u64 offset;
34281e88fdcSHuang Ying u32 trunk;
343d334a491SHuang Ying
34481e88fdcSHuang Ying while (len > 0) {
34581e88fdcSHuang Ying offset = paddr - (paddr & PAGE_MASK);
346b484079bSJames Morse vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx);
34781e88fdcSHuang Ying trunk = PAGE_SIZE - offset;
34881e88fdcSHuang Ying trunk = min(trunk, len);
349d334a491SHuang Ying if (from_phys)
35081e88fdcSHuang Ying memcpy_fromio(buffer, vaddr + offset, trunk);
351d334a491SHuang Ying else
35281e88fdcSHuang Ying memcpy_toio(vaddr + offset, buffer, trunk);
35381e88fdcSHuang Ying len -= trunk;
35481e88fdcSHuang Ying paddr += trunk;
35581e88fdcSHuang Ying buffer += trunk;
356b484079bSJames Morse ghes_unmap(vaddr, fixmap_idx);
35781e88fdcSHuang Ying }
358d334a491SHuang Ying }
359d334a491SHuang Ying
360f2a681b9SJames Morse /* Check the top-level record header has an appropriate size. */
__ghes_check_estatus(struct ghes * ghes,struct acpi_hest_generic_status * estatus)361f2a681b9SJames Morse static int __ghes_check_estatus(struct ghes *ghes,
362f2a681b9SJames Morse struct acpi_hest_generic_status *estatus)
363f2a681b9SJames Morse {
364f2a681b9SJames Morse u32 len = cper_estatus_len(estatus);
365f2a681b9SJames Morse
366f2a681b9SJames Morse if (len < sizeof(*estatus)) {
367f2a681b9SJames Morse pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status block!\n");
368f2a681b9SJames Morse return -EIO;
369f2a681b9SJames Morse }
370f2a681b9SJames Morse
371f2a681b9SJames Morse if (len > ghes->generic->error_block_length) {
372f2a681b9SJames Morse pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status block length!\n");
373f2a681b9SJames Morse return -EIO;
374f2a681b9SJames Morse }
375f2a681b9SJames Morse
376f2a681b9SJames Morse if (cper_estatus_check_header(estatus)) {
377f2a681b9SJames Morse pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n");
378f2a681b9SJames Morse return -EIO;
379f2a681b9SJames Morse }
380f2a681b9SJames Morse
381f2a681b9SJames Morse return 0;
382f2a681b9SJames Morse }
383f2a681b9SJames Morse
384e00a6e33SJames Morse /* Read the CPER block, returning its address, and header in estatus. */
__ghes_peek_estatus(struct ghes * ghes,struct acpi_hest_generic_status * estatus,u64 * buf_paddr,enum fixed_addresses fixmap_idx)385e00a6e33SJames Morse static int __ghes_peek_estatus(struct ghes *ghes,
386f2a7e059SJames Morse struct acpi_hest_generic_status *estatus,
387f2a7e059SJames Morse u64 *buf_paddr, enum fixed_addresses fixmap_idx)
388d334a491SHuang Ying {
389d334a491SHuang Ying struct acpi_hest_generic *g = ghes->generic;
390d334a491SHuang Ying int rc;
391d334a491SHuang Ying
392eeb25557SJames Morse rc = apei_read(buf_paddr, &g->error_status_address);
393d334a491SHuang Ying if (rc) {
394eeb25557SJames Morse *buf_paddr = 0;
39593066e9aSJames Morse pr_warn_ratelimited(FW_WARN GHES_PFX
396d334a491SHuang Ying "Failed to read error status block address for hardware error source: %d.\n",
397d334a491SHuang Ying g->header.source_id);
398d334a491SHuang Ying return -EIO;
399d334a491SHuang Ying }
400eeb25557SJames Morse if (!*buf_paddr)
401d334a491SHuang Ying return -ENOENT;
402d334a491SHuang Ying
403f2a7e059SJames Morse ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1,
404f2a7e059SJames Morse fixmap_idx);
405f2a7e059SJames Morse if (!estatus->block_status) {
406eeb25557SJames Morse *buf_paddr = 0;
407d334a491SHuang Ying return -ENOENT;
408eeb25557SJames Morse }
409d334a491SHuang Ying
410371b8689SLiguang Zhang return 0;
411e00a6e33SJames Morse }
412f2a681b9SJames Morse
__ghes_read_estatus(struct acpi_hest_generic_status * estatus,u64 buf_paddr,enum fixed_addresses fixmap_idx,size_t buf_len)413e00a6e33SJames Morse static int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
414e00a6e33SJames Morse u64 buf_paddr, enum fixed_addresses fixmap_idx,
415e00a6e33SJames Morse size_t buf_len)
416e00a6e33SJames Morse {
417e00a6e33SJames Morse ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx);
418f2a681b9SJames Morse if (cper_estatus_check(estatus)) {
41993066e9aSJames Morse pr_warn_ratelimited(FW_WARN GHES_PFX
420d334a491SHuang Ying "Failed to read error status block!\n");
421f2a681b9SJames Morse return -EIO;
422f2a681b9SJames Morse }
423eeb25557SJames Morse
424f2a681b9SJames Morse return 0;
425d334a491SHuang Ying }
426d334a491SHuang Ying
ghes_read_estatus(struct ghes * ghes,struct acpi_hest_generic_status * estatus,u64 * buf_paddr,enum fixed_addresses fixmap_idx)427e00a6e33SJames Morse static int ghes_read_estatus(struct ghes *ghes,
428e00a6e33SJames Morse struct acpi_hest_generic_status *estatus,
429e00a6e33SJames Morse u64 *buf_paddr, enum fixed_addresses fixmap_idx)
430e00a6e33SJames Morse {
431e00a6e33SJames Morse int rc;
432e00a6e33SJames Morse
433e00a6e33SJames Morse rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx);
434e00a6e33SJames Morse if (rc)
435e00a6e33SJames Morse return rc;
436e00a6e33SJames Morse
437e00a6e33SJames Morse rc = __ghes_check_estatus(ghes, estatus);
438e00a6e33SJames Morse if (rc)
439e00a6e33SJames Morse return rc;
440e00a6e33SJames Morse
441e00a6e33SJames Morse return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx,
442e00a6e33SJames Morse cper_estatus_len(estatus));
443e00a6e33SJames Morse }
444e00a6e33SJames Morse
ghes_clear_estatus(struct ghes * ghes,struct acpi_hest_generic_status * estatus,u64 buf_paddr,enum fixed_addresses fixmap_idx)445f2a7e059SJames Morse static void ghes_clear_estatus(struct ghes *ghes,
446f2a7e059SJames Morse struct acpi_hest_generic_status *estatus,
447f2a7e059SJames Morse u64 buf_paddr, enum fixed_addresses fixmap_idx)
448d334a491SHuang Ying {
449f2a7e059SJames Morse estatus->block_status = 0;
450eeb25557SJames Morse
451eeb25557SJames Morse if (!buf_paddr)
452eeb25557SJames Morse return;
453eeb25557SJames Morse
454f2a7e059SJames Morse ghes_copy_tofrom_phys(estatus, buf_paddr,
455f2a7e059SJames Morse sizeof(estatus->block_status), 0,
456b484079bSJames Morse fixmap_idx);
45706ddeadcSJames Morse
45806ddeadcSJames Morse /*
45906ddeadcSJames Morse * GHESv2 type HEST entries introduce support for error acknowledgment,
46006ddeadcSJames Morse * so only acknowledge the error if this support is present.
46106ddeadcSJames Morse */
46206ddeadcSJames Morse if (is_hest_type_generic_v2(ghes))
46306ddeadcSJames Morse ghes_ack_error(ghes->generic_v2);
464d334a491SHuang Ying }
465d334a491SHuang Ying
4667f17b4a1SJames Morse /*
4677f17b4a1SJames Morse * Called as task_work before returning to user-space.
4687f17b4a1SJames Morse * Ensure any queued work has been done before we return to the context that
4697f17b4a1SJames Morse * triggered the notification.
4707f17b4a1SJames Morse */
ghes_kick_task_work(struct callback_head * head)4717f17b4a1SJames Morse static void ghes_kick_task_work(struct callback_head *head)
472cf870c70SNaveen N. Rao {
4737f17b4a1SJames Morse struct acpi_hest_generic_status *estatus;
4747f17b4a1SJames Morse struct ghes_estatus_node *estatus_node;
4757f17b4a1SJames Morse u32 node_len;
4767f17b4a1SJames Morse
4777f17b4a1SJames Morse estatus_node = container_of(head, struct ghes_estatus_node, task_work);
4787f17b4a1SJames Morse if (IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
4797f17b4a1SJames Morse memory_failure_queue_kick(estatus_node->task_work_cpu);
4807f17b4a1SJames Morse
4817f17b4a1SJames Morse estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
4827f17b4a1SJames Morse node_len = GHES_ESTATUS_NODE_LEN(cper_estatus_len(estatus));
4837f17b4a1SJames Morse gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, node_len);
4847f17b4a1SJames Morse }
4857f17b4a1SJames Morse
ghes_do_memory_failure(u64 physical_addr,int flags)486ccb5ecdcSXiaofei Tan static bool ghes_do_memory_failure(u64 physical_addr, int flags)
4877f17b4a1SJames Morse {
488cf870c70SNaveen N. Rao unsigned long pfn;
489cf870c70SNaveen N. Rao
4907f17b4a1SJames Morse if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
4917f17b4a1SJames Morse return false;
4927f17b4a1SJames Morse
493ccb5ecdcSXiaofei Tan pfn = PHYS_PFN(physical_addr);
4943ad6fd77STony Luck if (!pfn_valid(pfn) && !arch_is_platform_page(physical_addr)) {
495ca104edcSChen, Gong pr_warn_ratelimited(FW_WARN GHES_PFX
496cf870c70SNaveen N. Rao "Invalid address in generic error data: %#llx\n",
497ccb5ecdcSXiaofei Tan physical_addr);
4987f17b4a1SJames Morse return false;
499cf870c70SNaveen N. Rao }
500ca104edcSChen, Gong
501ccb5ecdcSXiaofei Tan memory_failure_queue(pfn, flags);
502ccb5ecdcSXiaofei Tan return true;
503ccb5ecdcSXiaofei Tan }
504ccb5ecdcSXiaofei Tan
ghes_handle_memory_failure(struct acpi_hest_generic_data * gdata,int sev,bool sync)505ccb5ecdcSXiaofei Tan static bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata,
506*410063c9SShuai Xue int sev, bool sync)
507ccb5ecdcSXiaofei Tan {
508ccb5ecdcSXiaofei Tan int flags = -1;
509ccb5ecdcSXiaofei Tan int sec_sev = ghes_severity(gdata->error_severity);
510ccb5ecdcSXiaofei Tan struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
511ccb5ecdcSXiaofei Tan
512ccb5ecdcSXiaofei Tan if (!(mem_err->validation_bits & CPER_MEM_VALID_PA))
513ccb5ecdcSXiaofei Tan return false;
514ccb5ecdcSXiaofei Tan
515ca104edcSChen, Gong /* iff following two events can be handled properly by now */
516ca104edcSChen, Gong if (sec_sev == GHES_SEV_CORRECTED &&
517ca104edcSChen, Gong (gdata->flags & CPER_SEC_ERROR_THRESHOLD_EXCEEDED))
518ca104edcSChen, Gong flags = MF_SOFT_OFFLINE;
519ca104edcSChen, Gong if (sev == GHES_SEV_RECOVERABLE && sec_sev == GHES_SEV_RECOVERABLE)
520*410063c9SShuai Xue flags = sync ? MF_ACTION_REQUIRED : 0;
521ca104edcSChen, Gong
522ccb5ecdcSXiaofei Tan if (flags != -1)
523ccb5ecdcSXiaofei Tan return ghes_do_memory_failure(mem_err->physical_addr, flags);
5247f17b4a1SJames Morse
5257f17b4a1SJames Morse return false;
526cf870c70SNaveen N. Rao }
527cf870c70SNaveen N. Rao
ghes_handle_arm_hw_error(struct acpi_hest_generic_data * gdata,int sev,bool sync)528*410063c9SShuai Xue static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata,
529*410063c9SShuai Xue int sev, bool sync)
530ccb5ecdcSXiaofei Tan {
531ccb5ecdcSXiaofei Tan struct cper_sec_proc_arm *err = acpi_hest_get_payload(gdata);
532*410063c9SShuai Xue int flags = sync ? MF_ACTION_REQUIRED : 0;
533ccb5ecdcSXiaofei Tan bool queued = false;
534ccb5ecdcSXiaofei Tan int sec_sev, i;
535ccb5ecdcSXiaofei Tan char *p;
536ccb5ecdcSXiaofei Tan
537ccb5ecdcSXiaofei Tan log_arm_hw_error(err);
538ccb5ecdcSXiaofei Tan
539ccb5ecdcSXiaofei Tan sec_sev = ghes_severity(gdata->error_severity);
540ccb5ecdcSXiaofei Tan if (sev != GHES_SEV_RECOVERABLE || sec_sev != GHES_SEV_RECOVERABLE)
541ccb5ecdcSXiaofei Tan return false;
542ccb5ecdcSXiaofei Tan
543ccb5ecdcSXiaofei Tan p = (char *)(err + 1);
544ccb5ecdcSXiaofei Tan for (i = 0; i < err->err_info_num; i++) {
545ccb5ecdcSXiaofei Tan struct cper_arm_err_info *err_info = (struct cper_arm_err_info *)p;
546ccb5ecdcSXiaofei Tan bool is_cache = (err_info->type == CPER_ARM_CACHE_ERROR);
547ccb5ecdcSXiaofei Tan bool has_pa = (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR);
548ccb5ecdcSXiaofei Tan const char *error_type = "unknown error";
549ccb5ecdcSXiaofei Tan
550ccb5ecdcSXiaofei Tan /*
551ccb5ecdcSXiaofei Tan * The field (err_info->error_info & BIT(26)) is fixed to set to
552ccb5ecdcSXiaofei Tan * 1 in some old firmware of HiSilicon Kunpeng920. We assume that
553ccb5ecdcSXiaofei Tan * firmware won't mix corrected errors in an uncorrected section,
554ccb5ecdcSXiaofei Tan * and don't filter out 'corrected' error here.
555ccb5ecdcSXiaofei Tan */
556ccb5ecdcSXiaofei Tan if (is_cache && has_pa) {
557*410063c9SShuai Xue queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags);
558ccb5ecdcSXiaofei Tan p += err_info->length;
559ccb5ecdcSXiaofei Tan continue;
560ccb5ecdcSXiaofei Tan }
561ccb5ecdcSXiaofei Tan
562ccb5ecdcSXiaofei Tan if (err_info->type < ARRAY_SIZE(cper_proc_error_type_strs))
563ccb5ecdcSXiaofei Tan error_type = cper_proc_error_type_strs[err_info->type];
564ccb5ecdcSXiaofei Tan
565ccb5ecdcSXiaofei Tan pr_warn_ratelimited(FW_WARN GHES_PFX
566ccb5ecdcSXiaofei Tan "Unhandled processor error type: %s\n",
567ccb5ecdcSXiaofei Tan error_type);
568ccb5ecdcSXiaofei Tan p += err_info->length;
569ccb5ecdcSXiaofei Tan }
570ccb5ecdcSXiaofei Tan
571ccb5ecdcSXiaofei Tan return queued;
572ccb5ecdcSXiaofei Tan }
573ccb5ecdcSXiaofei Tan
5749852ce9aSTyler Baicar /*
5759852ce9aSTyler Baicar * PCIe AER errors need to be sent to the AER driver for reporting and
5769852ce9aSTyler Baicar * recovery. The GHES severities map to the following AER severities and
5779852ce9aSTyler Baicar * require the following handling:
5789852ce9aSTyler Baicar *
5799852ce9aSTyler Baicar * GHES_SEV_CORRECTABLE -> AER_CORRECTABLE
5809852ce9aSTyler Baicar * These need to be reported by the AER driver but no recovery is
5819852ce9aSTyler Baicar * necessary.
5829852ce9aSTyler Baicar * GHES_SEV_RECOVERABLE -> AER_NONFATAL
5839852ce9aSTyler Baicar * GHES_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL
5849852ce9aSTyler Baicar * These both need to be reported and recovered from by the AER driver.
5859852ce9aSTyler Baicar * GHES_SEV_PANIC does not make it to this handling since the kernel must
5869852ce9aSTyler Baicar * panic.
5879852ce9aSTyler Baicar */
ghes_handle_aer(struct acpi_hest_generic_data * gdata)5889852ce9aSTyler Baicar static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
589d334a491SHuang Ying {
590a654e5eeSHuang Ying #ifdef CONFIG_ACPI_APEI_PCIEAER
591bbcc2e7bSTyler Baicar struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata);
592bbcc2e7bSTyler Baicar
5939852ce9aSTyler Baicar if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID &&
594a654e5eeSHuang Ying pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) {
595a654e5eeSHuang Ying unsigned int devfn;
596a654e5eeSHuang Ying int aer_severity;
597b7765b0aSShiju Jose u8 *aer_info;
5980ba98ec9SBetty Dall
599a654e5eeSHuang Ying devfn = PCI_DEVFN(pcie_err->device_id.device,
600a654e5eeSHuang Ying pcie_err->device_id.function);
6012458d66bSTyler Baicar aer_severity = cper_severity_to_aer(gdata->error_severity);
6020ba98ec9SBetty Dall
6030ba98ec9SBetty Dall /*
6040ba98ec9SBetty Dall * If firmware reset the component to contain
6050ba98ec9SBetty Dall * the error, we must reinitialize it before
6060ba98ec9SBetty Dall * use, so treat it as a fatal AER error.
6070ba98ec9SBetty Dall */
6080ba98ec9SBetty Dall if (gdata->flags & CPER_SEC_RESET)
6090ba98ec9SBetty Dall aer_severity = AER_FATAL;
6100ba98ec9SBetty Dall
611b7765b0aSShiju Jose aer_info = (void *)gen_pool_alloc(ghes_estatus_pool,
612b7765b0aSShiju Jose sizeof(struct aer_capability_regs));
613b7765b0aSShiju Jose if (!aer_info)
614b7765b0aSShiju Jose return;
615b7765b0aSShiju Jose memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs));
616b7765b0aSShiju Jose
617a654e5eeSHuang Ying aer_recover_queue(pcie_err->device_id.segment,
618a654e5eeSHuang Ying pcie_err->device_id.bus,
61937448adfSLance Ortiz devfn, aer_severity,
62037448adfSLance Ortiz (struct aer_capability_regs *)
621b7765b0aSShiju Jose aer_info);
622a654e5eeSHuang Ying }
623a654e5eeSHuang Ying #endif
6243c5b977fSTyler Baicar }
6253c5b977fSTyler Baicar
6269aa9cf3eSShiju Jose static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list);
6279aa9cf3eSShiju Jose
ghes_register_vendor_record_notifier(struct notifier_block * nb)6289aa9cf3eSShiju Jose int ghes_register_vendor_record_notifier(struct notifier_block *nb)
6299aa9cf3eSShiju Jose {
6309aa9cf3eSShiju Jose return blocking_notifier_chain_register(&vendor_record_notify_list, nb);
6319aa9cf3eSShiju Jose }
6329aa9cf3eSShiju Jose EXPORT_SYMBOL_GPL(ghes_register_vendor_record_notifier);
6339aa9cf3eSShiju Jose
ghes_unregister_vendor_record_notifier(struct notifier_block * nb)6349aa9cf3eSShiju Jose void ghes_unregister_vendor_record_notifier(struct notifier_block *nb)
6359aa9cf3eSShiju Jose {
6369aa9cf3eSShiju Jose blocking_notifier_chain_unregister(&vendor_record_notify_list, nb);
6379aa9cf3eSShiju Jose }
6389aa9cf3eSShiju Jose EXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier);
6399aa9cf3eSShiju Jose
ghes_vendor_record_work_func(struct work_struct * work)6409aa9cf3eSShiju Jose static void ghes_vendor_record_work_func(struct work_struct *work)
6419aa9cf3eSShiju Jose {
6429aa9cf3eSShiju Jose struct ghes_vendor_record_entry *entry;
6439aa9cf3eSShiju Jose struct acpi_hest_generic_data *gdata;
6449aa9cf3eSShiju Jose u32 len;
6459aa9cf3eSShiju Jose
6469aa9cf3eSShiju Jose entry = container_of(work, struct ghes_vendor_record_entry, work);
6479aa9cf3eSShiju Jose gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry);
6489aa9cf3eSShiju Jose
6499aa9cf3eSShiju Jose blocking_notifier_call_chain(&vendor_record_notify_list,
6509aa9cf3eSShiju Jose entry->error_severity, gdata);
6519aa9cf3eSShiju Jose
6529aa9cf3eSShiju Jose len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata));
6539aa9cf3eSShiju Jose gen_pool_free(ghes_estatus_pool, (unsigned long)entry, len);
6549aa9cf3eSShiju Jose }
6559aa9cf3eSShiju Jose
ghes_defer_non_standard_event(struct acpi_hest_generic_data * gdata,int sev)6569aa9cf3eSShiju Jose static void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
6579aa9cf3eSShiju Jose int sev)
6589aa9cf3eSShiju Jose {
6599aa9cf3eSShiju Jose struct acpi_hest_generic_data *copied_gdata;
6609aa9cf3eSShiju Jose struct ghes_vendor_record_entry *entry;
6619aa9cf3eSShiju Jose u32 len;
6629aa9cf3eSShiju Jose
6639aa9cf3eSShiju Jose len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata));
6649aa9cf3eSShiju Jose entry = (void *)gen_pool_alloc(ghes_estatus_pool, len);
6659aa9cf3eSShiju Jose if (!entry)
6669aa9cf3eSShiju Jose return;
6679aa9cf3eSShiju Jose
6689aa9cf3eSShiju Jose copied_gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry);
6699aa9cf3eSShiju Jose memcpy(copied_gdata, gdata, acpi_hest_get_record_size(gdata));
6709aa9cf3eSShiju Jose entry->error_severity = sev;
6719aa9cf3eSShiju Jose
6729aa9cf3eSShiju Jose INIT_WORK(&entry->work, ghes_vendor_record_work_func);
6739aa9cf3eSShiju Jose schedule_work(&entry->work);
6749aa9cf3eSShiju Jose }
6759aa9cf3eSShiju Jose
ghes_do_proc(struct ghes * ghes,const struct acpi_hest_generic_status * estatus)6767f17b4a1SJames Morse static bool ghes_do_proc(struct ghes *ghes,
6773c5b977fSTyler Baicar const struct acpi_hest_generic_status *estatus)
6783c5b977fSTyler Baicar {
6793c5b977fSTyler Baicar int sev, sec_sev;
6803c5b977fSTyler Baicar struct acpi_hest_generic_data *gdata;
6813c5b977fSTyler Baicar guid_t *sec_type;
682bb100b64SAndy Shevchenko const guid_t *fru_id = &guid_null;
6833c5b977fSTyler Baicar char *fru_text = "";
6847f17b4a1SJames Morse bool queued = false;
685*410063c9SShuai Xue bool sync = is_hest_sync_notify(ghes);
6863c5b977fSTyler Baicar
6873c5b977fSTyler Baicar sev = ghes_severity(estatus->error_severity);
6883c5b977fSTyler Baicar apei_estatus_for_each_section(estatus, gdata) {
6893c5b977fSTyler Baicar sec_type = (guid_t *)gdata->section_type;
6903c5b977fSTyler Baicar sec_sev = ghes_severity(gdata->error_severity);
6913c5b977fSTyler Baicar if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
6923c5b977fSTyler Baicar fru_id = (guid_t *)gdata->fru_id;
6933c5b977fSTyler Baicar
6943c5b977fSTyler Baicar if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
6953c5b977fSTyler Baicar fru_text = gdata->fru_text;
6963c5b977fSTyler Baicar
6973c5b977fSTyler Baicar if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
6983c5b977fSTyler Baicar struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
6993c5b977fSTyler Baicar
7008e40612fSJia He atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err);
7013c5b977fSTyler Baicar
7023c5b977fSTyler Baicar arch_apei_report_mem_error(sev, mem_err);
703*410063c9SShuai Xue queued = ghes_handle_memory_failure(gdata, sev, sync);
7043c5b977fSTyler Baicar }
7053c5b977fSTyler Baicar else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
7069852ce9aSTyler Baicar ghes_handle_aer(gdata);
7073c5b977fSTyler Baicar }
708e9279e83STyler Baicar else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) {
709*410063c9SShuai Xue queued = ghes_handle_arm_hw_error(gdata, sev, sync);
710e9279e83STyler Baicar } else {
711297b64c7STyler Baicar void *err = acpi_hest_get_payload(gdata);
712297b64c7STyler Baicar
7139aa9cf3eSShiju Jose ghes_defer_non_standard_event(gdata, sev);
714297b64c7STyler Baicar log_non_standard_event(sec_type, fru_id, fru_text,
715297b64c7STyler Baicar sec_sev, err,
716297b64c7STyler Baicar gdata->error_data_length);
717297b64c7STyler Baicar }
71832c361f5SHuang Ying }
7197f17b4a1SJames Morse
7207f17b4a1SJames Morse return queued;
721ba61ca4aSHuang Ying }
722d334a491SHuang Ying
__ghes_print_estatus(const char * pfx,const struct acpi_hest_generic * generic,const struct acpi_hest_generic_status * estatus)72367eb2e99SHuang Ying static void __ghes_print_estatus(const char *pfx,
72467eb2e99SHuang Ying const struct acpi_hest_generic *generic,
7250a00fd5eSLv Zheng const struct acpi_hest_generic_status *estatus)
72632c361f5SHuang Ying {
7275ba82ab5SHuang Ying static atomic_t seqno;
7285ba82ab5SHuang Ying unsigned int curr_seqno;
7295ba82ab5SHuang Ying char pfx_seq[64];
7305ba82ab5SHuang Ying
73132c361f5SHuang Ying if (pfx == NULL) {
73267eb2e99SHuang Ying if (ghes_severity(estatus->error_severity) <=
73332c361f5SHuang Ying GHES_SEV_CORRECTED)
7345ba82ab5SHuang Ying pfx = KERN_WARNING;
73532c361f5SHuang Ying else
7365ba82ab5SHuang Ying pfx = KERN_ERR;
73732c361f5SHuang Ying }
7385ba82ab5SHuang Ying curr_seqno = atomic_inc_return(&seqno);
7395ba82ab5SHuang Ying snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno);
7405588340dSHuang Ying printk("%s""Hardware error from APEI Generic Hardware Error Source: %d\n",
7415ba82ab5SHuang Ying pfx_seq, generic->header.source_id);
74288f074f4SChen, Gong cper_estatus_print(pfx_seq, estatus);
74332c361f5SHuang Ying }
7445588340dSHuang Ying
ghes_print_estatus(const char * pfx,const struct acpi_hest_generic * generic,const struct acpi_hest_generic_status * estatus)745152cef40SHuang Ying static int ghes_print_estatus(const char *pfx,
74667eb2e99SHuang Ying const struct acpi_hest_generic *generic,
7470a00fd5eSLv Zheng const struct acpi_hest_generic_status *estatus)
7485588340dSHuang Ying {
7495588340dSHuang Ying /* Not more than 2 messages every 5 seconds */
75067eb2e99SHuang Ying static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5*HZ, 2);
75167eb2e99SHuang Ying static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5*HZ, 2);
75267eb2e99SHuang Ying struct ratelimit_state *ratelimit;
7535588340dSHuang Ying
75467eb2e99SHuang Ying if (ghes_severity(estatus->error_severity) <= GHES_SEV_CORRECTED)
75567eb2e99SHuang Ying ratelimit = &ratelimit_corrected;
75667eb2e99SHuang Ying else
75767eb2e99SHuang Ying ratelimit = &ratelimit_uncorrected;
758152cef40SHuang Ying if (__ratelimit(ratelimit)) {
75967eb2e99SHuang Ying __ghes_print_estatus(pfx, generic, estatus);
760152cef40SHuang Ying return 1;
761152cef40SHuang Ying }
762152cef40SHuang Ying return 0;
763152cef40SHuang Ying }
764152cef40SHuang Ying
765152cef40SHuang Ying /*
766152cef40SHuang Ying * GHES error status reporting throttle, to report more kinds of
767152cef40SHuang Ying * errors, instead of just most frequently occurred errors.
768152cef40SHuang Ying */
ghes_estatus_cached(struct acpi_hest_generic_status * estatus)7690a00fd5eSLv Zheng static int ghes_estatus_cached(struct acpi_hest_generic_status *estatus)
770152cef40SHuang Ying {
771152cef40SHuang Ying u32 len;
772152cef40SHuang Ying int i, cached = 0;
773152cef40SHuang Ying unsigned long long now;
774152cef40SHuang Ying struct ghes_estatus_cache *cache;
7750a00fd5eSLv Zheng struct acpi_hest_generic_status *cache_estatus;
776152cef40SHuang Ying
77788f074f4SChen, Gong len = cper_estatus_len(estatus);
778152cef40SHuang Ying rcu_read_lock();
779152cef40SHuang Ying for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
780152cef40SHuang Ying cache = rcu_dereference(ghes_estatus_caches[i]);
781152cef40SHuang Ying if (cache == NULL)
782152cef40SHuang Ying continue;
783152cef40SHuang Ying if (len != cache->estatus_len)
784152cef40SHuang Ying continue;
785152cef40SHuang Ying cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
786152cef40SHuang Ying if (memcmp(estatus, cache_estatus, len))
787152cef40SHuang Ying continue;
788152cef40SHuang Ying atomic_inc(&cache->count);
789152cef40SHuang Ying now = sched_clock();
790152cef40SHuang Ying if (now - cache->time_in < GHES_ESTATUS_IN_CACHE_MAX_NSEC)
791152cef40SHuang Ying cached = 1;
792152cef40SHuang Ying break;
793152cef40SHuang Ying }
794152cef40SHuang Ying rcu_read_unlock();
795152cef40SHuang Ying return cached;
796152cef40SHuang Ying }
797152cef40SHuang Ying
ghes_estatus_cache_alloc(struct acpi_hest_generic * generic,struct acpi_hest_generic_status * estatus)798152cef40SHuang Ying static struct ghes_estatus_cache *ghes_estatus_cache_alloc(
799152cef40SHuang Ying struct acpi_hest_generic *generic,
8000a00fd5eSLv Zheng struct acpi_hest_generic_status *estatus)
801152cef40SHuang Ying {
802152cef40SHuang Ying int alloced;
803152cef40SHuang Ying u32 len, cache_len;
804152cef40SHuang Ying struct ghes_estatus_cache *cache;
8050a00fd5eSLv Zheng struct acpi_hest_generic_status *cache_estatus;
806152cef40SHuang Ying
807152cef40SHuang Ying alloced = atomic_add_return(1, &ghes_estatus_cache_alloced);
808152cef40SHuang Ying if (alloced > GHES_ESTATUS_CACHE_ALLOCED_MAX) {
809152cef40SHuang Ying atomic_dec(&ghes_estatus_cache_alloced);
810152cef40SHuang Ying return NULL;
811152cef40SHuang Ying }
81288f074f4SChen, Gong len = cper_estatus_len(estatus);
813152cef40SHuang Ying cache_len = GHES_ESTATUS_CACHE_LEN(len);
814152cef40SHuang Ying cache = (void *)gen_pool_alloc(ghes_estatus_pool, cache_len);
815152cef40SHuang Ying if (!cache) {
816152cef40SHuang Ying atomic_dec(&ghes_estatus_cache_alloced);
817152cef40SHuang Ying return NULL;
818152cef40SHuang Ying }
819152cef40SHuang Ying cache_estatus = GHES_ESTATUS_FROM_CACHE(cache);
820152cef40SHuang Ying memcpy(cache_estatus, estatus, len);
821152cef40SHuang Ying cache->estatus_len = len;
822152cef40SHuang Ying atomic_set(&cache->count, 0);
823152cef40SHuang Ying cache->generic = generic;
824152cef40SHuang Ying cache->time_in = sched_clock();
825152cef40SHuang Ying return cache;
826152cef40SHuang Ying }
827152cef40SHuang Ying
ghes_estatus_cache_rcu_free(struct rcu_head * head)828dd3fa54bSArd Biesheuvel static void ghes_estatus_cache_rcu_free(struct rcu_head *head)
829152cef40SHuang Ying {
830dd3fa54bSArd Biesheuvel struct ghes_estatus_cache *cache;
831152cef40SHuang Ying u32 len;
832152cef40SHuang Ying
833dd3fa54bSArd Biesheuvel cache = container_of(head, struct ghes_estatus_cache, rcu);
83488f074f4SChen, Gong len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache));
835152cef40SHuang Ying len = GHES_ESTATUS_CACHE_LEN(len);
836152cef40SHuang Ying gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len);
837152cef40SHuang Ying atomic_dec(&ghes_estatus_cache_alloced);
838152cef40SHuang Ying }
839152cef40SHuang Ying
840dd3fa54bSArd Biesheuvel static void
ghes_estatus_cache_add(struct acpi_hest_generic * generic,struct acpi_hest_generic_status * estatus)841dd3fa54bSArd Biesheuvel ghes_estatus_cache_add(struct acpi_hest_generic *generic,
8420a00fd5eSLv Zheng struct acpi_hest_generic_status *estatus)
843152cef40SHuang Ying {
844152cef40SHuang Ying unsigned long long now, duration, period, max_period = 0;
845dd3fa54bSArd Biesheuvel struct ghes_estatus_cache *cache, *new_cache;
846dd3fa54bSArd Biesheuvel struct ghes_estatus_cache __rcu *victim;
847dd3fa54bSArd Biesheuvel int i, slot = -1, count;
848152cef40SHuang Ying
849152cef40SHuang Ying new_cache = ghes_estatus_cache_alloc(generic, estatus);
850dd3fa54bSArd Biesheuvel if (!new_cache)
851152cef40SHuang Ying return;
852dd3fa54bSArd Biesheuvel
853152cef40SHuang Ying rcu_read_lock();
854152cef40SHuang Ying now = sched_clock();
855152cef40SHuang Ying for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
856152cef40SHuang Ying cache = rcu_dereference(ghes_estatus_caches[i]);
857152cef40SHuang Ying if (cache == NULL) {
858152cef40SHuang Ying slot = i;
859152cef40SHuang Ying break;
860152cef40SHuang Ying }
861152cef40SHuang Ying duration = now - cache->time_in;
862152cef40SHuang Ying if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) {
863152cef40SHuang Ying slot = i;
864152cef40SHuang Ying break;
865152cef40SHuang Ying }
866152cef40SHuang Ying count = atomic_read(&cache->count);
86770cb6e1dSLen Brown period = duration;
86870cb6e1dSLen Brown do_div(period, (count + 1));
869152cef40SHuang Ying if (period > max_period) {
870152cef40SHuang Ying max_period = period;
871152cef40SHuang Ying slot = i;
872152cef40SHuang Ying }
873152cef40SHuang Ying }
874152cef40SHuang Ying rcu_read_unlock();
875dd3fa54bSArd Biesheuvel
876dd3fa54bSArd Biesheuvel if (slot != -1) {
877dd3fa54bSArd Biesheuvel /*
878dd3fa54bSArd Biesheuvel * Use release semantics to ensure that ghes_estatus_cached()
879dd3fa54bSArd Biesheuvel * running on another CPU will see the updated cache fields if
880dd3fa54bSArd Biesheuvel * it can see the new value of the pointer.
881dd3fa54bSArd Biesheuvel */
882dd3fa54bSArd Biesheuvel victim = xchg_release(&ghes_estatus_caches[slot],
883dd3fa54bSArd Biesheuvel RCU_INITIALIZER(new_cache));
884dd3fa54bSArd Biesheuvel
885dd3fa54bSArd Biesheuvel /*
886dd3fa54bSArd Biesheuvel * At this point, victim may point to a cached item different
887dd3fa54bSArd Biesheuvel * from the one based on which we selected the slot. Instead of
888dd3fa54bSArd Biesheuvel * going to the loop again to pick another slot, let's just
889dd3fa54bSArd Biesheuvel * drop the other item anyway: this may cause a false cache
890dd3fa54bSArd Biesheuvel * miss later on, but that won't cause any problems.
891dd3fa54bSArd Biesheuvel */
892dd3fa54bSArd Biesheuvel if (victim)
893dd3fa54bSArd Biesheuvel call_rcu(&unrcu_pointer(victim)->rcu,
894dd3fa54bSArd Biesheuvel ghes_estatus_cache_rcu_free);
895dd3fa54bSArd Biesheuvel }
896d334a491SHuang Ying }
897d334a491SHuang Ying
__ghes_panic(struct ghes * ghes,struct acpi_hest_generic_status * estatus,u64 buf_paddr,enum fixed_addresses fixmap_idx)898f2a7e059SJames Morse static void __ghes_panic(struct ghes *ghes,
899f2a7e059SJames Morse struct acpi_hest_generic_status *estatus,
900f2a7e059SJames Morse u64 buf_paddr, enum fixed_addresses fixmap_idx)
9012fb5853eSJonathan (Zhixiong) Zhang {
902f2a7e059SJames Morse __ghes_print_estatus(KERN_EMERG, ghes->generic, estatus);
9032fb5853eSJonathan (Zhixiong) Zhang
904f2a7e059SJames Morse ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
90598cff8b2SLenny Szubowicz
9062fb5853eSJonathan (Zhixiong) Zhang /* reboot to log the error! */
9072fb5853eSJonathan (Zhixiong) Zhang if (!panic_timeout)
9082fb5853eSJonathan (Zhixiong) Zhang panic_timeout = ghes_panic_timeout;
9092fb5853eSJonathan (Zhixiong) Zhang panic("Fatal hardware error!");
9102fb5853eSJonathan (Zhixiong) Zhang }
9112fb5853eSJonathan (Zhixiong) Zhang
ghes_proc(struct ghes * ghes)912d334a491SHuang Ying static int ghes_proc(struct ghes *ghes)
913d334a491SHuang Ying {
914f2a7e059SJames Morse struct acpi_hest_generic_status *estatus = ghes->estatus;
915eeb25557SJames Morse u64 buf_paddr;
916d334a491SHuang Ying int rc;
917d334a491SHuang Ying
918f2a7e059SJames Morse rc = ghes_read_estatus(ghes, estatus, &buf_paddr, FIX_APEI_GHES_IRQ);
919d334a491SHuang Ying if (rc)
920d334a491SHuang Ying goto out;
9212fb5853eSJonathan (Zhixiong) Zhang
922f2a7e059SJames Morse if (ghes_severity(estatus->error_severity) >= GHES_SEV_PANIC)
923f2a7e059SJames Morse __ghes_panic(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ);
9242fb5853eSJonathan (Zhixiong) Zhang
925f2a7e059SJames Morse if (!ghes_estatus_cached(estatus)) {
926f2a7e059SJames Morse if (ghes_print_estatus(NULL, ghes->generic, estatus))
927f2a7e059SJames Morse ghes_estatus_cache_add(ghes->generic, estatus);
928152cef40SHuang Ying }
929f2a7e059SJames Morse ghes_do_proc(ghes, estatus);
93042aa5604STyler Baicar
931aaf2c2fbSTyler Baicar out:
932f2a7e059SJames Morse ghes_clear_estatus(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ);
933aaf2c2fbSTyler Baicar
934806487a8SPunit Agrawal return rc;
935d334a491SHuang Ying }
936d334a491SHuang Ying
ghes_add_timer(struct ghes * ghes)93781e88fdcSHuang Ying static void ghes_add_timer(struct ghes *ghes)
93881e88fdcSHuang Ying {
93981e88fdcSHuang Ying struct acpi_hest_generic *g = ghes->generic;
94081e88fdcSHuang Ying unsigned long expire;
94181e88fdcSHuang Ying
94281e88fdcSHuang Ying if (!g->notify.poll_interval) {
943933ca4e3SKefeng Wang pr_warn(FW_WARN GHES_PFX "Poll interval is 0 for generic hardware error source: %d, disabled.\n",
94481e88fdcSHuang Ying g->header.source_id);
94581e88fdcSHuang Ying return;
94681e88fdcSHuang Ying }
94781e88fdcSHuang Ying expire = jiffies + msecs_to_jiffies(g->notify.poll_interval);
94881e88fdcSHuang Ying ghes->timer.expires = round_jiffies_relative(expire);
94981e88fdcSHuang Ying add_timer(&ghes->timer);
95081e88fdcSHuang Ying }
95181e88fdcSHuang Ying
ghes_poll_func(struct timer_list * t)952d5272003SKees Cook static void ghes_poll_func(struct timer_list *t)
95381e88fdcSHuang Ying {
954d5272003SKees Cook struct ghes *ghes = from_timer(ghes, t, timer);
9553b880cbeSJames Morse unsigned long flags;
95681e88fdcSHuang Ying
9573b880cbeSJames Morse spin_lock_irqsave(&ghes_notify_lock_irq, flags);
95881e88fdcSHuang Ying ghes_proc(ghes);
9593b880cbeSJames Morse spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
96081e88fdcSHuang Ying if (!(ghes->flags & GHES_EXITING))
96181e88fdcSHuang Ying ghes_add_timer(ghes);
96281e88fdcSHuang Ying }
96381e88fdcSHuang Ying
ghes_irq_func(int irq,void * data)96481e88fdcSHuang Ying static irqreturn_t ghes_irq_func(int irq, void *data)
96581e88fdcSHuang Ying {
96681e88fdcSHuang Ying struct ghes *ghes = data;
9673b880cbeSJames Morse unsigned long flags;
96881e88fdcSHuang Ying int rc;
96981e88fdcSHuang Ying
9703b880cbeSJames Morse spin_lock_irqsave(&ghes_notify_lock_irq, flags);
97181e88fdcSHuang Ying rc = ghes_proc(ghes);
9723b880cbeSJames Morse spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
97381e88fdcSHuang Ying if (rc)
97481e88fdcSHuang Ying return IRQ_NONE;
97581e88fdcSHuang Ying
97681e88fdcSHuang Ying return IRQ_HANDLED;
97781e88fdcSHuang Ying }
97881e88fdcSHuang Ying
ghes_notify_hed(struct notifier_block * this,unsigned long event,void * data)9797bf130e4SShiju Jose static int ghes_notify_hed(struct notifier_block *this, unsigned long event,
9807bf130e4SShiju Jose void *data)
981d334a491SHuang Ying {
982d334a491SHuang Ying struct ghes *ghes;
9833b880cbeSJames Morse unsigned long flags;
984d334a491SHuang Ying int ret = NOTIFY_DONE;
985d334a491SHuang Ying
9863b880cbeSJames Morse spin_lock_irqsave(&ghes_notify_lock_irq, flags);
987d334a491SHuang Ying rcu_read_lock();
9887bf130e4SShiju Jose list_for_each_entry_rcu(ghes, &ghes_hed, list) {
989d334a491SHuang Ying if (!ghes_proc(ghes))
990d334a491SHuang Ying ret = NOTIFY_OK;
991d334a491SHuang Ying }
992d334a491SHuang Ying rcu_read_unlock();
9933b880cbeSJames Morse spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
994d334a491SHuang Ying
995d334a491SHuang Ying return ret;
996d334a491SHuang Ying }
997d334a491SHuang Ying
9987bf130e4SShiju Jose static struct notifier_block ghes_notifier_hed = {
9997bf130e4SShiju Jose .notifier_call = ghes_notify_hed,
100044a69f61STomasz Nowicki };
100144a69f61STomasz Nowicki
100244a69f61STomasz Nowicki /*
10039c9d0805SJames Morse * Handlers for CPER records may not be NMI safe. For example,
10049c9d0805SJames Morse * memory_failure_queue() takes spinlocks and calls schedule_work_on().
10059c9d0805SJames Morse * In any NMI-like handler, memory from ghes_estatus_pool is used to save
10069c9d0805SJames Morse * estatus, and added to the ghes_estatus_llist. irq_work_queue() causes
10079c9d0805SJames Morse * ghes_proc_in_irq() to run in IRQ context where each estatus in
10089c9d0805SJames Morse * ghes_estatus_llist is processed.
10099c9d0805SJames Morse *
10109c9d0805SJames Morse * Memory from the ghes_estatus_pool is also used with the ghes_estatus_cache
10119c9d0805SJames Morse * to suppress frequent messages.
101244a69f61STomasz Nowicki */
101344a69f61STomasz Nowicki static struct llist_head ghes_estatus_llist;
101444a69f61STomasz Nowicki static struct irq_work ghes_proc_irq_work;
101544a69f61STomasz Nowicki
ghes_proc_in_irq(struct irq_work * irq_work)101646d12f0bSHuang Ying static void ghes_proc_in_irq(struct irq_work *irq_work)
101746d12f0bSHuang Ying {
101846d12f0bSHuang Ying struct llist_node *llnode, *next;
101946d12f0bSHuang Ying struct ghes_estatus_node *estatus_node;
102046d12f0bSHuang Ying struct acpi_hest_generic *generic;
10210a00fd5eSLv Zheng struct acpi_hest_generic_status *estatus;
10227f17b4a1SJames Morse bool task_work_pending;
102346d12f0bSHuang Ying u32 len, node_len;
10247f17b4a1SJames Morse int ret;
102546d12f0bSHuang Ying
102646d12f0bSHuang Ying llnode = llist_del_all(&ghes_estatus_llist);
102746d12f0bSHuang Ying /*
102846d12f0bSHuang Ying * Because the time order of estatus in list is reversed,
102946d12f0bSHuang Ying * revert it back to proper order.
103046d12f0bSHuang Ying */
10318d21d4c9SChen, Gong llnode = llist_reverse_order(llnode);
103267eb2e99SHuang Ying while (llnode) {
103367eb2e99SHuang Ying next = llnode->next;
103467eb2e99SHuang Ying estatus_node = llist_entry(llnode, struct ghes_estatus_node,
103567eb2e99SHuang Ying llnode);
103667eb2e99SHuang Ying estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
103788f074f4SChen, Gong len = cper_estatus_len(estatus);
103867eb2e99SHuang Ying node_len = GHES_ESTATUS_NODE_LEN(len);
10397f17b4a1SJames Morse task_work_pending = ghes_do_proc(estatus_node->ghes, estatus);
1040152cef40SHuang Ying if (!ghes_estatus_cached(estatus)) {
1041152cef40SHuang Ying generic = estatus_node->generic;
1042152cef40SHuang Ying if (ghes_print_estatus(NULL, generic, estatus))
1043152cef40SHuang Ying ghes_estatus_cache_add(generic, estatus);
1044152cef40SHuang Ying }
10457f17b4a1SJames Morse
1046415fed69SShuai Xue if (task_work_pending && current->mm) {
10477f17b4a1SJames Morse estatus_node->task_work.func = ghes_kick_task_work;
10487f17b4a1SJames Morse estatus_node->task_work_cpu = smp_processor_id();
10497f17b4a1SJames Morse ret = task_work_add(current, &estatus_node->task_work,
105091989c70SJens Axboe TWA_RESUME);
10517f17b4a1SJames Morse if (ret)
10527f17b4a1SJames Morse estatus_node->task_work.func = NULL;
10537f17b4a1SJames Morse }
10547f17b4a1SJames Morse
10557f17b4a1SJames Morse if (!estatus_node->task_work.func)
10567f17b4a1SJames Morse gen_pool_free(ghes_estatus_pool,
10577f17b4a1SJames Morse (unsigned long)estatus_node, node_len);
10587f17b4a1SJames Morse
105967eb2e99SHuang Ying llnode = next;
106067eb2e99SHuang Ying }
106167eb2e99SHuang Ying }
106267eb2e99SHuang Ying
ghes_print_queued_estatus(void)106346d12f0bSHuang Ying static void ghes_print_queued_estatus(void)
106446d12f0bSHuang Ying {
106546d12f0bSHuang Ying struct llist_node *llnode;
106646d12f0bSHuang Ying struct ghes_estatus_node *estatus_node;
106746d12f0bSHuang Ying struct acpi_hest_generic *generic;
10680a00fd5eSLv Zheng struct acpi_hest_generic_status *estatus;
106946d12f0bSHuang Ying
107046d12f0bSHuang Ying llnode = llist_del_all(&ghes_estatus_llist);
107146d12f0bSHuang Ying /*
107246d12f0bSHuang Ying * Because the time order of estatus in list is reversed,
107346d12f0bSHuang Ying * revert it back to proper order.
107446d12f0bSHuang Ying */
10758d21d4c9SChen, Gong llnode = llist_reverse_order(llnode);
107646d12f0bSHuang Ying while (llnode) {
107746d12f0bSHuang Ying estatus_node = llist_entry(llnode, struct ghes_estatus_node,
107846d12f0bSHuang Ying llnode);
107946d12f0bSHuang Ying estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
108046d12f0bSHuang Ying generic = estatus_node->generic;
108146d12f0bSHuang Ying ghes_print_estatus(NULL, generic, estatus);
108246d12f0bSHuang Ying llnode = llnode->next;
108346d12f0bSHuang Ying }
108446d12f0bSHuang Ying }
108546d12f0bSHuang Ying
ghes_in_nmi_queue_one_entry(struct ghes * ghes,enum fixed_addresses fixmap_idx)1086d9f608dcSJames Morse static int ghes_in_nmi_queue_one_entry(struct ghes *ghes,
1087d9f608dcSJames Morse enum fixed_addresses fixmap_idx)
108811568496SBorislav Petkov {
1089d9f608dcSJames Morse struct acpi_hest_generic_status *estatus, tmp_header;
109011568496SBorislav Petkov struct ghes_estatus_node *estatus_node;
1091d9f608dcSJames Morse u32 len, node_len;
1092d9f608dcSJames Morse u64 buf_paddr;
1093d9f608dcSJames Morse int sev, rc;
109411568496SBorislav Petkov
1095f2a7e059SJames Morse if (!IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG))
1096d9f608dcSJames Morse return -EOPNOTSUPP;
109711568496SBorislav Petkov
1098d9f608dcSJames Morse rc = __ghes_peek_estatus(ghes, &tmp_header, &buf_paddr, fixmap_idx);
1099d9f608dcSJames Morse if (rc) {
1100d9f608dcSJames Morse ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
1101d9f608dcSJames Morse return rc;
1102d9f608dcSJames Morse }
1103f2a7e059SJames Morse
1104d9f608dcSJames Morse rc = __ghes_check_estatus(ghes, &tmp_header);
1105d9f608dcSJames Morse if (rc) {
1106d9f608dcSJames Morse ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
1107d9f608dcSJames Morse return rc;
1108d9f608dcSJames Morse }
1109d9f608dcSJames Morse
1110d9f608dcSJames Morse len = cper_estatus_len(&tmp_header);
111111568496SBorislav Petkov node_len = GHES_ESTATUS_NODE_LEN(len);
111211568496SBorislav Petkov estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len);
111311568496SBorislav Petkov if (!estatus_node)
1114d9f608dcSJames Morse return -ENOMEM;
111511568496SBorislav Petkov
111611568496SBorislav Petkov estatus_node->ghes = ghes;
111711568496SBorislav Petkov estatus_node->generic = ghes->generic;
11187f17b4a1SJames Morse estatus_node->task_work.func = NULL;
111911568496SBorislav Petkov estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
112011568496SBorislav Petkov
1121d9f608dcSJames Morse if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) {
1122f2a7e059SJames Morse ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
1123d9f608dcSJames Morse rc = -ENOENT;
1124d9f608dcSJames Morse goto no_work;
112581e88fdcSHuang Ying }
11266169ddf8SBorislav Petkov
1127f2a7e059SJames Morse sev = ghes_severity(estatus->error_severity);
11282fb5853eSJonathan (Zhixiong) Zhang if (sev >= GHES_SEV_PANIC) {
11292fb5853eSJonathan (Zhixiong) Zhang ghes_print_queued_estatus();
1130f2a7e059SJames Morse __ghes_panic(ghes, estatus, buf_paddr, fixmap_idx);
11312fb5853eSJonathan (Zhixiong) Zhang }
11326169ddf8SBorislav Petkov
1133d9f608dcSJames Morse ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx);
1134ee2eb3d4SJames Morse
1135d9f608dcSJames Morse /* This error has been reported before, don't process it again. */
1136d9f608dcSJames Morse if (ghes_estatus_cached(estatus))
1137d9f608dcSJames Morse goto no_work;
1138d9f608dcSJames Morse
1139d9f608dcSJames Morse llist_add(&estatus_node->llnode, &ghes_estatus_llist);
1140d9f608dcSJames Morse
1141d9f608dcSJames Morse return rc;
1142d9f608dcSJames Morse
1143d9f608dcSJames Morse no_work:
1144d9f608dcSJames Morse gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
1145d9f608dcSJames Morse node_len);
1146d9f608dcSJames Morse
1147d9f608dcSJames Morse return rc;
114881e88fdcSHuang Ying }
114911568496SBorislav Petkov
ghes_in_nmi_spool_from_list(struct list_head * rcu_list,enum fixed_addresses fixmap_idx)1150b484079bSJames Morse static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
1151b484079bSJames Morse enum fixed_addresses fixmap_idx)
1152ee2eb3d4SJames Morse {
1153ee2eb3d4SJames Morse int ret = -ENOENT;
1154ee2eb3d4SJames Morse struct ghes *ghes;
1155ee2eb3d4SJames Morse
1156ee2eb3d4SJames Morse rcu_read_lock();
1157ee2eb3d4SJames Morse list_for_each_entry_rcu(ghes, rcu_list, list) {
1158b484079bSJames Morse if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx))
1159ee2eb3d4SJames Morse ret = 0;
1160ee2eb3d4SJames Morse }
1161ee2eb3d4SJames Morse rcu_read_unlock();
1162ee2eb3d4SJames Morse
1163ee2eb3d4SJames Morse if (IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG) && !ret)
116467eb2e99SHuang Ying irq_work_queue(&ghes_proc_irq_work);
1165ee2eb3d4SJames Morse
1166ee2eb3d4SJames Morse return ret;
1167ee2eb3d4SJames Morse }
11689c9d0805SJames Morse
11699c9d0805SJames Morse #ifdef CONFIG_ACPI_APEI_SEA
11709c9d0805SJames Morse static LIST_HEAD(ghes_sea);
11719c9d0805SJames Morse
11729c9d0805SJames Morse /*
11739c9d0805SJames Morse * Return 0 only if one of the SEA error sources successfully reported an error
11749c9d0805SJames Morse * record sent from the firmware.
11759c9d0805SJames Morse */
ghes_notify_sea(void)11769c9d0805SJames Morse int ghes_notify_sea(void)
11779c9d0805SJames Morse {
11783b880cbeSJames Morse static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sea);
11793b880cbeSJames Morse int rv;
11803b880cbeSJames Morse
11813b880cbeSJames Morse raw_spin_lock(&ghes_notify_lock_sea);
1182b972d2eaSJames Morse rv = ghes_in_nmi_spool_from_list(&ghes_sea, FIX_APEI_GHES_SEA);
11833b880cbeSJames Morse raw_spin_unlock(&ghes_notify_lock_sea);
11843b880cbeSJames Morse
11853b880cbeSJames Morse return rv;
11869c9d0805SJames Morse }
11879c9d0805SJames Morse
ghes_sea_add(struct ghes * ghes)11889c9d0805SJames Morse static void ghes_sea_add(struct ghes *ghes)
11899c9d0805SJames Morse {
11909c9d0805SJames Morse mutex_lock(&ghes_list_mutex);
11919c9d0805SJames Morse list_add_rcu(&ghes->list, &ghes_sea);
11929c9d0805SJames Morse mutex_unlock(&ghes_list_mutex);
11939c9d0805SJames Morse }
11949c9d0805SJames Morse
ghes_sea_remove(struct ghes * ghes)11959c9d0805SJames Morse static void ghes_sea_remove(struct ghes *ghes)
11969c9d0805SJames Morse {
11979c9d0805SJames Morse mutex_lock(&ghes_list_mutex);
11989c9d0805SJames Morse list_del_rcu(&ghes->list);
11999c9d0805SJames Morse mutex_unlock(&ghes_list_mutex);
12009c9d0805SJames Morse synchronize_rcu();
12019c9d0805SJames Morse }
12029c9d0805SJames Morse #else /* CONFIG_ACPI_APEI_SEA */
ghes_sea_add(struct ghes * ghes)12039c9d0805SJames Morse static inline void ghes_sea_add(struct ghes *ghes) { }
ghes_sea_remove(struct ghes * ghes)12049c9d0805SJames Morse static inline void ghes_sea_remove(struct ghes *ghes) { }
12059c9d0805SJames Morse #endif /* CONFIG_ACPI_APEI_SEA */
12069c9d0805SJames Morse
12079c9d0805SJames Morse #ifdef CONFIG_HAVE_ACPI_APEI_NMI
12089c9d0805SJames Morse /*
12099c9d0805SJames Morse * NMI may be triggered on any CPU, so ghes_in_nmi is used for
12109c9d0805SJames Morse * having only one concurrent reader.
12119c9d0805SJames Morse */
12129c9d0805SJames Morse static atomic_t ghes_in_nmi = ATOMIC_INIT(0);
12139c9d0805SJames Morse
12149c9d0805SJames Morse static LIST_HEAD(ghes_nmi);
1215ee2eb3d4SJames Morse
ghes_notify_nmi(unsigned int cmd,struct pt_regs * regs)1216ee2eb3d4SJames Morse static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
1217ee2eb3d4SJames Morse {
12183b880cbeSJames Morse static DEFINE_RAW_SPINLOCK(ghes_notify_lock_nmi);
1219ee2eb3d4SJames Morse int ret = NMI_DONE;
1220ee2eb3d4SJames Morse
1221ee2eb3d4SJames Morse if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
1222ee2eb3d4SJames Morse return ret;
1223ee2eb3d4SJames Morse
12243b880cbeSJames Morse raw_spin_lock(&ghes_notify_lock_nmi);
1225b484079bSJames Morse if (!ghes_in_nmi_spool_from_list(&ghes_nmi, FIX_APEI_GHES_NMI))
1226ee2eb3d4SJames Morse ret = NMI_HANDLED;
12273b880cbeSJames Morse raw_spin_unlock(&ghes_notify_lock_nmi);
1228ee2eb3d4SJames Morse
12296fe9e7c2SJiri Kosina atomic_dec(&ghes_in_nmi);
123081e88fdcSHuang Ying return ret;
123181e88fdcSHuang Ying }
123281e88fdcSHuang Ying
ghes_nmi_add(struct ghes * ghes)123344a69f61STomasz Nowicki static void ghes_nmi_add(struct ghes *ghes)
123444a69f61STomasz Nowicki {
123544a69f61STomasz Nowicki mutex_lock(&ghes_list_mutex);
123644a69f61STomasz Nowicki if (list_empty(&ghes_nmi))
123744a69f61STomasz Nowicki register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes");
123844a69f61STomasz Nowicki list_add_rcu(&ghes->list, &ghes_nmi);
123944a69f61STomasz Nowicki mutex_unlock(&ghes_list_mutex);
124044a69f61STomasz Nowicki }
124144a69f61STomasz Nowicki
ghes_nmi_remove(struct ghes * ghes)124244a69f61STomasz Nowicki static void ghes_nmi_remove(struct ghes *ghes)
124344a69f61STomasz Nowicki {
124444a69f61STomasz Nowicki mutex_lock(&ghes_list_mutex);
124544a69f61STomasz Nowicki list_del_rcu(&ghes->list);
124644a69f61STomasz Nowicki if (list_empty(&ghes_nmi))
124744a69f61STomasz Nowicki unregister_nmi_handler(NMI_LOCAL, "ghes");
124844a69f61STomasz Nowicki mutex_unlock(&ghes_list_mutex);
124944a69f61STomasz Nowicki /*
125044a69f61STomasz Nowicki * To synchronize with NMI handler, ghes can only be
125144a69f61STomasz Nowicki * freed after NMI handler finishes.
125244a69f61STomasz Nowicki */
125344a69f61STomasz Nowicki synchronize_rcu();
125444a69f61STomasz Nowicki }
1255255097c8SJames Morse #else /* CONFIG_HAVE_ACPI_APEI_NMI */
ghes_nmi_add(struct ghes * ghes)1256255097c8SJames Morse static inline void ghes_nmi_add(struct ghes *ghes) { }
ghes_nmi_remove(struct ghes * ghes)1257255097c8SJames Morse static inline void ghes_nmi_remove(struct ghes *ghes) { }
1258255097c8SJames Morse #endif /* CONFIG_HAVE_ACPI_APEI_NMI */
125944a69f61STomasz Nowicki
ghes_nmi_init_cxt(void)126044a69f61STomasz Nowicki static void ghes_nmi_init_cxt(void)
126144a69f61STomasz Nowicki {
126244a69f61STomasz Nowicki init_irq_work(&ghes_proc_irq_work, ghes_proc_in_irq);
126344a69f61STomasz Nowicki }
126444a69f61STomasz Nowicki
__ghes_sdei_callback(struct ghes * ghes,enum fixed_addresses fixmap_idx)1265f9f05395SJames Morse static int __ghes_sdei_callback(struct ghes *ghes,
1266f9f05395SJames Morse enum fixed_addresses fixmap_idx)
1267f9f05395SJames Morse {
1268f9f05395SJames Morse if (!ghes_in_nmi_queue_one_entry(ghes, fixmap_idx)) {
1269f9f05395SJames Morse irq_work_queue(&ghes_proc_irq_work);
1270f9f05395SJames Morse
1271f9f05395SJames Morse return 0;
1272f9f05395SJames Morse }
1273f9f05395SJames Morse
1274f9f05395SJames Morse return -ENOENT;
1275f9f05395SJames Morse }
1276f9f05395SJames Morse
ghes_sdei_normal_callback(u32 event_num,struct pt_regs * regs,void * arg)1277f9f05395SJames Morse static int ghes_sdei_normal_callback(u32 event_num, struct pt_regs *regs,
1278f9f05395SJames Morse void *arg)
1279f9f05395SJames Morse {
1280f9f05395SJames Morse static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sdei_normal);
1281f9f05395SJames Morse struct ghes *ghes = arg;
1282f9f05395SJames Morse int err;
1283f9f05395SJames Morse
1284f9f05395SJames Morse raw_spin_lock(&ghes_notify_lock_sdei_normal);
1285f9f05395SJames Morse err = __ghes_sdei_callback(ghes, FIX_APEI_GHES_SDEI_NORMAL);
1286f9f05395SJames Morse raw_spin_unlock(&ghes_notify_lock_sdei_normal);
1287f9f05395SJames Morse
1288f9f05395SJames Morse return err;
1289f9f05395SJames Morse }
1290f9f05395SJames Morse
ghes_sdei_critical_callback(u32 event_num,struct pt_regs * regs,void * arg)1291f9f05395SJames Morse static int ghes_sdei_critical_callback(u32 event_num, struct pt_regs *regs,
1292f9f05395SJames Morse void *arg)
1293f9f05395SJames Morse {
1294f9f05395SJames Morse static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sdei_critical);
1295f9f05395SJames Morse struct ghes *ghes = arg;
1296f9f05395SJames Morse int err;
1297f9f05395SJames Morse
1298f9f05395SJames Morse raw_spin_lock(&ghes_notify_lock_sdei_critical);
1299f9f05395SJames Morse err = __ghes_sdei_callback(ghes, FIX_APEI_GHES_SDEI_CRITICAL);
1300f9f05395SJames Morse raw_spin_unlock(&ghes_notify_lock_sdei_critical);
1301f9f05395SJames Morse
1302f9f05395SJames Morse return err;
1303f9f05395SJames Morse }
1304f9f05395SJames Morse
apei_sdei_register_ghes(struct ghes * ghes)1305f9f05395SJames Morse static int apei_sdei_register_ghes(struct ghes *ghes)
1306f9f05395SJames Morse {
1307f9f05395SJames Morse if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE))
1308f9f05395SJames Morse return -EOPNOTSUPP;
1309f9f05395SJames Morse
1310f9f05395SJames Morse return sdei_register_ghes(ghes, ghes_sdei_normal_callback,
1311f9f05395SJames Morse ghes_sdei_critical_callback);
1312f9f05395SJames Morse }
1313f9f05395SJames Morse
apei_sdei_unregister_ghes(struct ghes * ghes)1314f9f05395SJames Morse static int apei_sdei_unregister_ghes(struct ghes *ghes)
1315f9f05395SJames Morse {
1316f9f05395SJames Morse if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE))
1317f9f05395SJames Morse return -EOPNOTSUPP;
1318f9f05395SJames Morse
1319f9f05395SJames Morse return sdei_unregister_ghes(ghes);
1320f9f05395SJames Morse }
1321f9f05395SJames Morse
ghes_probe(struct platform_device * ghes_dev)1322da095fd3SBill Pemberton static int ghes_probe(struct platform_device *ghes_dev)
1323d334a491SHuang Ying {
1324d334a491SHuang Ying struct acpi_hest_generic *generic;
1325d334a491SHuang Ying struct ghes *ghes = NULL;
13263b880cbeSJames Morse unsigned long flags;
132744a69f61STomasz Nowicki
13287ad6e943SHuang Ying int rc = -EINVAL;
1329d334a491SHuang Ying
13301dd6b20eSJin Dongming generic = *(struct acpi_hest_generic **)ghes_dev->dev.platform_data;
1331d334a491SHuang Ying if (!generic->enabled)
13327ad6e943SHuang Ying return -ENODEV;
1333d334a491SHuang Ying
133481e88fdcSHuang Ying switch (generic->notify.type) {
133581e88fdcSHuang Ying case ACPI_HEST_NOTIFY_POLLED:
133681e88fdcSHuang Ying case ACPI_HEST_NOTIFY_EXTERNAL:
133781e88fdcSHuang Ying case ACPI_HEST_NOTIFY_SCI:
13387bf130e4SShiju Jose case ACPI_HEST_NOTIFY_GSIV:
13397bf130e4SShiju Jose case ACPI_HEST_NOTIFY_GPIO:
134044a69f61STomasz Nowicki break;
13417bf130e4SShiju Jose
13427edda088STyler Baicar case ACPI_HEST_NOTIFY_SEA:
13437edda088STyler Baicar if (!IS_ENABLED(CONFIG_ACPI_APEI_SEA)) {
13447edda088STyler Baicar pr_warn(GHES_PFX "Generic hardware error source: %d notified via SEA is not supported\n",
13457edda088STyler Baicar generic->header.source_id);
13467edda088STyler Baicar rc = -ENOTSUPP;
13477edda088STyler Baicar goto err;
13487edda088STyler Baicar }
13497edda088STyler Baicar break;
135081e88fdcSHuang Ying case ACPI_HEST_NOTIFY_NMI:
135144a69f61STomasz Nowicki if (!IS_ENABLED(CONFIG_HAVE_ACPI_APEI_NMI)) {
135244a69f61STomasz Nowicki pr_warn(GHES_PFX "Generic hardware error source: %d notified via NMI interrupt is not supported!\n",
135344a69f61STomasz Nowicki generic->header.source_id);
135444a69f61STomasz Nowicki goto err;
135544a69f61STomasz Nowicki }
135681e88fdcSHuang Ying break;
1357f9f05395SJames Morse case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED:
1358f9f05395SJames Morse if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE)) {
1359f9f05395SJames Morse pr_warn(GHES_PFX "Generic hardware error source: %d notified via SDE Interface is not supported!\n",
1360f9f05395SJames Morse generic->header.source_id);
1361f9f05395SJames Morse goto err;
1362f9f05395SJames Morse }
1363f9f05395SJames Morse break;
136481e88fdcSHuang Ying case ACPI_HEST_NOTIFY_LOCAL:
1365933ca4e3SKefeng Wang pr_warn(GHES_PFX "Generic hardware error source: %d notified via local interrupt is not supported!\n",
1366d334a491SHuang Ying generic->header.source_id);
1367d334a491SHuang Ying goto err;
136881e88fdcSHuang Ying default:
1369933ca4e3SKefeng Wang pr_warn(FW_WARN GHES_PFX "Unknown notification type: %u for generic hardware error source: %d\n",
137081e88fdcSHuang Ying generic->notify.type, generic->header.source_id);
137181e88fdcSHuang Ying goto err;
1372d334a491SHuang Ying }
137381e88fdcSHuang Ying
137481e88fdcSHuang Ying rc = -EIO;
137581e88fdcSHuang Ying if (generic->error_block_length <
13760a00fd5eSLv Zheng sizeof(struct acpi_hest_generic_status)) {
1377933ca4e3SKefeng Wang pr_warn(FW_BUG GHES_PFX "Invalid error block length: %u for generic hardware error source: %d\n",
1378933ca4e3SKefeng Wang generic->error_block_length, generic->header.source_id);
1379d334a491SHuang Ying goto err;
1380d334a491SHuang Ying }
1381d334a491SHuang Ying ghes = ghes_new(generic);
1382d334a491SHuang Ying if (IS_ERR(ghes)) {
1383d334a491SHuang Ying rc = PTR_ERR(ghes);
1384d334a491SHuang Ying ghes = NULL;
1385d334a491SHuang Ying goto err;
1386d334a491SHuang Ying }
138721480547SMauro Carvalho Chehab
138881e88fdcSHuang Ying switch (generic->notify.type) {
138981e88fdcSHuang Ying case ACPI_HEST_NOTIFY_POLLED:
1390cea79e7eSBhaskar Upadhaya timer_setup(&ghes->timer, ghes_poll_func, 0);
139181e88fdcSHuang Ying ghes_add_timer(ghes);
139281e88fdcSHuang Ying break;
139381e88fdcSHuang Ying case ACPI_HEST_NOTIFY_EXTERNAL:
139481e88fdcSHuang Ying /* External interrupt vector is GSI */
1395a98d4f64SWei Yongjun rc = acpi_gsi_to_irq(generic->notify.vector, &ghes->irq);
1396a98d4f64SWei Yongjun if (rc) {
139781e88fdcSHuang Ying pr_err(GHES_PFX "Failed to map GSI to IRQ for generic hardware error source: %d\n",
139881e88fdcSHuang Ying generic->header.source_id);
1399cc7f3f13SBorislav Petkov goto err;
140081e88fdcSHuang Ying }
1401bdb9458aSLoc Ho rc = request_irq(ghes->irq, ghes_irq_func, IRQF_SHARED,
1402bdb9458aSLoc Ho "GHES IRQ", ghes);
1403a98d4f64SWei Yongjun if (rc) {
140481e88fdcSHuang Ying pr_err(GHES_PFX "Failed to register IRQ for generic hardware error source: %d\n",
140581e88fdcSHuang Ying generic->header.source_id);
1406cc7f3f13SBorislav Petkov goto err;
140781e88fdcSHuang Ying }
140881e88fdcSHuang Ying break;
14097bf130e4SShiju Jose
141081e88fdcSHuang Ying case ACPI_HEST_NOTIFY_SCI:
14117bf130e4SShiju Jose case ACPI_HEST_NOTIFY_GSIV:
14127bf130e4SShiju Jose case ACPI_HEST_NOTIFY_GPIO:
14137ad6e943SHuang Ying mutex_lock(&ghes_list_mutex);
14147bf130e4SShiju Jose if (list_empty(&ghes_hed))
14157bf130e4SShiju Jose register_acpi_hed_notifier(&ghes_notifier_hed);
14167bf130e4SShiju Jose list_add_rcu(&ghes->list, &ghes_hed);
14177ad6e943SHuang Ying mutex_unlock(&ghes_list_mutex);
1418d334a491SHuang Ying break;
14197bf130e4SShiju Jose
14207edda088STyler Baicar case ACPI_HEST_NOTIFY_SEA:
14217edda088STyler Baicar ghes_sea_add(ghes);
14227edda088STyler Baicar break;
1423d334a491SHuang Ying case ACPI_HEST_NOTIFY_NMI:
142444a69f61STomasz Nowicki ghes_nmi_add(ghes);
1425d334a491SHuang Ying break;
1426f9f05395SJames Morse case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED:
1427f9f05395SJames Morse rc = apei_sdei_register_ghes(ghes);
1428f9f05395SJames Morse if (rc)
1429f9f05395SJames Morse goto err;
1430f9f05395SJames Morse break;
143181e88fdcSHuang Ying default:
143281e88fdcSHuang Ying BUG();
14337ad6e943SHuang Ying }
1434cc7f3f13SBorislav Petkov
14357ad6e943SHuang Ying platform_set_drvdata(ghes_dev, ghes);
1436d334a491SHuang Ying
14379057a3f7SJia He ghes->dev = &ghes_dev->dev;
14389057a3f7SJia He
14399057a3f7SJia He mutex_lock(&ghes_devs_mutex);
14409057a3f7SJia He list_add_tail(&ghes->elist, &ghes_devs);
14419057a3f7SJia He mutex_unlock(&ghes_devs_mutex);
1442cc7f3f13SBorislav Petkov
144377b246b3STyler Baicar /* Handle any pending errors right away */
14443b880cbeSJames Morse spin_lock_irqsave(&ghes_notify_lock_irq, flags);
144577b246b3STyler Baicar ghes_proc(ghes);
14463b880cbeSJames Morse spin_unlock_irqrestore(&ghes_notify_lock_irq, flags);
144777b246b3STyler Baicar
1448d334a491SHuang Ying return 0;
1449cc7f3f13SBorislav Petkov
1450d334a491SHuang Ying err:
14517ad6e943SHuang Ying if (ghes) {
1452d334a491SHuang Ying ghes_fini(ghes);
1453d334a491SHuang Ying kfree(ghes);
1454d334a491SHuang Ying }
14557ad6e943SHuang Ying return rc;
1456d334a491SHuang Ying }
1457d334a491SHuang Ying
ghes_remove(struct platform_device * ghes_dev)1458b59bc2fbSBill Pemberton static int ghes_remove(struct platform_device *ghes_dev)
14597ad6e943SHuang Ying {
1460f9f05395SJames Morse int rc;
14617ad6e943SHuang Ying struct ghes *ghes;
14627ad6e943SHuang Ying struct acpi_hest_generic *generic;
14637ad6e943SHuang Ying
14647ad6e943SHuang Ying ghes = platform_get_drvdata(ghes_dev);
14657ad6e943SHuang Ying generic = ghes->generic;
14667ad6e943SHuang Ying
146781e88fdcSHuang Ying ghes->flags |= GHES_EXITING;
14687ad6e943SHuang Ying switch (generic->notify.type) {
146981e88fdcSHuang Ying case ACPI_HEST_NOTIFY_POLLED:
1470292a089dSSteven Rostedt (Google) timer_shutdown_sync(&ghes->timer);
147181e88fdcSHuang Ying break;
147281e88fdcSHuang Ying case ACPI_HEST_NOTIFY_EXTERNAL:
147381e88fdcSHuang Ying free_irq(ghes->irq, ghes);
147481e88fdcSHuang Ying break;
14757bf130e4SShiju Jose
14767ad6e943SHuang Ying case ACPI_HEST_NOTIFY_SCI:
14777bf130e4SShiju Jose case ACPI_HEST_NOTIFY_GSIV:
14787bf130e4SShiju Jose case ACPI_HEST_NOTIFY_GPIO:
14797ad6e943SHuang Ying mutex_lock(&ghes_list_mutex);
14807ad6e943SHuang Ying list_del_rcu(&ghes->list);
14817bf130e4SShiju Jose if (list_empty(&ghes_hed))
14827bf130e4SShiju Jose unregister_acpi_hed_notifier(&ghes_notifier_hed);
14837ad6e943SHuang Ying mutex_unlock(&ghes_list_mutex);
14847d64f82cSJames Morse synchronize_rcu();
14857ad6e943SHuang Ying break;
14867bf130e4SShiju Jose
14877edda088STyler Baicar case ACPI_HEST_NOTIFY_SEA:
14887edda088STyler Baicar ghes_sea_remove(ghes);
14897edda088STyler Baicar break;
149081e88fdcSHuang Ying case ACPI_HEST_NOTIFY_NMI:
149144a69f61STomasz Nowicki ghes_nmi_remove(ghes);
149281e88fdcSHuang Ying break;
1493f9f05395SJames Morse case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED:
1494f9f05395SJames Morse rc = apei_sdei_unregister_ghes(ghes);
1495f9f05395SJames Morse if (rc)
1496f9f05395SJames Morse return rc;
1497f9f05395SJames Morse break;
14987ad6e943SHuang Ying default:
14997ad6e943SHuang Ying BUG();
15007ad6e943SHuang Ying break;
15017ad6e943SHuang Ying }
15027ad6e943SHuang Ying
15037ad6e943SHuang Ying ghes_fini(ghes);
150421480547SMauro Carvalho Chehab
15059057a3f7SJia He mutex_lock(&ghes_devs_mutex);
15069057a3f7SJia He list_del(&ghes->elist);
15079057a3f7SJia He mutex_unlock(&ghes_devs_mutex);
150821480547SMauro Carvalho Chehab
15097ad6e943SHuang Ying kfree(ghes);
15107ad6e943SHuang Ying
15117ad6e943SHuang Ying return 0;
15127ad6e943SHuang Ying }
15137ad6e943SHuang Ying
15147ad6e943SHuang Ying static struct platform_driver ghes_platform_driver = {
15157ad6e943SHuang Ying .driver = {
15167ad6e943SHuang Ying .name = "GHES",
15177ad6e943SHuang Ying },
15187ad6e943SHuang Ying .probe = ghes_probe,
15197ad6e943SHuang Ying .remove = ghes_remove,
15207ad6e943SHuang Ying };
15217ad6e943SHuang Ying
acpi_ghes_init(void)152227e932a3SShuai Xue void __init acpi_ghes_init(void)
1523d334a491SHuang Ying {
152481e88fdcSHuang Ying int rc;
152581e88fdcSHuang Ying
1526dc4e8c07SShuai Xue sdei_init();
1527dc4e8c07SShuai Xue
1528d334a491SHuang Ying if (acpi_disabled)
1529dc4e8c07SShuai Xue return;
1530d334a491SHuang Ying
1531e931d0daSPunit Agrawal switch (hest_disable) {
1532e931d0daSPunit Agrawal case HEST_NOT_FOUND:
1533dc4e8c07SShuai Xue return;
1534e931d0daSPunit Agrawal case HEST_DISABLED:
1535d334a491SHuang Ying pr_info(GHES_PFX "HEST is not enabled!\n");
1536dc4e8c07SShuai Xue return;
1537e931d0daSPunit Agrawal default:
1538e931d0daSPunit Agrawal break;
1539d334a491SHuang Ying }
1540d334a491SHuang Ying
1541b6a95016SHuang Ying if (ghes_disable) {
1542b6a95016SHuang Ying pr_info(GHES_PFX "GHES is not enabled!\n");
1543dc4e8c07SShuai Xue return;
1544b6a95016SHuang Ying }
1545b6a95016SHuang Ying
154644a69f61STomasz Nowicki ghes_nmi_init_cxt();
154767eb2e99SHuang Ying
154867eb2e99SHuang Ying rc = platform_driver_register(&ghes_platform_driver);
154967eb2e99SHuang Ying if (rc)
1550dc4e8c07SShuai Xue return;
155167eb2e99SHuang Ying
15529fb0bfe1SHuang Ying rc = apei_osc_setup();
15539fb0bfe1SHuang Ying if (rc == 0 && osc_sb_apei_support_acked)
15549fb0bfe1SHuang Ying pr_info(GHES_PFX "APEI firmware first mode is enabled by APEI bit and WHEA _OSC.\n");
15559fb0bfe1SHuang Ying else if (rc == 0 && !osc_sb_apei_support_acked)
15569fb0bfe1SHuang Ying pr_info(GHES_PFX "APEI firmware first mode is enabled by WHEA _OSC.\n");
15579fb0bfe1SHuang Ying else if (rc && osc_sb_apei_support_acked)
15589fb0bfe1SHuang Ying pr_info(GHES_PFX "APEI firmware first mode is enabled by APEI bit.\n");
15599fb0bfe1SHuang Ying else
15609fb0bfe1SHuang Ying pr_info(GHES_PFX "Failed to enable APEI firmware first mode.\n");
1561d334a491SHuang Ying }
15628e40612fSJia He
15639057a3f7SJia He /*
15649057a3f7SJia He * Known x86 systems that prefer GHES error reporting:
15659057a3f7SJia He */
15669057a3f7SJia He static struct acpi_platform_list plat_list[] = {
15679057a3f7SJia He {"HPE ", "Server ", 0, ACPI_SIG_FADT, all_versions},
15689057a3f7SJia He { } /* End */
15699057a3f7SJia He };
15709057a3f7SJia He
ghes_get_devices(void)15719057a3f7SJia He struct list_head *ghes_get_devices(void)
15729057a3f7SJia He {
15739057a3f7SJia He int idx = -1;
15749057a3f7SJia He
15759057a3f7SJia He if (IS_ENABLED(CONFIG_X86)) {
15769057a3f7SJia He idx = acpi_match_platform_list(plat_list);
15779057a3f7SJia He if (idx < 0) {
15789057a3f7SJia He if (!ghes_edac_force_enable)
15799057a3f7SJia He return NULL;
15809057a3f7SJia He
15819057a3f7SJia He pr_warn_once("Force-loading ghes_edac on an unsupported platform. You're on your own!\n");
15829057a3f7SJia He }
15839368aa18SLi Yang } else if (list_empty(&ghes_devs)) {
15849368aa18SLi Yang return NULL;
15859057a3f7SJia He }
15869057a3f7SJia He
15879057a3f7SJia He return &ghes_devs;
15889057a3f7SJia He }
15899057a3f7SJia He EXPORT_SYMBOL_GPL(ghes_get_devices);
15909057a3f7SJia He
ghes_register_report_chain(struct notifier_block * nb)15918e40612fSJia He void ghes_register_report_chain(struct notifier_block *nb)
15928e40612fSJia He {
15938e40612fSJia He atomic_notifier_chain_register(&ghes_report_chain, nb);
15948e40612fSJia He }
15958e40612fSJia He EXPORT_SYMBOL_GPL(ghes_register_report_chain);
15968e40612fSJia He
ghes_unregister_report_chain(struct notifier_block * nb)15978e40612fSJia He void ghes_unregister_report_chain(struct notifier_block *nb)
15988e40612fSJia He {
15998e40612fSJia He atomic_notifier_chain_unregister(&ghes_report_chain, nb);
16008e40612fSJia He }
16018e40612fSJia He EXPORT_SYMBOL_GPL(ghes_unregister_report_chain);
1602