Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
# SPDX-License-Identifier: GPL-2.0
config HAVE_ACPI_APEI
bool
config HAVE_ACPI_APEI_NMI
bool
config ACPI_APEI
bool "ACPI Platform Error Interface (APEI)"
select MISC_FILESYSTEMS
select PSTORE
select UEFI_CPER
depends on HAVE_ACPI_APEI
help
APEI allows to report errors (for example from the chipset)
to the operating system. This improves NMI handling
especially. In addition it supports error serialization and
error injection.
config ACPI_APEI_GHES
bool "APEI Generic Hardware Error Source"
depends on ACPI_APEI
select ACPI_HED
select IRQ_WORK
select GENERIC_ALLOCATOR
select ARM_SDE_INTERFACE if ARM64
help
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 valuable hardware error
information for Linux.
config ACPI_APEI_PCIEAER
bool "APEI PCIe AER logging/recovering support"
depends on ACPI_APEI && PCIEAER
help
PCIe AER errors may be reported via APEI firmware first mode.
Turn on this option to enable the corresponding support.
config ACPI_APEI_SEA
bool
depends on ARM64 && ACPI_APEI_GHES
default y
config ACPI_APEI_MEMORY_FAILURE
bool "APEI memory error recovering support"
depends on ACPI_APEI && MEMORY_FAILURE
help
Memory errors may be reported via APEI firmware first mode.
Turn on this option to enable the memory recovering support.
config ACPI_APEI_EINJ
tristate "APEI Error INJection (EINJ)"
depends on ACPI_APEI && DEBUG_FS
help
EINJ provides a hardware error injection mechanism, it is
mainly used for debugging and testing the other parts of
APEI and some other RAS features.
config ACPI_APEI_EINJ_CXL
bool "CXL Error INJection Support"
default ACPI_APEI_EINJ
depends on ACPI_APEI_EINJ
depends on CXL_BUS && CXL_BUS <= ACPI_APEI_EINJ
help
Support for CXL protocol Error INJection through debugfs/cxl.
Availability and which errors are supported is dependent on
the host platform. Look to ACPI v6.5 section 18.6.4 and kernel
EINJ documentation for more information.
If unsure say 'n'
config ACPI_APEI_GHES_NVIDIA
tristate "NVIDIA GHES vendor record handler"
depends on ACPI_APEI_GHES
help
Support for decoding NVIDIA-specific CPER sections delivered via
the APEI GHES vendor record notifier chain. Registers a handler
for the NVIDIA section GUID and logs error signatures, severity,
socket, and diagnostic register address-value pairs.
Enable on NVIDIA server platforms (e.g. DGX, HGX) that expose
ACPI device NVDA2012 in their firmware tables.
If unsure, say N.
config ACPI_APEI_ERST_DEBUG
tristate "APEI Error Record Serialization Table (ERST) Debug Support"
depends on ACPI_APEI
help
ERST is a way provided by APEI to save and retrieve hardware
error information to and from a persistent store. Enable this
if you want to debugging and testing the ERST kernel support
and firmware implementation.
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ACPI_APEI) += apei.o
obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o
# clang versions prior to 18 may blow out the stack with KASAN
ifeq ($(CONFIG_COMPILE_TEST)_$(CONFIG_CC_IS_CLANG)_$(call clang-min-version, 180000),y_y_)
KASAN_SANITIZE_ghes.o := n
endif
obj-$(CONFIG_ACPI_APEI_PCIEAER) += ghes_helpers.o
obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o
einj-y := einj-core.o
einj-$(CONFIG_ACPI_APEI_EINJ_CXL) += einj-cxl.o
obj-$(CONFIG_ACPI_APEI_ERST_DEBUG) += erst-dbg.o
obj-$(CONFIG_ACPI_APEI_GHES_NVIDIA) += ghes-nvidia.o
apei-y := apei-base.o hest.o erst.o bert.o
+800
View File
@@ -0,0 +1,800 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* apei-base.c - ACPI Platform Error Interface (APEI) supporting
* infrastructure
*
* APEI allows to report errors (for example from the chipset) to
* the operating system. This improves NMI handling especially. In
* addition it supports error serialization and error injection.
*
* For more information about APEI, please refer to ACPI Specification
* version 4.0, chapter 17.
*
* This file has Common functions used by more than one APEI table,
* including framework of interpreter for ERST and EINJ; resource
* management for APEI registers.
*
* Copyright (C) 2009, Intel Corp.
* Author: Huang Ying <ying.huang@intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/kref.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <acpi/apei.h>
#include <linux/unaligned.h>
#include "apei-internal.h"
#define APEI_PFX "APEI: "
/*
* APEI ERST (Error Record Serialization Table) and EINJ (Error
* INJection) interpreter framework.
*/
#define APEI_EXEC_PRESERVE_REGISTER 0x1
void apei_exec_ctx_init(struct apei_exec_context *ctx,
struct apei_exec_ins_type *ins_table,
u32 instructions,
struct acpi_whea_header *action_table,
u32 entries)
{
ctx->ins_table = ins_table;
ctx->instructions = instructions;
ctx->action_table = action_table;
ctx->entries = entries;
}
EXPORT_SYMBOL_GPL(apei_exec_ctx_init);
int __apei_exec_read_register(struct acpi_whea_header *entry, u64 *val)
{
int rc;
rc = apei_read(val, &entry->register_region);
if (rc)
return rc;
*val >>= entry->register_region.bit_offset;
*val &= entry->mask;
return 0;
}
int apei_exec_read_register(struct apei_exec_context *ctx,
struct acpi_whea_header *entry)
{
int rc;
u64 val = 0;
rc = __apei_exec_read_register(entry, &val);
if (rc)
return rc;
ctx->value = val;
return 0;
}
EXPORT_SYMBOL_GPL(apei_exec_read_register);
int apei_exec_read_register_value(struct apei_exec_context *ctx,
struct acpi_whea_header *entry)
{
int rc;
rc = apei_exec_read_register(ctx, entry);
if (rc)
return rc;
ctx->value = (ctx->value == entry->value);
return 0;
}
EXPORT_SYMBOL_GPL(apei_exec_read_register_value);
int __apei_exec_write_register(struct acpi_whea_header *entry, u64 val)
{
int rc;
val &= entry->mask;
val <<= entry->register_region.bit_offset;
if (entry->flags & APEI_EXEC_PRESERVE_REGISTER) {
u64 valr = 0;
rc = apei_read(&valr, &entry->register_region);
if (rc)
return rc;
valr &= ~(entry->mask << entry->register_region.bit_offset);
val |= valr;
}
rc = apei_write(val, &entry->register_region);
return rc;
}
int apei_exec_write_register(struct apei_exec_context *ctx,
struct acpi_whea_header *entry)
{
return __apei_exec_write_register(entry, ctx->value);
}
EXPORT_SYMBOL_GPL(apei_exec_write_register);
int apei_exec_write_register_value(struct apei_exec_context *ctx,
struct acpi_whea_header *entry)
{
ctx->value = entry->value;
return apei_exec_write_register(ctx, entry);
}
EXPORT_SYMBOL_GPL(apei_exec_write_register_value);
int apei_exec_noop(struct apei_exec_context *ctx,
struct acpi_whea_header *entry)
{
return 0;
}
EXPORT_SYMBOL_GPL(apei_exec_noop);
/*
* Interpret the specified action. Go through whole action table,
* execute all instructions belong to the action.
*/
int __apei_exec_run(struct apei_exec_context *ctx, u8 action,
bool optional)
{
int rc = -ENOENT;
u32 i, ip;
struct acpi_whea_header *entry;
apei_exec_ins_func_t run;
ctx->ip = 0;
/*
* "ip" is the instruction pointer of current instruction,
* "ctx->ip" specifies the next instruction to executed,
* instruction "run" function may change the "ctx->ip" to
* implement "goto" semantics.
*/
rewind:
ip = 0;
for (i = 0; i < ctx->entries; i++) {
entry = &ctx->action_table[i];
if (entry->action != action)
continue;
if (ip == ctx->ip) {
if (entry->instruction >= ctx->instructions ||
!ctx->ins_table[entry->instruction].run) {
pr_warn(FW_WARN APEI_PFX
"Invalid action table, unknown instruction type: %d\n",
entry->instruction);
return -EINVAL;
}
run = ctx->ins_table[entry->instruction].run;
rc = run(ctx, entry);
if (rc < 0)
return rc;
else if (rc != APEI_EXEC_SET_IP)
ctx->ip++;
}
ip++;
if (ctx->ip < ip)
goto rewind;
}
return !optional && rc < 0 ? rc : 0;
}
EXPORT_SYMBOL_GPL(__apei_exec_run);
typedef int (*apei_exec_entry_func_t)(struct apei_exec_context *ctx,
struct acpi_whea_header *entry,
void *data);
static int apei_exec_for_each_entry(struct apei_exec_context *ctx,
apei_exec_entry_func_t func,
void *data,
int *end)
{
u8 ins;
int i, rc;
struct acpi_whea_header *entry;
struct apei_exec_ins_type *ins_table = ctx->ins_table;
for (i = 0; i < ctx->entries; i++) {
entry = ctx->action_table + i;
ins = entry->instruction;
if (end)
*end = i;
if (ins >= ctx->instructions || !ins_table[ins].run) {
pr_warn(FW_WARN APEI_PFX
"Invalid action table, unknown instruction type: %d\n",
ins);
return -EINVAL;
}
rc = func(ctx, entry, data);
if (rc)
return rc;
}
return 0;
}
static int pre_map_gar_callback(struct apei_exec_context *ctx,
struct acpi_whea_header *entry,
void *data)
{
u8 ins = entry->instruction;
if (ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER)
return apei_map_generic_address(&entry->register_region);
return 0;
}
/*
* Pre-map all GARs in action table to make it possible to access them
* in NMI handler.
*/
int apei_exec_pre_map_gars(struct apei_exec_context *ctx)
{
int rc, end;
rc = apei_exec_for_each_entry(ctx, pre_map_gar_callback,
NULL, &end);
if (rc) {
struct apei_exec_context ctx_unmap;
memcpy(&ctx_unmap, ctx, sizeof(*ctx));
ctx_unmap.entries = end;
apei_exec_post_unmap_gars(&ctx_unmap);
}
return rc;
}
EXPORT_SYMBOL_GPL(apei_exec_pre_map_gars);
static int post_unmap_gar_callback(struct apei_exec_context *ctx,
struct acpi_whea_header *entry,
void *data)
{
u8 ins = entry->instruction;
if (ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER)
apei_unmap_generic_address(&entry->register_region);
return 0;
}
/* Post-unmap all GAR in action table. */
int apei_exec_post_unmap_gars(struct apei_exec_context *ctx)
{
return apei_exec_for_each_entry(ctx, post_unmap_gar_callback,
NULL, NULL);
}
EXPORT_SYMBOL_GPL(apei_exec_post_unmap_gars);
/*
* Resource management for GARs in APEI
*/
struct apei_res {
struct list_head list;
unsigned long start;
unsigned long end;
};
/* Collect all resources requested, to avoid conflict */
static struct apei_resources apei_resources_all = {
.iomem = LIST_HEAD_INIT(apei_resources_all.iomem),
.ioport = LIST_HEAD_INIT(apei_resources_all.ioport),
};
static int apei_res_add(struct list_head *res_list,
unsigned long start, unsigned long size)
{
struct apei_res *res, *resn, *res_ins = NULL;
unsigned long end = start + size;
if (end <= start)
return 0;
repeat:
list_for_each_entry_safe(res, resn, res_list, list) {
if (res->start > end || res->end < start)
continue;
else if (end <= res->end && start >= res->start) {
kfree(res_ins);
return 0;
}
list_del(&res->list);
res->start = start = min(res->start, start);
res->end = end = max(res->end, end);
kfree(res_ins);
res_ins = res;
goto repeat;
}
if (res_ins)
list_add(&res_ins->list, res_list);
else {
res_ins = kmalloc_obj(*res_ins);
if (!res_ins)
return -ENOMEM;
res_ins->start = start;
res_ins->end = end;
list_add(&res_ins->list, res_list);
}
return 0;
}
static int apei_res_sub(struct list_head *res_list1,
struct list_head *res_list2)
{
struct apei_res *res1, *resn1, *res2, *res;
res1 = list_entry(res_list1->next, struct apei_res, list);
resn1 = list_entry(res1->list.next, struct apei_res, list);
while (&res1->list != res_list1) {
list_for_each_entry(res2, res_list2, list) {
if (res1->start >= res2->end ||
res1->end <= res2->start)
continue;
else if (res1->end <= res2->end &&
res1->start >= res2->start) {
list_del(&res1->list);
kfree(res1);
break;
} else if (res1->end > res2->end &&
res1->start < res2->start) {
res = kmalloc_obj(*res);
if (!res)
return -ENOMEM;
res->start = res2->end;
res->end = res1->end;
res1->end = res2->start;
list_add(&res->list, &res1->list);
resn1 = res;
} else {
if (res1->start < res2->start)
res1->end = res2->start;
else
res1->start = res2->end;
}
}
res1 = resn1;
resn1 = list_entry(resn1->list.next, struct apei_res, list);
}
return 0;
}
static void apei_res_clean(struct list_head *res_list)
{
struct apei_res *res, *resn;
list_for_each_entry_safe(res, resn, res_list, list) {
list_del(&res->list);
kfree(res);
}
}
void apei_resources_fini(struct apei_resources *resources)
{
apei_res_clean(&resources->iomem);
apei_res_clean(&resources->ioport);
}
EXPORT_SYMBOL_GPL(apei_resources_fini);
static int apei_resources_merge(struct apei_resources *resources1,
struct apei_resources *resources2)
{
int rc;
struct apei_res *res;
list_for_each_entry(res, &resources2->iomem, list) {
rc = apei_res_add(&resources1->iomem, res->start,
res->end - res->start);
if (rc)
return rc;
}
list_for_each_entry(res, &resources2->ioport, list) {
rc = apei_res_add(&resources1->ioport, res->start,
res->end - res->start);
if (rc)
return rc;
}
return 0;
}
int apei_resources_add(struct apei_resources *resources,
unsigned long start, unsigned long size,
bool iomem)
{
if (iomem)
return apei_res_add(&resources->iomem, start, size);
else
return apei_res_add(&resources->ioport, start, size);
}
EXPORT_SYMBOL_GPL(apei_resources_add);
/*
* EINJ has two groups of GARs (EINJ table entry and trigger table
* entry), so common resources are subtracted from the trigger table
* resources before the second requesting.
*/
int apei_resources_sub(struct apei_resources *resources1,
struct apei_resources *resources2)
{
int rc;
rc = apei_res_sub(&resources1->iomem, &resources2->iomem);
if (rc)
return rc;
return apei_res_sub(&resources1->ioport, &resources2->ioport);
}
EXPORT_SYMBOL_GPL(apei_resources_sub);
static int apei_get_res_callback(__u64 start, __u64 size, void *data)
{
struct apei_resources *resources = data;
return apei_res_add(&resources->iomem, start, size);
}
static int apei_get_nvs_resources(struct apei_resources *resources)
{
return acpi_nvs_for_each_region(apei_get_res_callback, resources);
}
int (*arch_apei_filter_addr)(int (*func)(__u64 start, __u64 size,
void *data), void *data);
static int apei_get_arch_resources(struct apei_resources *resources)
{
return arch_apei_filter_addr(apei_get_res_callback, resources);
}
/*
* IO memory/port resource management mechanism is used to check
* whether memory/port area used by GARs conflicts with normal memory
* or IO memory/port of devices.
*/
int apei_resources_request(struct apei_resources *resources,
const char *desc)
{
struct apei_res *res, *res_bak = NULL;
struct resource *r;
struct apei_resources nvs_resources, arch_res;
int rc;
rc = apei_resources_sub(resources, &apei_resources_all);
if (rc)
return rc;
/*
* Some firmware uses ACPI NVS region, that has been marked as
* busy, so exclude it from APEI resources to avoid false
* conflict.
*/
apei_resources_init(&nvs_resources);
rc = apei_get_nvs_resources(&nvs_resources);
if (rc)
goto nvs_res_fini;
rc = apei_resources_sub(resources, &nvs_resources);
if (rc)
goto nvs_res_fini;
if (arch_apei_filter_addr) {
apei_resources_init(&arch_res);
rc = apei_get_arch_resources(&arch_res);
if (rc)
goto arch_res_fini;
rc = apei_resources_sub(resources, &arch_res);
if (rc)
goto arch_res_fini;
}
rc = -EINVAL;
list_for_each_entry(res, &resources->iomem, list) {
r = request_mem_region(res->start, res->end - res->start,
desc);
if (!r) {
pr_err(APEI_PFX
"Can not request [mem %#010llx-%#010llx] for %s registers\n",
(unsigned long long)res->start,
(unsigned long long)res->end - 1, desc);
res_bak = res;
goto err_unmap_iomem;
}
}
list_for_each_entry(res, &resources->ioport, list) {
r = request_region(res->start, res->end - res->start, desc);
if (!r) {
pr_err(APEI_PFX
"Can not request [io %#06llx-%#06llx] for %s registers\n",
(unsigned long long)res->start,
(unsigned long long)res->end - 1, desc);
res_bak = res;
goto err_unmap_ioport;
}
}
rc = apei_resources_merge(&apei_resources_all, resources);
if (rc) {
pr_err(APEI_PFX "Fail to merge resources!\n");
goto err_unmap_ioport;
}
goto arch_res_fini;
err_unmap_ioport:
list_for_each_entry(res, &resources->ioport, list) {
if (res == res_bak)
break;
release_region(res->start, res->end - res->start);
}
res_bak = NULL;
err_unmap_iomem:
list_for_each_entry(res, &resources->iomem, list) {
if (res == res_bak)
break;
release_mem_region(res->start, res->end - res->start);
}
arch_res_fini:
if (arch_apei_filter_addr)
apei_resources_fini(&arch_res);
nvs_res_fini:
apei_resources_fini(&nvs_resources);
return rc;
}
EXPORT_SYMBOL_GPL(apei_resources_request);
void apei_resources_release(struct apei_resources *resources)
{
int rc;
struct apei_res *res;
list_for_each_entry(res, &resources->iomem, list)
release_mem_region(res->start, res->end - res->start);
list_for_each_entry(res, &resources->ioport, list)
release_region(res->start, res->end - res->start);
rc = apei_resources_sub(&apei_resources_all, resources);
if (rc)
pr_err(APEI_PFX "Fail to sub resources!\n");
}
EXPORT_SYMBOL_GPL(apei_resources_release);
static int apei_check_gar(struct acpi_generic_address *reg, u64 *paddr,
u32 *access_bit_width)
{
u32 bit_width, bit_offset, access_size_code, space_id;
bit_width = reg->bit_width;
bit_offset = reg->bit_offset;
access_size_code = reg->access_width;
space_id = reg->space_id;
*paddr = get_unaligned(&reg->address);
if (!*paddr) {
pr_warn(FW_BUG APEI_PFX
"Invalid physical address in GAR [0x%llx/%u/%u/%u/%u]\n",
*paddr, bit_width, bit_offset, access_size_code,
space_id);
return -EINVAL;
}
if (access_size_code < 1 || access_size_code > 4) {
pr_warn(FW_BUG APEI_PFX
"Invalid access size code in GAR [0x%llx/%u/%u/%u/%u]\n",
*paddr, bit_width, bit_offset, access_size_code,
space_id);
return -EINVAL;
}
*access_bit_width = 1UL << (access_size_code + 2);
/* Fixup common BIOS bug */
if (bit_width == 32 && bit_offset == 0 && (*paddr & 0x03) == 0 &&
*access_bit_width < 32)
*access_bit_width = 32;
else if (bit_width == 64 && bit_offset == 0 && (*paddr & 0x07) == 0 &&
*access_bit_width < 64)
*access_bit_width = 64;
if ((bit_width + bit_offset) > *access_bit_width) {
pr_warn(FW_BUG APEI_PFX
"Invalid bit width + offset in GAR [0x%llx/%u/%u/%u/%u]\n",
*paddr, bit_width, bit_offset, access_size_code,
space_id);
return -EINVAL;
}
if (space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY &&
space_id != ACPI_ADR_SPACE_SYSTEM_IO) {
pr_warn(FW_BUG APEI_PFX
"Invalid address space type in GAR [0x%llx/%u/%u/%u/%u]\n",
*paddr, bit_width, bit_offset, access_size_code,
space_id);
return -EINVAL;
}
return 0;
}
int apei_map_generic_address(struct acpi_generic_address *reg)
{
int rc;
u32 access_bit_width;
u64 address;
rc = apei_check_gar(reg, &address, &access_bit_width);
if (rc)
return rc;
/* IO space doesn't need mapping */
if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO)
return 0;
if (!acpi_os_map_generic_address(reg))
return -ENXIO;
return 0;
}
EXPORT_SYMBOL_GPL(apei_map_generic_address);
/* read GAR in interrupt (including NMI) or process context */
int apei_read(u64 *val, struct acpi_generic_address *reg)
{
int rc;
u32 access_bit_width;
u64 address;
acpi_status status;
rc = apei_check_gar(reg, &address, &access_bit_width);
if (rc)
return rc;
*val = 0;
switch(reg->space_id) {
case ACPI_ADR_SPACE_SYSTEM_MEMORY:
status = acpi_os_read_memory((acpi_physical_address) address,
val, access_bit_width);
if (ACPI_FAILURE(status))
return -EIO;
break;
case ACPI_ADR_SPACE_SYSTEM_IO:
status = acpi_os_read_port(address, (u32 *)val,
access_bit_width);
if (ACPI_FAILURE(status))
return -EIO;
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(apei_read);
/* write GAR in interrupt (including NMI) or process context */
int apei_write(u64 val, struct acpi_generic_address *reg)
{
int rc;
u32 access_bit_width;
u64 address;
acpi_status status;
rc = apei_check_gar(reg, &address, &access_bit_width);
if (rc)
return rc;
switch (reg->space_id) {
case ACPI_ADR_SPACE_SYSTEM_MEMORY:
status = acpi_os_write_memory((acpi_physical_address) address,
val, access_bit_width);
if (ACPI_FAILURE(status))
return -EIO;
break;
case ACPI_ADR_SPACE_SYSTEM_IO:
status = acpi_os_write_port(address, val, access_bit_width);
if (ACPI_FAILURE(status))
return -EIO;
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(apei_write);
static int collect_res_callback(struct apei_exec_context *ctx,
struct acpi_whea_header *entry,
void *data)
{
struct apei_resources *resources = data;
struct acpi_generic_address *reg = &entry->register_region;
u8 ins = entry->instruction;
u32 access_bit_width;
u64 paddr;
int rc;
if (!(ctx->ins_table[ins].flags & APEI_EXEC_INS_ACCESS_REGISTER))
return 0;
rc = apei_check_gar(reg, &paddr, &access_bit_width);
if (rc)
return rc;
switch (reg->space_id) {
case ACPI_ADR_SPACE_SYSTEM_MEMORY:
return apei_res_add(&resources->iomem, paddr,
access_bit_width / 8);
case ACPI_ADR_SPACE_SYSTEM_IO:
return apei_res_add(&resources->ioport, paddr,
access_bit_width / 8);
default:
return -EINVAL;
}
}
/*
* Same register may be used by multiple instructions in GARs, so
* resources are collected before requesting.
*/
int apei_exec_collect_resources(struct apei_exec_context *ctx,
struct apei_resources *resources)
{
return apei_exec_for_each_entry(ctx, collect_res_callback,
resources, NULL);
}
EXPORT_SYMBOL_GPL(apei_exec_collect_resources);
struct dentry *apei_get_debugfs_dir(void)
{
static struct dentry *dapei;
if (!dapei)
dapei = debugfs_create_dir("apei", NULL);
return dapei;
}
EXPORT_SYMBOL_GPL(apei_get_debugfs_dir);
int __weak arch_apei_enable_cmcff(struct acpi_hest_header *hest_hdr,
void *data)
{
return 1;
}
EXPORT_SYMBOL_GPL(arch_apei_enable_cmcff);
void __weak arch_apei_report_mem_error(int sev,
struct cper_sec_mem_err *mem_err)
{
}
EXPORT_SYMBOL_GPL(arch_apei_report_mem_error);
int apei_osc_setup(void)
{
static u8 whea_uuid_str[] = "ed855e0c-6c90-47bf-a62a-26de0fc5ad5c";
acpi_handle handle;
u32 capbuf[3];
struct acpi_osc_context context = {
.uuid_str = whea_uuid_str,
.rev = 1,
.cap.length = sizeof(capbuf),
.cap.pointer = capbuf,
};
capbuf[OSC_QUERY_DWORD] = OSC_QUERY_ENABLE;
capbuf[OSC_SUPPORT_DWORD] = 1;
capbuf[OSC_CONTROL_DWORD] = 0;
if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_SB", &handle))
|| ACPI_FAILURE(acpi_run_osc(handle, &context)))
return -EIO;
else {
kfree(context.ret.pointer);
return 0;
}
}
EXPORT_SYMBOL_GPL(apei_osc_setup);
@@ -0,0 +1,151 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* apei-internal.h - ACPI Platform Error Interface internal
* definitions.
*/
#ifndef APEI_INTERNAL_H
#define APEI_INTERNAL_H
#include <linux/acpi.h>
struct apei_exec_context;
typedef int (*apei_exec_ins_func_t)(struct apei_exec_context *ctx,
struct acpi_whea_header *entry);
#define APEI_EXEC_INS_ACCESS_REGISTER 0x0001
struct apei_exec_ins_type {
u32 flags;
apei_exec_ins_func_t run;
};
struct apei_exec_context {
u32 ip;
u64 value;
u64 var1;
u64 var2;
u64 src_base;
u64 dst_base;
struct apei_exec_ins_type *ins_table;
u32 instructions;
struct acpi_whea_header *action_table;
u32 entries;
};
void apei_exec_ctx_init(struct apei_exec_context *ctx,
struct apei_exec_ins_type *ins_table,
u32 instructions,
struct acpi_whea_header *action_table,
u32 entries);
static inline void apei_exec_ctx_set_input(struct apei_exec_context *ctx,
u64 input)
{
ctx->value = input;
}
static inline u64 apei_exec_ctx_get_output(struct apei_exec_context *ctx)
{
return ctx->value;
}
int __apei_exec_run(struct apei_exec_context *ctx, u8 action, bool optional);
static inline int apei_exec_run(struct apei_exec_context *ctx, u8 action)
{
return __apei_exec_run(ctx, action, 0);
}
/* It is optional whether the firmware provides the action */
static inline int apei_exec_run_optional(struct apei_exec_context *ctx, u8 action)
{
return __apei_exec_run(ctx, action, 1);
}
/* Common instruction implementation */
/* IP has been set in instruction function */
#define APEI_EXEC_SET_IP 1
int apei_map_generic_address(struct acpi_generic_address *reg);
static inline void apei_unmap_generic_address(struct acpi_generic_address *reg)
{
acpi_os_unmap_generic_address(reg);
}
int apei_read(u64 *val, struct acpi_generic_address *reg);
int apei_write(u64 val, struct acpi_generic_address *reg);
int __apei_exec_read_register(struct acpi_whea_header *entry, u64 *val);
int __apei_exec_write_register(struct acpi_whea_header *entry, u64 val);
int apei_exec_read_register(struct apei_exec_context *ctx,
struct acpi_whea_header *entry);
int apei_exec_read_register_value(struct apei_exec_context *ctx,
struct acpi_whea_header *entry);
int apei_exec_write_register(struct apei_exec_context *ctx,
struct acpi_whea_header *entry);
int apei_exec_write_register_value(struct apei_exec_context *ctx,
struct acpi_whea_header *entry);
int apei_exec_noop(struct apei_exec_context *ctx,
struct acpi_whea_header *entry);
int apei_exec_pre_map_gars(struct apei_exec_context *ctx);
int apei_exec_post_unmap_gars(struct apei_exec_context *ctx);
struct apei_resources {
struct list_head iomem;
struct list_head ioport;
};
static inline void apei_resources_init(struct apei_resources *resources)
{
INIT_LIST_HEAD(&resources->iomem);
INIT_LIST_HEAD(&resources->ioport);
}
void apei_resources_fini(struct apei_resources *resources);
int apei_resources_add(struct apei_resources *resources,
unsigned long start, unsigned long size,
bool iomem);
int apei_resources_sub(struct apei_resources *resources1,
struct apei_resources *resources2);
int apei_resources_request(struct apei_resources *resources,
const char *desc);
void apei_resources_release(struct apei_resources *resources);
int apei_exec_collect_resources(struct apei_exec_context *ctx,
struct apei_resources *resources);
struct dentry;
struct dentry *apei_get_debugfs_dir(void);
static inline u32 cper_estatus_len(struct acpi_hest_generic_status *estatus)
{
if (estatus->raw_data_length)
return estatus->raw_data_offset + \
estatus->raw_data_length;
else
return sizeof(*estatus) + estatus->data_length;
}
int apei_osc_setup(void);
int einj_get_available_error_type(u32 *type, int einj_action);
int einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2, u64 param3,
u64 param4);
int einj_cxl_rch_error_inject(u32 type, u32 flags, u64 param1, u64 param2,
u64 param3, u64 param4);
bool einj_is_cxl_error_type(u64 type);
int einj_validate_error_type(u64 type);
#ifndef ACPI_EINJ_CXL_CACHE_CORRECTABLE
#define ACPI_EINJ_CXL_CACHE_CORRECTABLE BIT(12)
#define ACPI_EINJ_CXL_CACHE_UNCORRECTABLE BIT(13)
#define ACPI_EINJ_CXL_CACHE_FATAL BIT(14)
#define ACPI_EINJ_CXL_MEM_CORRECTABLE BIT(15)
#define ACPI_EINJ_CXL_MEM_UNCORRECTABLE BIT(16)
#define ACPI_EINJ_CXL_MEM_FATAL BIT(17)
#endif
#endif
+174
View File
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* APEI Boot Error Record Table (BERT) support
*
* Copyright 2011 Intel Corp.
* Author: Huang Ying <ying.huang@intel.com>
*
* Under normal circumstances, when a hardware error occurs, the error
* handler receives control and processes the error. This gives OSPM a
* chance to process the error condition, report it, and optionally attempt
* recovery. In some cases, the system is unable to process an error.
* For example, system firmware or a management controller may choose to
* reset the system or the system might experience an uncontrolled crash
* or reset.The boot error source is used to report unhandled errors that
* occurred in a previous boot. This mechanism is described in the BERT
* table.
*
* For more information about BERT, please refer to ACPI Specification
* version 4.0, section 17.3.1
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/cper.h>
#include <linux/io.h>
#include "apei-internal.h"
#undef pr_fmt
#define pr_fmt(fmt) "BERT: " fmt
#define ACPI_BERT_PRINT_MAX_RECORDS 5
#define ACPI_BERT_PRINT_MAX_LEN 1024
static int bert_disable __initdata;
/*
* Print "all" the error records in the BERT table, but avoid huge spam to
* the console if the BIOS included oversize records, or too many records.
* Skipping some records here does not lose anything because the full
* data is available to user tools in:
* /sys/firmware/acpi/tables/data/BERT
*/
static void __init bert_print_all(struct acpi_bert_region *region,
unsigned int region_len)
{
struct acpi_hest_generic_status *estatus =
(struct acpi_hest_generic_status *)region;
int remain = region_len;
int printed = 0, skipped = 0;
u32 estatus_len;
while (remain >= sizeof(struct acpi_bert_region)) {
estatus_len = cper_estatus_len(estatus);
if (remain < estatus_len) {
pr_err(FW_BUG "Truncated status block (length: %u).\n",
estatus_len);
break;
}
/* No more error records. */
if (!estatus->block_status)
break;
if (cper_estatus_check(estatus)) {
pr_err(FW_BUG "Invalid error record.\n");
break;
}
if (estatus_len < ACPI_BERT_PRINT_MAX_LEN &&
printed < ACPI_BERT_PRINT_MAX_RECORDS) {
pr_info_once("Error records from previous boot:\n");
cper_estatus_print(KERN_INFO HW_ERR, estatus);
printed++;
} else {
skipped++;
}
/*
* Because the boot error source is "one-time polled" type,
* clear Block Status of current Generic Error Status Block,
* once it's printed.
*/
estatus->block_status = 0;
estatus = (void *)estatus + estatus_len;
remain -= estatus_len;
}
if (skipped)
pr_info(HW_ERR "Skipped %d error records\n", skipped);
if (printed + skipped)
pr_info("Total records found: %d\n", printed + skipped);
}
static int __init setup_bert_disable(char *str)
{
bert_disable = 1;
return 1;
}
__setup("bert_disable", setup_bert_disable);
static int __init bert_check_table(struct acpi_table_bert *bert_tab)
{
if (bert_tab->header.length < sizeof(struct acpi_table_bert) ||
bert_tab->region_length < sizeof(struct acpi_bert_region))
return -EINVAL;
return 0;
}
static int __init bert_init(void)
{
struct apei_resources bert_resources;
struct acpi_bert_region *boot_error_region;
struct acpi_table_bert *bert_tab;
unsigned int region_len;
acpi_status status;
int rc = 0;
if (acpi_disabled)
return 0;
if (bert_disable) {
pr_info("Boot Error Record Table support is disabled.\n");
return 0;
}
status = acpi_get_table(ACPI_SIG_BERT, 0, (struct acpi_table_header **)&bert_tab);
if (status == AE_NOT_FOUND)
return 0;
if (ACPI_FAILURE(status)) {
pr_err("get table failed, %s.\n", acpi_format_exception(status));
return -EINVAL;
}
rc = bert_check_table(bert_tab);
if (rc) {
pr_err(FW_BUG "table invalid.\n");
goto out_put_bert_tab;
}
region_len = bert_tab->region_length;
apei_resources_init(&bert_resources);
rc = apei_resources_add(&bert_resources, bert_tab->address,
region_len, true);
if (rc)
goto out_put_bert_tab;
rc = apei_resources_request(&bert_resources, "APEI BERT");
if (rc)
goto out_fini;
boot_error_region = ioremap_cache(bert_tab->address, region_len);
if (boot_error_region) {
bert_print_all(boot_error_region, region_len);
iounmap(boot_error_region);
} else {
rc = -ENOMEM;
}
apei_resources_release(&bert_resources);
out_fini:
apei_resources_fini(&bert_resources);
out_put_bert_tab:
acpi_put_table((struct acpi_table_header *)bert_tab);
return rc;
}
late_initcall(bert_init);
File diff suppressed because it is too large Load Diff
+113
View File
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* CXL Error INJection support. Used by CXL core to inject
* protocol errors into CXL ports.
*
* Copyright (C) 2023 Advanced Micro Devices, Inc.
*
* Author: Ben Cheatham <benjamin.cheatham@amd.com>
*/
#include <linux/seq_file.h>
#include <linux/pci.h>
#include <cxl/einj.h>
#include "apei-internal.h"
/* Defined in einj-core.c */
extern bool einj_initialized;
static struct { u32 mask; const char *str; } const einj_cxl_error_type_string[] = {
{ ACPI_EINJ_CXL_CACHE_CORRECTABLE, "CXL.cache Protocol Correctable" },
{ ACPI_EINJ_CXL_CACHE_UNCORRECTABLE, "CXL.cache Protocol Uncorrectable non-fatal" },
{ ACPI_EINJ_CXL_CACHE_FATAL, "CXL.cache Protocol Uncorrectable fatal" },
{ ACPI_EINJ_CXL_MEM_CORRECTABLE, "CXL.mem Protocol Correctable" },
{ ACPI_EINJ_CXL_MEM_UNCORRECTABLE, "CXL.mem Protocol Uncorrectable non-fatal" },
{ ACPI_EINJ_CXL_MEM_FATAL, "CXL.mem Protocol Uncorrectable fatal" },
};
int einj_cxl_available_error_type_show(struct seq_file *m, void *v)
{
int cxl_err, rc;
u32 available_error_type = 0;
rc = einj_get_available_error_type(&available_error_type, ACPI_EINJ_GET_ERROR_TYPE);
if (rc)
return rc;
for (int pos = 0; pos < ARRAY_SIZE(einj_cxl_error_type_string); pos++) {
cxl_err = ACPI_EINJ_CXL_CACHE_CORRECTABLE << pos;
if (available_error_type & cxl_err)
seq_printf(m, "0x%08x\t%s\n",
einj_cxl_error_type_string[pos].mask,
einj_cxl_error_type_string[pos].str);
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(einj_cxl_available_error_type_show, "CXL");
static int cxl_dport_get_sbdf(struct pci_dev *dport_dev, u64 *sbdf)
{
struct pci_bus *pbus;
struct pci_host_bridge *bridge;
u64 seg = 0, bus;
pbus = dport_dev->bus;
bridge = pci_find_host_bridge(pbus);
if (!bridge)
return -ENODEV;
if (bridge->domain_nr != PCI_DOMAIN_NR_NOT_SET)
seg = bridge->domain_nr;
bus = pbus->number;
*sbdf = (seg << 24) | (bus << 16) | (dport_dev->devfn << 8);
return 0;
}
int einj_cxl_inject_rch_error(u64 rcrb, u64 type)
{
int rc;
/* Only CXL error types can be specified */
if (!einj_is_cxl_error_type(type))
return -EINVAL;
rc = einj_validate_error_type(type);
if (rc)
return rc;
return einj_cxl_rch_error_inject(type, 0x2, rcrb, GENMASK_ULL(63, 0),
0, 0);
}
EXPORT_SYMBOL_NS_GPL(einj_cxl_inject_rch_error, "CXL");
int einj_cxl_inject_error(struct pci_dev *dport, u64 type)
{
u64 param4 = 0;
int rc;
/* Only CXL error types can be specified */
if (!einj_is_cxl_error_type(type))
return -EINVAL;
rc = einj_validate_error_type(type);
if (rc)
return rc;
rc = cxl_dport_get_sbdf(dport, &param4);
if (rc)
return rc;
return einj_error_inject(type, 0x4, 0, 0, 0, param4);
}
EXPORT_SYMBOL_NS_GPL(einj_cxl_inject_error, "CXL");
bool einj_cxl_is_initialized(void)
{
return einj_initialized;
}
EXPORT_SYMBOL_NS_GPL(einj_cxl_is_initialized, "CXL");
+228
View File
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* APEI Error Record Serialization Table debug support
*
* ERST is a way provided by APEI to save and retrieve hardware error
* information to and from a persistent store. This file provide the
* debugging/testing support for ERST kernel support and firmware
* implementation.
*
* Copyright 2010 Intel Corp.
* Author: Huang Ying <ying.huang@intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <acpi/apei.h>
#include <linux/miscdevice.h>
#include "apei-internal.h"
#define ERST_DBG_PFX "ERST DBG: "
#define ERST_DBG_RECORD_LEN_MAX 0x4000
static void *erst_dbg_buf;
static unsigned int erst_dbg_buf_len;
/* Prevent erst_dbg_read/write from being invoked concurrently */
static DEFINE_MUTEX(erst_dbg_mutex);
static int erst_dbg_open(struct inode *inode, struct file *file)
{
int rc, *pos;
if (erst_disable)
return -ENODEV;
pos = (int *)&file->private_data;
rc = erst_get_record_id_begin(pos);
if (rc)
return rc;
return nonseekable_open(inode, file);
}
static int erst_dbg_release(struct inode *inode, struct file *file)
{
erst_get_record_id_end();
return 0;
}
static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
int rc;
u64 record_id;
u32 record_count;
switch (cmd) {
case APEI_ERST_CLEAR_RECORD:
if (copy_from_user(&record_id, (void __user *)arg,
sizeof(record_id)))
return -EFAULT;
return erst_clear(record_id);
case APEI_ERST_GET_RECORD_COUNT:
rc = erst_get_record_count();
if (rc < 0)
return rc;
record_count = rc;
rc = put_user(record_count, (u32 __user *)arg);
if (rc)
return rc;
return 0;
default:
return -ENOTTY;
}
}
static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
size_t usize, loff_t *off)
{
int rc, *pos;
ssize_t len = 0;
u64 id;
if (*off)
return -EINVAL;
if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
return -EINTR;
pos = (int *)&filp->private_data;
retry_next:
rc = erst_get_record_id_next(pos, &id);
if (rc)
goto out;
/* no more record */
if (id == APEI_ERST_INVALID_RECORD_ID) {
/*
* If the persistent store is empty initially, the function
* 'erst_read' below will return "-ENOENT" value. This causes
* 'retry_next' label is entered again. The returned value
* should be zero indicating the read operation is EOF.
*/
len = 0;
goto out;
}
retry:
rc = len = erst_read_record(id, erst_dbg_buf, erst_dbg_buf_len,
erst_dbg_buf_len, NULL);
/* The record may be cleared by others, try read next record */
if (rc == -ENOENT)
goto retry_next;
if (rc < 0)
goto out;
if (len > ERST_DBG_RECORD_LEN_MAX) {
pr_warn(ERST_DBG_PFX
"Record (ID: 0x%llx) length is too long: %zd\n", id, len);
rc = -EIO;
goto out;
}
if (len > erst_dbg_buf_len) {
void *p;
rc = -ENOMEM;
p = kmalloc(len, GFP_KERNEL);
if (!p)
goto out;
kfree(erst_dbg_buf);
erst_dbg_buf = p;
erst_dbg_buf_len = len;
goto retry;
}
rc = -EINVAL;
if (len > usize)
goto out;
rc = -EFAULT;
if (copy_to_user(ubuf, erst_dbg_buf, len))
goto out;
rc = 0;
out:
mutex_unlock(&erst_dbg_mutex);
return rc ? rc : len;
}
static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
size_t usize, loff_t *off)
{
int rc;
struct cper_record_header *rcd;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (usize > ERST_DBG_RECORD_LEN_MAX) {
pr_err(ERST_DBG_PFX "Too long record to be written\n");
return -EINVAL;
}
if (mutex_lock_interruptible(&erst_dbg_mutex))
return -EINTR;
if (usize > erst_dbg_buf_len) {
void *p;
rc = -ENOMEM;
p = kmalloc(usize, GFP_KERNEL);
if (!p)
goto out;
kfree(erst_dbg_buf);
erst_dbg_buf = p;
erst_dbg_buf_len = usize;
}
if (copy_from_user(erst_dbg_buf, ubuf, usize)) {
rc = -EFAULT;
goto out;
}
rcd = erst_dbg_buf;
rc = -EINVAL;
if (rcd->record_length != usize)
goto out;
rc = erst_write(erst_dbg_buf);
out:
mutex_unlock(&erst_dbg_mutex);
return rc < 0 ? rc : usize;
}
static const struct file_operations erst_dbg_ops = {
.owner = THIS_MODULE,
.open = erst_dbg_open,
.release = erst_dbg_release,
.read = erst_dbg_read,
.write = erst_dbg_write,
.unlocked_ioctl = erst_dbg_ioctl,
};
static struct miscdevice erst_dbg_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "erst_dbg",
.fops = &erst_dbg_ops,
};
static __init int erst_dbg_init(void)
{
if (erst_disable) {
pr_info(ERST_DBG_PFX "ERST support is disabled.\n");
return -ENODEV;
}
return misc_register(&erst_dbg_dev);
}
static __exit void erst_dbg_exit(void)
{
misc_deregister(&erst_dbg_dev);
kfree(erst_dbg_buf);
}
module_init(erst_dbg_init);
module_exit(erst_dbg_exit);
MODULE_AUTHOR("Huang Ying");
MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NVIDIA GHES vendor record handler
*
* Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/uuid.h>
#include <acpi/ghes.h>
static const guid_t nvidia_sec_guid =
GUID_INIT(0x6d5244f2, 0x2712, 0x11ec,
0xbe, 0xa7, 0xcb, 0x3f, 0xdb, 0x95, 0xc7, 0x86);
struct cper_sec_nvidia {
char signature[16];
__le16 error_type;
__le16 error_instance;
u8 severity;
u8 socket;
u8 number_regs;
u8 reserved;
__le64 instance_base;
struct {
__le64 addr;
__le64 val;
} regs[] __counted_by(number_regs);
};
struct nvidia_ghes_private {
struct notifier_block nb;
struct device *dev;
};
static void nvidia_ghes_print_error(struct device *dev,
const struct cper_sec_nvidia *nvidia_err,
size_t error_data_length, bool fatal)
{
const char *level = fatal ? KERN_ERR : KERN_INFO;
size_t min_size;
dev_printk(level, dev, "signature: %.16s\n", nvidia_err->signature);
dev_printk(level, dev, "error_type: %u\n", le16_to_cpu(nvidia_err->error_type));
dev_printk(level, dev, "error_instance: %u\n", le16_to_cpu(nvidia_err->error_instance));
dev_printk(level, dev, "severity: %u\n", nvidia_err->severity);
dev_printk(level, dev, "socket: %u\n", nvidia_err->socket);
dev_printk(level, dev, "number_regs: %u\n", nvidia_err->number_regs);
dev_printk(level, dev, "instance_base: 0x%016llx\n",
le64_to_cpu(nvidia_err->instance_base));
if (nvidia_err->number_regs == 0)
return;
/*
* Validate that all registers fit within error_data_length.
* Each register pair is two little-endian u64s.
*/
min_size = struct_size(nvidia_err, regs, nvidia_err->number_regs);
if (error_data_length < min_size) {
dev_err(dev, "Invalid number_regs %u (section size %zu, need %zu)\n",
nvidia_err->number_regs, error_data_length, min_size);
return;
}
for (int i = 0; i < nvidia_err->number_regs; i++)
dev_printk(level, dev, "register[%d]: address=0x%016llx value=0x%016llx\n",
i, le64_to_cpu(nvidia_err->regs[i].addr),
le64_to_cpu(nvidia_err->regs[i].val));
}
static int nvidia_ghes_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
struct acpi_hest_generic_data *gdata = data;
struct nvidia_ghes_private *priv;
const struct cper_sec_nvidia *nvidia_err;
guid_t sec_guid;
import_guid(&sec_guid, gdata->section_type);
if (!guid_equal(&sec_guid, &nvidia_sec_guid))
return NOTIFY_DONE;
priv = container_of(nb, struct nvidia_ghes_private, nb);
if (acpi_hest_get_error_length(gdata) < sizeof(*nvidia_err)) {
dev_err(priv->dev, "Section too small (%d < %zu)\n",
acpi_hest_get_error_length(gdata), sizeof(*nvidia_err));
return NOTIFY_OK;
}
nvidia_err = acpi_hest_get_payload(gdata);
if (event >= GHES_SEV_RECOVERABLE)
dev_err(priv->dev, "NVIDIA CPER section, error_data_length: %u\n",
acpi_hest_get_error_length(gdata));
else
dev_info(priv->dev, "NVIDIA CPER section, error_data_length: %u\n",
acpi_hest_get_error_length(gdata));
nvidia_ghes_print_error(priv->dev, nvidia_err, acpi_hest_get_error_length(gdata),
event >= GHES_SEV_RECOVERABLE);
return NOTIFY_OK;
}
static int nvidia_ghes_probe(struct platform_device *pdev)
{
struct nvidia_ghes_private *priv;
int ret;
priv = devm_kmalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
*priv = (struct nvidia_ghes_private) {
.nb.notifier_call = nvidia_ghes_notify,
.dev = &pdev->dev,
};
ret = devm_ghes_register_vendor_record_notifier(&pdev->dev, &priv->nb);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"Failed to register NVIDIA GHES vendor record notifier\n");
return 0;
}
static const struct acpi_device_id nvidia_ghes_acpi_match[] = {
{ "NVDA2012" },
{ }
};
MODULE_DEVICE_TABLE(acpi, nvidia_ghes_acpi_match);
static struct platform_driver nvidia_ghes_driver = {
.driver = {
.name = "nvidia-ghes",
.acpi_match_table = nvidia_ghes_acpi_match,
},
.probe = nvidia_ghes_probe,
};
module_platform_driver(nvidia_ghes_driver);
MODULE_AUTHOR("Kai-Heng Feng <kaihengf@nvidia.com>");
MODULE_DESCRIPTION("NVIDIA GHES vendor CPER record handler");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2025 Intel Corporation. All rights reserved
#include <linux/printk.h>
#include <linux/aer.h>
#include <cxl/event.h>
int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err)
{
if (!(prot_err->valid_bits & PROT_ERR_VALID_AGENT_ADDRESS)) {
pr_err_ratelimited("CXL CPER invalid agent type\n");
return -EINVAL;
}
if (!(prot_err->valid_bits & PROT_ERR_VALID_ERROR_LOG)) {
pr_err_ratelimited("CXL CPER invalid protocol error log\n");
return -EINVAL;
}
if (prot_err->err_len != sizeof(struct cxl_ras_capability_regs)) {
pr_err_ratelimited("CXL CPER invalid RAS Cap size (%u)\n",
prot_err->err_len);
return -EINVAL;
}
if ((prot_err->agent_type == RCD || prot_err->agent_type == DEVICE ||
prot_err->agent_type == LD || prot_err->agent_type == FMLD) &&
!(prot_err->valid_bits & PROT_ERR_VALID_SERIAL_NUMBER))
pr_warn_ratelimited(FW_WARN
"CXL CPER no device serial number\n");
return 0;
}
EXPORT_SYMBOL_GPL(cxl_cper_sec_prot_err_valid);
int cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd,
struct cxl_cper_sec_prot_err *prot_err,
int severity)
{
u8 *dvsec_start, *cap_start;
switch (prot_err->agent_type) {
case RCD:
case DEVICE:
case LD:
case FMLD:
case RP:
case DSP:
case USP:
memcpy(&wd->prot_err, prot_err, sizeof(wd->prot_err));
dvsec_start = (u8 *)(prot_err + 1);
cap_start = dvsec_start + prot_err->dvsec_len;
memcpy(&wd->ras_cap, cap_start, sizeof(wd->ras_cap));
wd->severity = cper_severity_to_aer(severity);
break;
default:
pr_err_ratelimited("CXL CPER invalid agent type: %d\n",
prot_err->agent_type);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(cxl_cper_setup_prot_err_work_data);
+326
View File
@@ -0,0 +1,326 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* APEI Hardware Error Source Table support
*
* HEST describes error sources in detail; communicates operational
* parameters (i.e. severity levels, masking bits, and threshold
* values) to Linux as necessary. It also allows the BIOS to report
* non-standard error sources to Linux (for example, chipset-specific
* error registers).
*
* For more information about HEST, please refer to ACPI Specification
* version 4.0, section 17.3.2.
*
* Copyright 2009 Intel Corp.
* Author: Huang Ying <ying.huang@intel.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/kdebug.h>
#include <linux/highmem.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <acpi/apei.h>
#include <acpi/ghes.h>
#include "apei-internal.h"
#define HEST_PFX "HEST: "
int hest_disable;
EXPORT_SYMBOL_GPL(hest_disable);
/* HEST table parsing */
static struct acpi_table_hest *__read_mostly hest_tab;
/*
* Since GHES_ASSIST is not supported, skip initialization of GHES_ASSIST
* structures for MCA.
* During HEST parsing, detected MCA error sources are cached from early
* table entries so that the Flags and Source Id fields from these cached
* values are then referred to in later table entries to determine if the
* encountered GHES_ASSIST structure should be initialized.
*/
static struct {
struct acpi_hest_ia_corrected *cmc;
struct acpi_hest_ia_machine_check *mc;
struct acpi_hest_ia_deferred_check *dmc;
} mces;
static const int hest_esrc_len_tab[ACPI_HEST_TYPE_RESERVED] = {
[ACPI_HEST_TYPE_IA32_CHECK] = -1, /* need further calculation */
[ACPI_HEST_TYPE_IA32_CORRECTED_CHECK] = -1,
[ACPI_HEST_TYPE_IA32_NMI] = sizeof(struct acpi_hest_ia_nmi),
[ACPI_HEST_TYPE_AER_ROOT_PORT] = sizeof(struct acpi_hest_aer_root),
[ACPI_HEST_TYPE_AER_ENDPOINT] = sizeof(struct acpi_hest_aer),
[ACPI_HEST_TYPE_AER_BRIDGE] = sizeof(struct acpi_hest_aer_bridge),
[ACPI_HEST_TYPE_GENERIC_ERROR] = sizeof(struct acpi_hest_generic),
[ACPI_HEST_TYPE_GENERIC_ERROR_V2] = sizeof(struct acpi_hest_generic_v2),
[ACPI_HEST_TYPE_IA32_DEFERRED_CHECK] = -1,
};
static inline bool is_generic_error(struct acpi_hest_header *hest_hdr)
{
return hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR ||
hest_hdr->type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;
}
static int hest_esrc_len(struct acpi_hest_header *hest_hdr)
{
u16 hest_type = hest_hdr->type;
int len;
if (hest_type >= ACPI_HEST_TYPE_RESERVED)
return 0;
len = hest_esrc_len_tab[hest_type];
if (hest_type == ACPI_HEST_TYPE_IA32_CORRECTED_CHECK) {
struct acpi_hest_ia_corrected *cmc;
cmc = (struct acpi_hest_ia_corrected *)hest_hdr;
len = sizeof(*cmc) + cmc->num_hardware_banks *
sizeof(struct acpi_hest_ia_error_bank);
mces.cmc = cmc;
} else if (hest_type == ACPI_HEST_TYPE_IA32_CHECK) {
struct acpi_hest_ia_machine_check *mc;
mc = (struct acpi_hest_ia_machine_check *)hest_hdr;
len = sizeof(*mc) + mc->num_hardware_banks *
sizeof(struct acpi_hest_ia_error_bank);
mces.mc = mc;
} else if (hest_type == ACPI_HEST_TYPE_IA32_DEFERRED_CHECK) {
struct acpi_hest_ia_deferred_check *mc;
mc = (struct acpi_hest_ia_deferred_check *)hest_hdr;
len = sizeof(*mc) + mc->num_hardware_banks *
sizeof(struct acpi_hest_ia_error_bank);
mces.dmc = mc;
}
BUG_ON(len == -1);
return len;
};
/*
* GHES and GHESv2 structures share the same format, starting from
* Source Id and ending in Error Status Block Length (inclusive).
*/
static bool is_ghes_assist_struct(struct acpi_hest_header *hest_hdr)
{
struct acpi_hest_generic *ghes;
u16 related_source_id;
if (hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR &&
hest_hdr->type != ACPI_HEST_TYPE_GENERIC_ERROR_V2)
return false;
ghes = (struct acpi_hest_generic *)hest_hdr;
related_source_id = ghes->related_source_id;
if (mces.cmc && mces.cmc->flags & ACPI_HEST_GHES_ASSIST &&
related_source_id == mces.cmc->header.source_id)
return true;
if (mces.mc && mces.mc->flags & ACPI_HEST_GHES_ASSIST &&
related_source_id == mces.mc->header.source_id)
return true;
if (mces.dmc && mces.dmc->flags & ACPI_HEST_GHES_ASSIST &&
related_source_id == mces.dmc->header.source_id)
return true;
return false;
}
typedef int (*apei_hest_func_t)(struct acpi_hest_header *hest_hdr, void *data);
static int apei_hest_parse(apei_hest_func_t func, void *data)
{
struct acpi_hest_header *hest_hdr;
int i, rc, len;
if (hest_disable || !hest_tab)
return -EINVAL;
hest_hdr = (struct acpi_hest_header *)(hest_tab + 1);
for (i = 0; i < hest_tab->error_source_count; i++) {
len = hest_esrc_len(hest_hdr);
if (!len) {
pr_warn(FW_WARN HEST_PFX
"Unknown or unused hardware error source "
"type: %d for hardware error source: %d.\n",
hest_hdr->type, hest_hdr->source_id);
return -EINVAL;
}
if ((void *)hest_hdr + len >
(void *)hest_tab + hest_tab->header.length) {
pr_warn(FW_BUG HEST_PFX
"Table contents overflow for hardware error source: %d.\n",
hest_hdr->source_id);
return -EINVAL;
}
if (is_ghes_assist_struct(hest_hdr)) {
hest_hdr = (void *)hest_hdr + len;
continue;
}
rc = func(hest_hdr, data);
if (rc)
return rc;
hest_hdr = (void *)hest_hdr + len;
}
return 0;
}
/*
* Check if firmware advertises firmware first mode. We need FF bit to be set
* along with a set of MC banks which work in FF mode.
*/
static int __init hest_parse_cmc(struct acpi_hest_header *hest_hdr, void *data)
{
if (hest_hdr->type != ACPI_HEST_TYPE_IA32_CORRECTED_CHECK)
return 0;
if (!acpi_disable_cmcff)
return !arch_apei_enable_cmcff(hest_hdr, data);
return 0;
}
struct ghes_arr {
struct platform_device **ghes_devs;
unsigned int count;
};
static int __init hest_parse_ghes_count(struct acpi_hest_header *hest_hdr, void *data)
{
int *count = data;
if (is_generic_error(hest_hdr))
(*count)++;
return 0;
}
static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)
{
struct platform_device *ghes_dev;
struct ghes_arr *ghes_arr = data;
int rc, i;
if (!is_generic_error(hest_hdr))
return 0;
if (!((struct acpi_hest_generic *)hest_hdr)->enabled)
return 0;
for (i = 0; i < ghes_arr->count; i++) {
struct acpi_hest_header *hdr;
ghes_dev = ghes_arr->ghes_devs[i];
hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data;
if (hdr->source_id == hest_hdr->source_id) {
pr_warn(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n",
hdr->source_id);
return -EIO;
}
}
ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);
if (!ghes_dev)
return -ENOMEM;
rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));
if (rc)
goto err;
rc = platform_device_add(ghes_dev);
if (rc)
goto err;
ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;
return 0;
err:
platform_device_put(ghes_dev);
return rc;
}
static int __init hest_ghes_dev_register(unsigned int ghes_count)
{
int rc, i;
struct ghes_arr ghes_arr;
ghes_arr.count = 0;
ghes_arr.ghes_devs = kmalloc_array(ghes_count, sizeof(void *),
GFP_KERNEL);
if (!ghes_arr.ghes_devs)
return -ENOMEM;
rc = apei_hest_parse(hest_parse_ghes, &ghes_arr);
if (rc)
goto err;
rc = ghes_estatus_pool_init(ghes_count);
if (rc)
goto err;
out:
kfree(ghes_arr.ghes_devs);
return rc;
err:
for (i = 0; i < ghes_arr.count; i++)
platform_device_unregister(ghes_arr.ghes_devs[i]);
goto out;
}
static int __init setup_hest_disable(char *str)
{
hest_disable = HEST_DISABLED;
return 1;
}
__setup("hest_disable", setup_hest_disable);
void __init acpi_hest_init(void)
{
acpi_status status;
int rc;
unsigned int ghes_count = 0;
if (hest_disable) {
pr_info(HEST_PFX "Table parsing disabled.\n");
return;
}
status = acpi_get_table(ACPI_SIG_HEST, 0,
(struct acpi_table_header **)&hest_tab);
if (status == AE_NOT_FOUND) {
hest_disable = HEST_NOT_FOUND;
return;
} else if (ACPI_FAILURE(status)) {
const char *msg = acpi_format_exception(status);
pr_err(HEST_PFX "Failed to get table, %s\n", msg);
hest_disable = HEST_DISABLED;
return;
}
rc = apei_hest_parse(hest_parse_cmc, NULL);
if (rc)
goto err;
if (!ghes_disable) {
rc = apei_hest_parse(hest_parse_ghes_count, &ghes_count);
if (rc)
goto err;
if (ghes_count)
rc = hest_ghes_dev_register(ghes_count);
if (rc)
goto err;
}
pr_info(HEST_PFX "Table parsing has been initialized.\n");
return;
err:
hest_disable = HEST_DISABLED;
acpi_put_table((struct acpi_table_header *)hest_tab);
}