linux/drivers/acpi/apei/ghes.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * APEI Generic Hardware Error Source support
 *
 * Generic Hardware Error Source provides a way to report platform
 * hardware errors (such as that from chipset). It works in so called
 * "Firmware First" mode, that is, hardware errors are reported to
 * firmware firstly, then reported to Linux by firmware. This way,
 * some non-standard hardware error registers or non-standard hardware
 * link can be checked by firmware to produce more hardware error
 * information for Linux.
 *
 * For more information about Generic Hardware Error Source, please
 * refer to ACPI Specification version 4.0, section 17.3.2.6
 *
 * Copyright 2010,2011 Intel Corp.
 *   Author: Huang Ying <[email protected]>
 */

#include <linux/arm_sdei.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/cper.h>
#include <linux/cleanup.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/ratelimit.h>
#include <linux/vmalloc.h>
#include <linux/irq_work.h>
#include <linux/llist.h>
#include <linux/genalloc.h>
#include <linux/kfifo.h>
#include <linux/pci.h>
#include <linux/pfn.h>
#include <linux/aer.h>
#include <linux/nmi.h>
#include <linux/sched/clock.h>
#include <linux/uuid.h>
#include <linux/ras.h>
#include <linux/task_work.h>

#include <acpi/actbl1.h>
#include <acpi/ghes.h>
#include <acpi/apei.h>
#include <asm/fixmap.h>
#include <asm/tlbflush.h>
#include <cxl/event.h>
#include <ras/ras_event.h>

#include "apei-internal.h"

#define GHES_PFX

#define GHES_ESTATUS_MAX_SIZE
#define GHES_ESOURCE_PREALLOC_MAX_SIZE

#define GHES_ESTATUS_POOL_MIN_ALLOC_ORDER

/* This is just an estimation for memory pool allocation */
#define GHES_ESTATUS_CACHE_AVG_SIZE

#define GHES_ESTATUS_CACHES_SIZE

#define GHES_ESTATUS_IN_CACHE_MAX_NSEC
/* Prevent too many caches are allocated because of RCU */
#define GHES_ESTATUS_CACHE_ALLOCED_MAX

#define GHES_ESTATUS_CACHE_LEN(estatus_len)
#define GHES_ESTATUS_FROM_CACHE(estatus_cache)

#define GHES_ESTATUS_NODE_LEN(estatus_len)
#define GHES_ESTATUS_FROM_NODE(estatus_node)

#define GHES_VENDOR_ENTRY_LEN(gdata_len)
#define GHES_GDATA_FROM_VENDOR_ENTRY(vendor_entry)

/*
 *  NMI-like notifications vary by architecture, before the compiler can prune
 *  unused static functions it needs a value for these enums.
 */
#ifndef CONFIG_ARM_SDE_INTERFACE
#define FIX_APEI_GHES_SDEI_NORMAL
#define FIX_APEI_GHES_SDEI_CRITICAL
#endif

static ATOMIC_NOTIFIER_HEAD(ghes_report_chain);

static inline bool is_hest_type_generic_v2(struct ghes *ghes)
{}

/*
 * A platform may describe one error source for the handling of synchronous
 * errors (e.g. MCE or SEA), or for handling asynchronous errors (e.g. SCI
 * or External Interrupt). On x86, the HEST notifications are always
 * asynchronous, so only SEA on ARM is delivered as a synchronous
 * notification.
 */
static inline bool is_hest_sync_notify(struct ghes *ghes)
{}

/*
 * This driver isn't really modular, however for the time being,
 * continuing to use module_param is the easiest way to remain
 * compatible with existing boot arg use cases.
 */
bool ghes_disable;
module_param_named(disable, ghes_disable, bool, 0);

/*
 * "ghes.edac_force_enable" forcibly enables ghes_edac and skips the platform
 * check.
 */
static bool ghes_edac_force_enable;
module_param_named(edac_force_enable, ghes_edac_force_enable, bool, 0);

/*
 * All error sources notified with HED (Hardware Error Device) share a
 * single notifier callback, so they need to be linked and checked one
 * by one. This holds true for NMI too.
 *
 * RCU is used for these lists, so ghes_list_mutex is only used for
 * list changing, not for traversing.
 */
static LIST_HEAD(ghes_hed);
static DEFINE_MUTEX(ghes_list_mutex);

/*
 * A list of GHES devices which are given to the corresponding EDAC driver
 * ghes_edac for further use.
 */
static LIST_HEAD(ghes_devs);
static DEFINE_MUTEX(ghes_devs_mutex);

/*
 * Because the memory area used to transfer hardware error information
 * from BIOS to Linux can be determined only in NMI, IRQ or timer
 * handler, but general ioremap can not be used in atomic context, so
 * the fixmap is used instead.
 *
 * This spinlock is used to prevent the fixmap entry from being used
 * simultaneously.
 */
static DEFINE_SPINLOCK(ghes_notify_lock_irq);

struct ghes_vendor_record_entry {};

static struct gen_pool *ghes_estatus_pool;

static struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
static atomic_t ghes_estatus_cache_alloced;

static int ghes_panic_timeout __read_mostly =;

static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
{}

static void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx)
{}

int ghes_estatus_pool_init(unsigned int num_ghes)
{}

/**
 * ghes_estatus_pool_region_free - free previously allocated memory
 *				   from the ghes_estatus_pool.
 * @addr: address of memory to free.
 * @size: size of memory to free.
 *
 * Returns none.
 */
void ghes_estatus_pool_region_free(unsigned long addr, u32 size)
{}
EXPORT_SYMBOL_GPL();

static int map_gen_v2(struct ghes *ghes)
{}

static void unmap_gen_v2(struct ghes *ghes)
{}

static void ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
{}

static struct ghes *ghes_new(struct acpi_hest_generic *generic)
{}

static void ghes_fini(struct ghes *ghes)
{}

static inline int ghes_severity(int severity)
{}

static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len,
				  int from_phys,
				  enum fixed_addresses fixmap_idx)
{}

/* Check the top-level record header has an appropriate size. */
static int __ghes_check_estatus(struct ghes *ghes,
				struct acpi_hest_generic_status *estatus)
{}

/* Read the CPER block, returning its address, and header in estatus. */
static int __ghes_peek_estatus(struct ghes *ghes,
			       struct acpi_hest_generic_status *estatus,
			       u64 *buf_paddr, enum fixed_addresses fixmap_idx)
{}

static int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
			       u64 buf_paddr, enum fixed_addresses fixmap_idx,
			       size_t buf_len)
{}

static int ghes_read_estatus(struct ghes *ghes,
			     struct acpi_hest_generic_status *estatus,
			     u64 *buf_paddr, enum fixed_addresses fixmap_idx)
{}

static void ghes_clear_estatus(struct ghes *ghes,
			       struct acpi_hest_generic_status *estatus,
			       u64 buf_paddr, enum fixed_addresses fixmap_idx)
{}

/*
 * Called as task_work before returning to user-space.
 * Ensure any queued work has been done before we return to the context that
 * triggered the notification.
 */
static void ghes_kick_task_work(struct callback_head *head)
{}

static bool ghes_do_memory_failure(u64 physical_addr, int flags)
{}

static bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata,
				       int sev, bool sync)
{}

static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata,
				       int sev, bool sync)
{}

/*
 * PCIe AER errors need to be sent to the AER driver for reporting and
 * recovery. The GHES severities map to the following AER severities and
 * require the following handling:
 *
 * GHES_SEV_CORRECTABLE -> AER_CORRECTABLE
 *     These need to be reported by the AER driver but no recovery is
 *     necessary.
 * GHES_SEV_RECOVERABLE -> AER_NONFATAL
 * GHES_SEV_RECOVERABLE && CPER_SEC_RESET -> AER_FATAL
 *     These both need to be reported and recovered from by the AER driver.
 * GHES_SEV_PANIC does not make it to this handling since the kernel must
 *     panic.
 */
static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
{}

static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list);

int ghes_register_vendor_record_notifier(struct notifier_block *nb)
{}
EXPORT_SYMBOL_GPL();

void ghes_unregister_vendor_record_notifier(struct notifier_block *nb)
{}
EXPORT_SYMBOL_GPL();

static void ghes_vendor_record_work_func(struct work_struct *work)
{}

static void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
					  int sev)
{}

/* Room for 8 entries for each of the 4 event log queues */
#define CXL_CPER_FIFO_DEPTH
DEFINE_KFIFO(cxl_cper_fifo, } ;

/* Synchronize schedule_work() with cxl_cper_work changes */
static DEFINE_SPINLOCK(cxl_cper_work_lock);
struct work_struct *cxl_cper_work;

static void cxl_cper_post_event(enum cxl_event_type event_type,
				struct cxl_cper_event_rec *rec)
{}

int cxl_cper_register_work(struct work_struct *work)
{}
EXPORT_SYMBOL_NS_GPL();

int cxl_cper_unregister_work(struct work_struct *work)
{}
EXPORT_SYMBOL_NS_GPL();

int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd)
{}
EXPORT_SYMBOL_NS_GPL();

static bool ghes_do_proc(struct ghes *ghes,
			 const struct acpi_hest_generic_status *estatus)
{}

static void __ghes_print_estatus(const char *pfx,
				 const struct acpi_hest_generic *generic,
				 const struct acpi_hest_generic_status *estatus)
{}

static int ghes_print_estatus(const char *pfx,
			      const struct acpi_hest_generic *generic,
			      const struct acpi_hest_generic_status *estatus)
{}

/*
 * GHES error status reporting throttle, to report more kinds of
 * errors, instead of just most frequently occurred errors.
 */
static int ghes_estatus_cached(struct acpi_hest_generic_status *estatus)
{}

static struct ghes_estatus_cache *ghes_estatus_cache_alloc(
	struct acpi_hest_generic *generic,
	struct acpi_hest_generic_status *estatus)
{}

static void ghes_estatus_cache_rcu_free(struct rcu_head *head)
{}

static void
ghes_estatus_cache_add(struct acpi_hest_generic *generic,
		       struct acpi_hest_generic_status *estatus)
{}

static void __ghes_panic(struct ghes *ghes,
			 struct acpi_hest_generic_status *estatus,
			 u64 buf_paddr, enum fixed_addresses fixmap_idx)
{}

static int ghes_proc(struct ghes *ghes)
{}

static void ghes_add_timer(struct ghes *ghes)
{}

static void ghes_poll_func(struct timer_list *t)
{}

static irqreturn_t ghes_irq_func(int irq, void *data)
{}

static int ghes_notify_hed(struct notifier_block *this, unsigned long event,
			   void *data)
{}

static struct notifier_block ghes_notifier_hed =;

/*
 * Handlers for CPER records may not be NMI safe. For example,
 * memory_failure_queue() takes spinlocks and calls schedule_work_on().
 * In any NMI-like handler, memory from ghes_estatus_pool is used to save
 * estatus, and added to the ghes_estatus_llist. irq_work_queue() causes
 * ghes_proc_in_irq() to run in IRQ context where each estatus in
 * ghes_estatus_llist is processed.
 *
 * Memory from the ghes_estatus_pool is also used with the ghes_estatus_cache
 * to suppress frequent messages.
 */
static struct llist_head ghes_estatus_llist;
static struct irq_work ghes_proc_irq_work;

static void ghes_proc_in_irq(struct irq_work *irq_work)
{}

static void ghes_print_queued_estatus(void)
{}

static int ghes_in_nmi_queue_one_entry(struct ghes *ghes,
				       enum fixed_addresses fixmap_idx)
{}

static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
				       enum fixed_addresses fixmap_idx)
{}

#ifdef CONFIG_ACPI_APEI_SEA
static LIST_HEAD(ghes_sea);

/*
 * Return 0 only if one of the SEA error sources successfully reported an error
 * record sent from the firmware.
 */
int ghes_notify_sea(void)
{
	static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sea);
	int rv;

	raw_spin_lock(&ghes_notify_lock_sea);
	rv = ghes_in_nmi_spool_from_list(&ghes_sea, FIX_APEI_GHES_SEA);
	raw_spin_unlock(&ghes_notify_lock_sea);

	return rv;
}

static void ghes_sea_add(struct ghes *ghes)
{
	mutex_lock(&ghes_list_mutex);
	list_add_rcu(&ghes->list, &ghes_sea);
	mutex_unlock(&ghes_list_mutex);
}

static void ghes_sea_remove(struct ghes *ghes)
{
	mutex_lock(&ghes_list_mutex);
	list_del_rcu(&ghes->list);
	mutex_unlock(&ghes_list_mutex);
	synchronize_rcu();
}
#else /* CONFIG_ACPI_APEI_SEA */
static inline void ghes_sea_add(struct ghes *ghes) {}
static inline void ghes_sea_remove(struct ghes *ghes) {}
#endif /* CONFIG_ACPI_APEI_SEA */

#ifdef CONFIG_HAVE_ACPI_APEI_NMI
/*
 * NMI may be triggered on any CPU, so ghes_in_nmi is used for
 * having only one concurrent reader.
 */
static atomic_t ghes_in_nmi =;

static LIST_HEAD(ghes_nmi);

static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
{}

static void ghes_nmi_add(struct ghes *ghes)
{}

static void ghes_nmi_remove(struct ghes *ghes)
{}
#else /* CONFIG_HAVE_ACPI_APEI_NMI */
static inline void ghes_nmi_add(struct ghes *ghes) { }
static inline void ghes_nmi_remove(struct ghes *ghes) { }
#endif /* CONFIG_HAVE_ACPI_APEI_NMI */

static void ghes_nmi_init_cxt(void)
{}

static int __ghes_sdei_callback(struct ghes *ghes,
				enum fixed_addresses fixmap_idx)
{}

static int ghes_sdei_normal_callback(u32 event_num, struct pt_regs *regs,
				      void *arg)
{}

static int ghes_sdei_critical_callback(u32 event_num, struct pt_regs *regs,
				       void *arg)
{}

static int apei_sdei_register_ghes(struct ghes *ghes)
{}

static int apei_sdei_unregister_ghes(struct ghes *ghes)
{}

static int ghes_probe(struct platform_device *ghes_dev)
{}

static void ghes_remove(struct platform_device *ghes_dev)
{}

static struct platform_driver ghes_platform_driver =;

void __init acpi_ghes_init(void)
{}

/*
 * Known x86 systems that prefer GHES error reporting:
 */
static struct acpi_platform_list plat_list[] =;

struct list_head *ghes_get_devices(void)
{}
EXPORT_SYMBOL_GPL();

void ghes_register_report_chain(struct notifier_block *nb)
{}
EXPORT_SYMBOL_GPL();

void ghes_unregister_report_chain(struct notifier_block *nb)
{}
EXPORT_SYMBOL_GPL();