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