xref: /openbmc/linux/drivers/acpi/apei/ghes.c (revision 410063c9)
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