Merge branch 'acpi-apei'

Merge ACPI APEI updates for 6.17-rc1:

 - Fix iomem-related sparse warnings in the APEI EINJ driver (Zaid
   Alali, Tony Luck)

 - Add EINJv2 error injection support to the APEI EINJ driver (Zaid
   Alali)

 - Fix memory corruption in error_type_set() in the APEI EINJ driver (Dan
   Carpenter)

 - Fix less than zero comparison on a size_t variable in the APEI EINJ
   driver (Colin Ian King)

 - Fix check and iounmap of an uninitialized pointer in the APEI EINJ
   driver (Colin Ian King)

 - Add TAINT_MACHINE_CHECK to the GHES panic path in APEI to improve
   diagnostics and post-mortem analysis (Breno Leitao)

 - Update APEI reviewer records in MAINTAINERS (Rafael Wysocki)

 - Fix the handling of synchronous uncorrected memory errors in APEI
   (Shuai Xue)

* acpi-apei:
  ACPI: APEI: handle synchronous exceptions in task work
  ACPI: APEI: send SIGBUS to current task if synchronous memory error not recovered
  ACPI: APEI: MAINTAINERS: Update reviewers for APEI
  ACPI: APEI: EINJ: Fix trigger actions
  ACPI: APEI: GHES: add TAINT_MACHINE_CHECK on GHES panic path
  ACPI: APEI: EINJ: Fix check and iounmap of uninitialized pointer p
  ACPI: APEI: EINJ: Fix less than zero comparison on a size_t variable
  ACPI: APEI: EINJ: prevent memory corruption in error_type_set()
  ACPI: APEI: EINJ: Update the documentation for EINJv2 support
  ACPI: APEI: EINJ: Enable EINJv2 error injections
  ACPI: APEI: EINJ: Create debugfs files to enter device id and syndrome
  ACPI: APEI: EINJ: Discover EINJv2 parameters
  ACPI: APEI: EINJ: Add einjv2 extension struct
  ACPI: APEI: EINJ: Enable the discovery of EINJv2 capabilities
  ACPI: APEI: EINJ: Fix kernel test sparse warnings
This commit is contained in:
Rafael J. Wysocki
2025-07-22 15:50:20 +02:00
9 changed files with 413 additions and 122 deletions
@@ -59,6 +59,9 @@ The following files belong to it:
0x00000200 Platform Correctable
0x00000400 Platform Uncorrectable non-fatal
0x00000800 Platform Uncorrectable fatal
V2_0x00000001 EINJV2 Processor Error
V2_0x00000002 EINJV2 Memory Error
V2_0x00000004 EINJV2 PCI Express Error
================ ===================================
The format of the file contents are as above, except present are only
@@ -88,6 +91,8 @@ The following files belong to it:
Memory address and mask valid (param1 and param2).
Bit 2
PCIe (seg,bus,dev,fn) valid (see param4 below).
Bit 3
EINJv2 extension structure is valid
If set to zero, legacy behavior is mimicked where the type of
injection specifies just one bit set, and param1 is multiplexed.
@@ -122,6 +127,13 @@ The following files belong to it:
this actually works depends on what operations the BIOS actually
includes in the trigger phase.
- component_id0 .. component_idN, component_syndrome0 .. component_syndromeN
These files are used to set the "Component Array" field
of the EINJv2 Extension Structure. Each holds a 128-bit
hex value. Writing just a newline to any of these files
sets an invalid (all-ones) value.
CXL error types are supported from ACPI 6.5 onwards (given a CXL port
is present). The EINJ user interface for CXL error types is at
<debugfs mount point>/cxl. The following files belong to it:
@@ -194,6 +206,27 @@ An error injection example::
# echo 0x8 > error_type # Choose correctable memory error
# echo 1 > error_inject # Inject now
An EINJv2 error injection example::
# cd /sys/kernel/debug/apei/einj
# cat available_error_type # See which errors can be injected
0x00000002 Processor Uncorrectable non-fatal
0x00000008 Memory Correctable
0x00000010 Memory Uncorrectable non-fatal
V2_0x00000001 EINJV2 Processor Error
V2_0x00000002 EINJV2 Memory Error
# echo 0x12345000 > param1 # Set memory address for injection
# echo 0xfffffffffffff000 > param2 # Range - anywhere in this page
# echo 0x1 > component_id0 # First device ID
# echo 0x4 > component_syndrome0 # First error syndrome
# echo 0x2 > component_id1 # Second device ID
# echo 0x4 > component_syndrome1 # Second error syndrome
# echo '' > component_id2 # Mark id2 invalid to terminate list
# echo V2_0x2 > error_type # Choose EINJv2 memory error
# echo 0xa > flags # set flags to indicate EINJv2
# echo 1 > error_inject # Inject now
You should see something like this in dmesg::
[22715.830801] EDAC sbridge MC3: HANDLING MCE MEMORY ERROR
+3 -2
View File
@@ -306,10 +306,11 @@ F: tools/power/acpi/
ACPI APEI
M: "Rafael J. Wysocki" <rafael@kernel.org>
R: Len Brown <lenb@kernel.org>
R: James Morse <james.morse@arm.com>
R: Tony Luck <tony.luck@intel.com>
R: Borislav Petkov <bp@alien8.de>
R: Hanjun Guo <guohanjun@huawei.com>
R: Mauro Carvalho Chehab <mchehab@kernel.org>
R: Shuai Xue <xueshuai@linux.alibaba.com>
L: linux-acpi@vger.kernel.org
F: drivers/acpi/apei/
+1 -1
View File
@@ -131,7 +131,7 @@ static inline u32 cper_estatus_len(struct acpi_hest_generic_status *estatus)
int apei_osc_setup(void);
int einj_get_available_error_type(u32 *type);
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,
+318 -68
View File
@@ -33,6 +33,8 @@
#define SLEEP_UNIT_MAX 5000 /* 5ms */
/* Firmware should respond within 1 seconds */
#define FIRMWARE_TIMEOUT (1 * USEC_PER_SEC)
#define COMPONENT_LEN 16
#define ACPI65_EINJV2_SUPP BIT(30)
#define ACPI5_VENDOR_BIT BIT(31)
#define MEM_ERROR_MASK (ACPI_EINJ_MEMORY_CORRECTABLE | \
ACPI_EINJ_MEMORY_UNCORRECTABLE | \
@@ -49,6 +51,28 @@
*/
static int acpi5;
struct syndrome_array {
union {
u8 acpi_id[COMPONENT_LEN];
u8 device_id[COMPONENT_LEN];
u8 pcie_sbdf[COMPONENT_LEN];
u8 vendor_id[COMPONENT_LEN];
} comp_id;
union {
u8 proc_synd[COMPONENT_LEN];
u8 mem_synd[COMPONENT_LEN];
u8 pcie_synd[COMPONENT_LEN];
u8 vendor_synd[COMPONENT_LEN];
} comp_synd;
};
struct einjv2_extension_struct {
u32 length;
u16 revision;
u16 component_arr_count;
struct syndrome_array component_arr[] __counted_by(component_arr_count);
};
struct set_error_type_with_address {
u32 type;
u32 vendor_extension;
@@ -57,11 +81,13 @@ struct set_error_type_with_address {
u64 memory_address;
u64 memory_address_range;
u32 pcie_sbdf;
struct einjv2_extension_struct einjv2_struct;
};
enum {
SETWA_FLAGS_APICID = 1,
SETWA_FLAGS_MEM = 2,
SETWA_FLAGS_PCIE_SBDF = 4,
SETWA_FLAGS_EINJV2 = 8,
};
/*
@@ -83,7 +109,10 @@ static struct debugfs_blob_wrapper vendor_blob;
static struct debugfs_blob_wrapper vendor_errors;
static char vendor_dev[64];
static u32 max_nr_components;
static u32 available_error_type;
static u32 available_error_type_v2;
static struct syndrome_array *syndrome_data;
/*
* Some BIOSes allow parameters to the SET_ERROR_TYPE entries in the
@@ -151,7 +180,9 @@ static DEFINE_MUTEX(einj_mutex);
*/
bool einj_initialized __ro_after_init;
static void *einj_param;
static void __iomem *einj_param;
static u32 v5param_size;
static bool is_v2;
static void einj_exec_ctx_init(struct apei_exec_context *ctx)
{
@@ -159,13 +190,13 @@ static void einj_exec_ctx_init(struct apei_exec_context *ctx)
EINJ_TAB_ENTRY(einj_tab), einj_tab->entries);
}
static int __einj_get_available_error_type(u32 *type)
static int __einj_get_available_error_type(u32 *type, int einj_action)
{
struct apei_exec_context ctx;
int rc;
einj_exec_ctx_init(&ctx);
rc = apei_exec_run(&ctx, ACPI_EINJ_GET_ERROR_TYPE);
rc = apei_exec_run(&ctx, einj_action);
if (rc)
return rc;
*type = apei_exec_ctx_get_output(&ctx);
@@ -174,17 +205,34 @@ static int __einj_get_available_error_type(u32 *type)
}
/* Get error injection capabilities of the platform */
int einj_get_available_error_type(u32 *type)
int einj_get_available_error_type(u32 *type, int einj_action)
{
int rc;
mutex_lock(&einj_mutex);
rc = __einj_get_available_error_type(type);
rc = __einj_get_available_error_type(type, einj_action);
mutex_unlock(&einj_mutex);
return rc;
}
static int einj_get_available_error_types(u32 *type1, u32 *type2)
{
int rc;
rc = einj_get_available_error_type(type1, ACPI_EINJ_GET_ERROR_TYPE);
if (rc)
return rc;
if (*type1 & ACPI65_EINJV2_SUPP) {
rc = einj_get_available_error_type(type2,
ACPI_EINJV2_GET_ERROR_TYPE);
if (rc)
return rc;
}
return 0;
}
static int einj_timedout(u64 *t)
{
if ((s64)*t < SLEEP_UNIT_MIN) {
@@ -216,24 +264,26 @@ static void check_vendor_extension(u64 paddr,
struct set_error_type_with_address *v5param)
{
int offset = v5param->vendor_extension;
struct vendor_error_type_extension *v;
struct vendor_error_type_extension v;
struct vendor_error_type_extension __iomem *p;
u32 sbdf;
if (!offset)
return;
v = acpi_os_map_iomem(paddr + offset, sizeof(*v));
if (!v)
p = acpi_os_map_iomem(paddr + offset, sizeof(*p));
if (!p)
return;
get_oem_vendor_struct(paddr, offset, v);
sbdf = v->pcie_sbdf;
memcpy_fromio(&v, p, sizeof(v));
get_oem_vendor_struct(paddr, offset, &v);
sbdf = v.pcie_sbdf;
sprintf(vendor_dev, "%x:%x:%x.%x vendor_id=%x device_id=%x rev_id=%x\n",
sbdf >> 24, (sbdf >> 16) & 0xff,
(sbdf >> 11) & 0x1f, (sbdf >> 8) & 0x7,
v->vendor_id, v->device_id, v->rev_id);
acpi_os_unmap_iomem(v, sizeof(*v));
v.vendor_id, v.device_id, v.rev_id);
acpi_os_unmap_iomem(p, sizeof(v));
}
static void *einj_get_parameter_address(void)
static void __iomem *einj_get_parameter_address(void)
{
int i;
u64 pa_v4 = 0, pa_v5 = 0;
@@ -254,26 +304,50 @@ static void *einj_get_parameter_address(void)
entry++;
}
if (pa_v5) {
struct set_error_type_with_address *v5param;
struct set_error_type_with_address v5param;
struct set_error_type_with_address __iomem *p;
v5param = acpi_os_map_iomem(pa_v5, sizeof(*v5param));
if (v5param) {
v5param_size = sizeof(v5param);
p = acpi_os_map_iomem(pa_v5, sizeof(*p));
if (p) {
int offset, len;
memcpy_fromio(&v5param, p, v5param_size);
acpi5 = 1;
check_vendor_extension(pa_v5, v5param);
return v5param;
check_vendor_extension(pa_v5, &v5param);
if (available_error_type & ACPI65_EINJV2_SUPP) {
len = v5param.einjv2_struct.length;
offset = offsetof(struct einjv2_extension_struct, component_arr);
max_nr_components = (len - offset) /
sizeof(v5param.einjv2_struct.component_arr[0]);
/*
* The first call to acpi_os_map_iomem above does not include the
* component array, instead it is used to read and calculate maximum
* number of components supported by the system. Below, the mapping
* is expanded to include the component array.
*/
acpi_os_unmap_iomem(p, v5param_size);
offset = offsetof(struct set_error_type_with_address, einjv2_struct);
v5param_size = offset + struct_size(&v5param.einjv2_struct,
component_arr, max_nr_components);
p = acpi_os_map_iomem(pa_v5, v5param_size);
}
return p;
}
}
if (param_extension && pa_v4) {
struct einj_parameter *v4param;
struct einj_parameter v4param;
struct einj_parameter __iomem *p;
v4param = acpi_os_map_iomem(pa_v4, sizeof(*v4param));
if (!v4param)
p = acpi_os_map_iomem(pa_v4, sizeof(*p));
if (!p)
return NULL;
if (v4param->reserved1 || v4param->reserved2) {
acpi_os_unmap_iomem(v4param, sizeof(*v4param));
memcpy_fromio(&v4param, p, sizeof(v4param));
if (v4param.reserved1 || v4param.reserved2) {
acpi_os_unmap_iomem(p, sizeof(v4param));
return NULL;
}
return v4param;
return p;
}
return NULL;
@@ -319,7 +393,8 @@ static struct acpi_generic_address *einj_get_trigger_parameter_region(
static int __einj_error_trigger(u64 trigger_paddr, u32 type,
u64 param1, u64 param2)
{
struct acpi_einj_trigger *trigger_tab = NULL;
struct acpi_einj_trigger trigger_tab;
struct acpi_einj_trigger *full_trigger_tab;
struct apei_exec_context trigger_ctx;
struct apei_resources trigger_resources;
struct acpi_whea_header *trigger_entry;
@@ -327,54 +402,60 @@ static int __einj_error_trigger(u64 trigger_paddr, u32 type,
u32 table_size;
int rc = -EIO;
struct acpi_generic_address *trigger_param_region = NULL;
struct acpi_einj_trigger __iomem *p = NULL;
r = request_mem_region(trigger_paddr, sizeof(*trigger_tab),
r = request_mem_region(trigger_paddr, sizeof(trigger_tab),
"APEI EINJ Trigger Table");
if (!r) {
pr_err("Can not request [mem %#010llx-%#010llx] for Trigger table\n",
(unsigned long long)trigger_paddr,
(unsigned long long)trigger_paddr +
sizeof(*trigger_tab) - 1);
sizeof(trigger_tab) - 1);
goto out;
}
trigger_tab = ioremap_cache(trigger_paddr, sizeof(*trigger_tab));
if (!trigger_tab) {
p = ioremap_cache(trigger_paddr, sizeof(*p));
if (!p) {
pr_err("Failed to map trigger table!\n");
goto out_rel_header;
}
rc = einj_check_trigger_header(trigger_tab);
memcpy_fromio(&trigger_tab, p, sizeof(trigger_tab));
rc = einj_check_trigger_header(&trigger_tab);
if (rc) {
pr_warn(FW_BUG "Invalid trigger error action table.\n");
goto out_rel_header;
}
/* No action structures in the TRIGGER_ERROR table, nothing to do */
if (!trigger_tab->entry_count)
if (!trigger_tab.entry_count)
goto out_rel_header;
rc = -EIO;
table_size = trigger_tab->table_size;
r = request_mem_region(trigger_paddr + sizeof(*trigger_tab),
table_size - sizeof(*trigger_tab),
table_size = trigger_tab.table_size;
full_trigger_tab = kmalloc(table_size, GFP_KERNEL);
if (!full_trigger_tab)
goto out_rel_header;
r = request_mem_region(trigger_paddr + sizeof(trigger_tab),
table_size - sizeof(trigger_tab),
"APEI EINJ Trigger Table");
if (!r) {
pr_err("Can not request [mem %#010llx-%#010llx] for Trigger Table Entry\n",
(unsigned long long)trigger_paddr + sizeof(*trigger_tab),
(unsigned long long)trigger_paddr + sizeof(trigger_tab),
(unsigned long long)trigger_paddr + table_size - 1);
goto out_rel_header;
goto out_free_trigger_tab;
}
iounmap(trigger_tab);
trigger_tab = ioremap_cache(trigger_paddr, table_size);
if (!trigger_tab) {
iounmap(p);
p = ioremap_cache(trigger_paddr, table_size);
if (!p) {
pr_err("Failed to map trigger table!\n");
goto out_rel_entry;
}
memcpy_fromio(full_trigger_tab, p, table_size);
trigger_entry = (struct acpi_whea_header *)
((char *)trigger_tab + sizeof(struct acpi_einj_trigger));
((char *)full_trigger_tab + sizeof(struct acpi_einj_trigger));
apei_resources_init(&trigger_resources);
apei_exec_ctx_init(&trigger_ctx, einj_ins_type,
ARRAY_SIZE(einj_ins_type),
trigger_entry, trigger_tab->entry_count);
trigger_entry, trigger_tab.entry_count);
rc = apei_exec_collect_resources(&trigger_ctx, &trigger_resources);
if (rc)
goto out_fini;
@@ -392,7 +473,7 @@ static int __einj_error_trigger(u64 trigger_paddr, u32 type,
apei_resources_init(&addr_resources);
trigger_param_region = einj_get_trigger_parameter_region(
trigger_tab, param1, param2);
full_trigger_tab, param1, param2);
if (trigger_param_region) {
rc = apei_resources_add(&addr_resources,
trigger_param_region->address,
@@ -421,23 +502,33 @@ out_release:
out_fini:
apei_resources_fini(&trigger_resources);
out_rel_entry:
release_mem_region(trigger_paddr + sizeof(*trigger_tab),
table_size - sizeof(*trigger_tab));
release_mem_region(trigger_paddr + sizeof(trigger_tab),
table_size - sizeof(trigger_tab));
out_free_trigger_tab:
kfree(full_trigger_tab);
out_rel_header:
release_mem_region(trigger_paddr, sizeof(*trigger_tab));
release_mem_region(trigger_paddr, sizeof(trigger_tab));
out:
if (trigger_tab)
iounmap(trigger_tab);
if (p)
iounmap(p);
return rc;
}
static bool is_end_of_list(u8 *val)
{
for (int i = 0; i < COMPONENT_LEN; ++i) {
if (val[i] != 0xFF)
return false;
}
return true;
}
static int __einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2,
u64 param3, u64 param4)
{
struct apei_exec_context ctx;
u64 val, trigger_paddr, timeout = FIRMWARE_TIMEOUT;
int rc;
int i, rc;
einj_exec_ctx_init(&ctx);
@@ -446,8 +537,10 @@ static int __einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2,
return rc;
apei_exec_ctx_set_input(&ctx, type);
if (acpi5) {
struct set_error_type_with_address *v5param = einj_param;
struct set_error_type_with_address *v5param;
v5param = kmalloc(v5param_size, GFP_KERNEL);
memcpy_fromio(v5param, einj_param, v5param_size);
v5param->type = type;
if (type & ACPI5_VENDOR_BIT) {
switch (vendor_flags) {
@@ -467,8 +560,21 @@ static int __einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2,
v5param->flags = flags;
v5param->memory_address = param1;
v5param->memory_address_range = param2;
v5param->apicid = param3;
v5param->pcie_sbdf = param4;
if (is_v2) {
for (i = 0; i < max_nr_components; i++) {
if (is_end_of_list(syndrome_data[i].comp_id.acpi_id))
break;
v5param->einjv2_struct.component_arr[i].comp_id =
syndrome_data[i].comp_id;
v5param->einjv2_struct.component_arr[i].comp_synd =
syndrome_data[i].comp_synd;
}
v5param->einjv2_struct.component_arr_count = i;
} else {
v5param->apicid = param3;
v5param->pcie_sbdf = param4;
}
} else {
switch (type) {
case ACPI_EINJ_PROCESSOR_CORRECTABLE:
@@ -492,15 +598,19 @@ static int __einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2,
break;
}
}
memcpy_toio(einj_param, v5param, v5param_size);
kfree(v5param);
} else {
rc = apei_exec_run(&ctx, ACPI_EINJ_SET_ERROR_TYPE);
if (rc)
return rc;
if (einj_param) {
struct einj_parameter *v4param = einj_param;
struct einj_parameter v4param;
v4param->param1 = param1;
v4param->param2 = param2;
memcpy_fromio(&v4param, einj_param, sizeof(v4param));
v4param.param1 = param1;
v4param.param2 = param2;
memcpy_toio(einj_param, &v4param, sizeof(v4param));
}
}
rc = apei_exec_run(&ctx, ACPI_EINJ_EXECUTE_OPERATION);
@@ -551,10 +661,15 @@ int einj_error_inject(u32 type, u32 flags, u64 param1, u64 param2, u64 param3,
u64 base_addr, size;
/* If user manually set "flags", make sure it is legal */
if (flags && (flags &
~(SETWA_FLAGS_APICID|SETWA_FLAGS_MEM|SETWA_FLAGS_PCIE_SBDF)))
if (flags && (flags & ~(SETWA_FLAGS_APICID | SETWA_FLAGS_MEM |
SETWA_FLAGS_PCIE_SBDF | SETWA_FLAGS_EINJV2)))
return -EINVAL;
/* check if type is a valid EINJv2 error type */
if (is_v2) {
if (!(type & available_error_type_v2))
return -EINVAL;
}
/*
* We need extra sanity checks for memory errors.
* Other types leap directly to injection.
@@ -632,6 +747,8 @@ static u64 error_param2;
static u64 error_param3;
static u64 error_param4;
static struct dentry *einj_debug_dir;
static char einj_buf[32];
static bool einj_v2_enabled;
static struct { u32 mask; const char *str; } const einj_error_type_string[] = {
{ BIT(0), "Processor Correctable" },
{ BIT(1), "Processor Uncorrectable non-fatal" },
@@ -648,6 +765,12 @@ static struct { u32 mask; const char *str; } const einj_error_type_string[] = {
{ BIT(31), "Vendor Defined Error Types" },
};
static struct { u32 mask; const char *str; } const einjv2_error_type_string[] = {
{ BIT(0), "EINJV2 Processor Error" },
{ BIT(1), "EINJV2 Memory Error" },
{ BIT(2), "EINJV2 PCI Express Error" },
};
static int available_error_type_show(struct seq_file *m, void *v)
{
@@ -655,17 +778,22 @@ static int available_error_type_show(struct seq_file *m, void *v)
if (available_error_type & einj_error_type_string[pos].mask)
seq_printf(m, "0x%08x\t%s\n", einj_error_type_string[pos].mask,
einj_error_type_string[pos].str);
if ((available_error_type & ACPI65_EINJV2_SUPP) && einj_v2_enabled) {
for (int pos = 0; pos < ARRAY_SIZE(einjv2_error_type_string); pos++) {
if (available_error_type_v2 & einjv2_error_type_string[pos].mask)
seq_printf(m, "V2_0x%08x\t%s\n", einjv2_error_type_string[pos].mask,
einjv2_error_type_string[pos].str);
}
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(available_error_type);
static int error_type_get(void *data, u64 *val)
static ssize_t error_type_get(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
*val = error_type;
return 0;
return simple_read_from_buffer(buf, count, ppos, einj_buf, strlen(einj_buf));
}
bool einj_is_cxl_error_type(u64 type)
@@ -692,15 +820,35 @@ int einj_validate_error_type(u64 type)
if (tval & (tval - 1))
return -EINVAL;
if (!vendor)
if (!(type & available_error_type))
if (!(type & (available_error_type | available_error_type_v2)))
return -EINVAL;
return 0;
}
static int error_type_set(void *data, u64 val)
static ssize_t error_type_set(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int rc;
u64 val;
/* Leave the last character for the NUL terminator */
if (count > sizeof(einj_buf) - 1)
return -EINVAL;
memset(einj_buf, 0, sizeof(einj_buf));
if (copy_from_user(einj_buf, buf, count))
return -EFAULT;
if (strncmp(einj_buf, "V2_", 3) == 0) {
if (!sscanf(einj_buf, "V2_%llx", &val))
return -EINVAL;
is_v2 = true;
} else {
if (!sscanf(einj_buf, "%llx", &val))
return -EINVAL;
is_v2 = false;
}
rc = einj_validate_error_type(val);
if (rc)
@@ -708,17 +856,24 @@ static int error_type_set(void *data, u64 val)
error_type = val;
return 0;
return count;
}
DEFINE_DEBUGFS_ATTRIBUTE(error_type_fops, error_type_get, error_type_set,
"0x%llx\n");
static const struct file_operations error_type_fops = {
.read = error_type_get,
.write = error_type_set,
};
static int error_inject_set(void *data, u64 val)
{
if (!error_type)
return -EINVAL;
if (is_v2)
error_flags |= SETWA_FLAGS_EINJV2;
else
error_flags &= ~SETWA_FLAGS_EINJV2;
return einj_error_inject(error_type, error_flags, error_param1, error_param2,
error_param3, error_param4);
}
@@ -741,6 +896,98 @@ static int einj_check_table(struct acpi_table_einj *einj_tab)
return 0;
}
static ssize_t u128_read(struct file *f, char __user *buf, size_t count, loff_t *off)
{
char output[2 * COMPONENT_LEN + 1];
u8 *data = f->f_inode->i_private;
int i;
if (*off >= sizeof(output))
return 0;
for (i = 0; i < COMPONENT_LEN; i++)
sprintf(output + 2 * i, "%.02x", data[COMPONENT_LEN - i - 1]);
output[2 * COMPONENT_LEN] = '\n';
return simple_read_from_buffer(buf, count, off, output, sizeof(output));
}
static ssize_t u128_write(struct file *f, const char __user *buf, size_t count, loff_t *off)
{
char input[2 + 2 * COMPONENT_LEN + 2];
u8 *save = f->f_inode->i_private;
u8 tmp[COMPONENT_LEN];
char byte[3] = {};
char *s, *e;
ssize_t c;
long val;
int i;
/* Require that user supply whole input line in one write(2) syscall */
if (*off)
return -EINVAL;
c = simple_write_to_buffer(input, sizeof(input), off, buf, count);
if (c < 0)
return c;
if (c < 1 || input[c - 1] != '\n')
return -EINVAL;
/* Empty line means invalidate this entry */
if (c == 1) {
memset(save, 0xff, COMPONENT_LEN);
return c;
}
if (input[0] == '0' && (input[1] == 'x' || input[1] == 'X'))
s = input + 2;
else
s = input;
e = input + c - 1;
for (i = 0; i < COMPONENT_LEN; i++) {
byte[1] = *--e;
byte[0] = e > s ? *--e : '0';
if (kstrtol(byte, 16, &val))
return -EINVAL;
tmp[i] = val;
if (e <= s)
break;
}
while (++i < COMPONENT_LEN)
tmp[i] = 0;
memcpy(save, tmp, COMPONENT_LEN);
return c;
}
static const struct file_operations u128_fops = {
.read = u128_read,
.write = u128_write,
};
static bool setup_einjv2_component_files(void)
{
char name[32];
syndrome_data = kcalloc(max_nr_components, sizeof(syndrome_data[0]), GFP_KERNEL);
if (!syndrome_data)
return false;
for (int i = 0; i < max_nr_components; i++) {
sprintf(name, "component_id%d", i);
debugfs_create_file(name, 0600, einj_debug_dir,
&syndrome_data[i].comp_id, &u128_fops);
sprintf(name, "component_syndrome%d", i);
debugfs_create_file(name, 0600, einj_debug_dir,
&syndrome_data[i].comp_synd, &u128_fops);
}
return true;
}
static int __init einj_probe(struct faux_device *fdev)
{
int rc;
@@ -764,7 +1011,7 @@ static int __init einj_probe(struct faux_device *fdev)
goto err_put_table;
}
rc = einj_get_available_error_type(&available_error_type);
rc = einj_get_available_error_types(&available_error_type, &available_error_type_v2);
if (rc)
goto err_put_table;
@@ -812,6 +1059,8 @@ static int __init einj_probe(struct faux_device *fdev)
&error_param4);
debugfs_create_x32("notrigger", S_IRUSR | S_IWUSR,
einj_debug_dir, &notrigger);
if (available_error_type & ACPI65_EINJV2_SUPP)
einj_v2_enabled = setup_einjv2_component_files();
}
if (vendor_dev[0]) {
@@ -848,7 +1097,7 @@ static void __exit einj_remove(struct faux_device *fdev)
if (einj_param) {
acpi_size size = (acpi5) ?
sizeof(struct set_error_type_with_address) :
v5param_size :
sizeof(struct einj_parameter);
acpi_os_unmap_iomem(einj_param, size);
@@ -860,6 +1109,7 @@ static void __exit einj_remove(struct faux_device *fdev)
apei_resources_release(&einj_resources);
apei_resources_fini(&einj_resources);
debugfs_remove_recursive(einj_debug_dir);
kfree(syndrome_data);
acpi_put_table((struct acpi_table_header *)einj_tab);
}
+1 -1
View File
@@ -30,7 +30,7 @@ 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);
rc = einj_get_available_error_type(&available_error_type, ACPI_EINJ_GET_ERROR_TYPE);
if (rc)
return rc;
+57 -33
View File
@@ -464,28 +464,41 @@ static void ghes_clear_estatus(struct ghes *ghes,
ghes_ack_error(ghes->generic_v2);
}
/*
* 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.
/**
* struct ghes_task_work - for synchronous RAS event
*
* @twork: callback_head for task work
* @pfn: page frame number of corrupted page
* @flags: work control flags
*
* Structure to pass task work to be handled before
* returning to user-space via task_work_add().
*/
static void ghes_kick_task_work(struct callback_head *head)
struct ghes_task_work {
struct callback_head twork;
u64 pfn;
int flags;
};
static void memory_failure_cb(struct callback_head *twork)
{
struct acpi_hest_generic_status *estatus;
struct ghes_estatus_node *estatus_node;
u32 node_len;
struct ghes_task_work *twcb = container_of(twork, struct ghes_task_work, twork);
int ret;
estatus_node = container_of(head, struct ghes_estatus_node, task_work);
if (IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
memory_failure_queue_kick(estatus_node->task_work_cpu);
ret = memory_failure(twcb->pfn, twcb->flags);
gen_pool_free(ghes_estatus_pool, (unsigned long)twcb, sizeof(*twcb));
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
node_len = GHES_ESTATUS_NODE_LEN(cper_estatus_len(estatus));
gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, node_len);
if (!ret || ret == -EHWPOISON || ret == -EOPNOTSUPP)
return;
pr_err("%#llx: Sending SIGBUS to %s:%d due to hardware memory corruption\n",
twcb->pfn, current->comm, task_pid_nr(current));
force_sig(SIGBUS);
}
static bool ghes_do_memory_failure(u64 physical_addr, int flags)
{
struct ghes_task_work *twcb;
unsigned long pfn;
if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
@@ -499,6 +512,18 @@ static bool ghes_do_memory_failure(u64 physical_addr, int flags)
return false;
}
if (flags == MF_ACTION_REQUIRED && current->mm) {
twcb = (void *)gen_pool_alloc(ghes_estatus_pool, sizeof(*twcb));
if (!twcb)
return false;
twcb->pfn = pfn;
twcb->flags = flags;
init_task_work(&twcb->twork, memory_failure_cb);
task_work_add(current, &twcb->twork, TWA_RESUME);
return true;
}
memory_failure_queue(pfn, flags);
return true;
}
@@ -842,7 +867,7 @@ int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd)
}
EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL");
static bool ghes_do_proc(struct ghes *ghes,
static void ghes_do_proc(struct ghes *ghes,
const struct acpi_hest_generic_status *estatus)
{
int sev, sec_sev;
@@ -902,7 +927,16 @@ static bool ghes_do_proc(struct ghes *ghes,
}
}
return queued;
/*
* If no memory failure work is queued for abnormal synchronous
* errors, do a force kill.
*/
if (sync && !queued) {
dev_err(ghes->dev,
HW_ERR GHES_PFX "%s:%d: synchronous unrecoverable error (SIGBUS)\n",
current->comm, task_pid_nr(current));
force_sig(SIGBUS);
}
}
static void __ghes_print_estatus(const char *pfx,
@@ -1088,6 +1122,8 @@ static void __ghes_panic(struct ghes *ghes,
__ghes_print_estatus(KERN_EMERG, ghes->generic, estatus);
add_taint(TAINT_MACHINE_CHECK, LOCKDEP_STILL_OK);
ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx);
if (!panic_timeout)
@@ -1206,9 +1242,7 @@ static void ghes_proc_in_irq(struct irq_work *irq_work)
struct ghes_estatus_node *estatus_node;
struct acpi_hest_generic *generic;
struct acpi_hest_generic_status *estatus;
bool task_work_pending;
u32 len, node_len;
int ret;
llnode = llist_del_all(&ghes_estatus_llist);
/*
@@ -1223,25 +1257,16 @@ static void ghes_proc_in_irq(struct irq_work *irq_work)
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
len = cper_estatus_len(estatus);
node_len = GHES_ESTATUS_NODE_LEN(len);
task_work_pending = ghes_do_proc(estatus_node->ghes, estatus);
ghes_do_proc(estatus_node->ghes, estatus);
if (!ghes_estatus_cached(estatus)) {
generic = estatus_node->generic;
if (ghes_print_estatus(NULL, generic, estatus))
ghes_estatus_cache_add(generic, estatus);
}
if (task_work_pending && current->mm) {
estatus_node->task_work.func = ghes_kick_task_work;
estatus_node->task_work_cpu = smp_processor_id();
ret = task_work_add(current, &estatus_node->task_work,
TWA_RESUME);
if (ret)
estatus_node->task_work.func = NULL;
}
if (!estatus_node->task_work.func)
gen_pool_free(ghes_estatus_pool,
(unsigned long)estatus_node, node_len);
gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
node_len);
llnode = next;
}
@@ -1302,7 +1327,6 @@ static int ghes_in_nmi_queue_one_entry(struct ghes *ghes,
estatus_node->ghes = ghes;
estatus_node->generic = ghes->generic;
estatus_node->task_work.func = NULL;
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) {
-3
View File
@@ -35,9 +35,6 @@ struct ghes_estatus_node {
struct llist_node llnode;
struct acpi_hest_generic *generic;
struct ghes *ghes;
int task_work_cpu;
struct callback_head task_work;
};
struct ghes_estatus_cache {
-1
View File
@@ -3901,7 +3901,6 @@ enum mf_flags {
int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index,
unsigned long count, int mf_flags);
extern int memory_failure(unsigned long pfn, int flags);
extern void memory_failure_queue_kick(int cpu);
extern int unpoison_memory(unsigned long pfn);
extern atomic_long_t num_poisoned_pages __read_mostly;
extern int soft_offline_page(unsigned long pfn, int flags);
-13
View File
@@ -2503,19 +2503,6 @@ static void memory_failure_work_func(struct work_struct *work)
}
}
/*
* Process memory_failure work queued on the specified CPU.
* Used to avoid return-to-userspace racing with the memory_failure workqueue.
*/
void memory_failure_queue_kick(int cpu)
{
struct memory_failure_cpu *mf_cpu;
mf_cpu = &per_cpu(memory_failure_cpu, cpu);
cancel_work_sync(&mf_cpu->work);
memory_failure_work_func(&mf_cpu->work);
}
static int __init memory_failure_init(void)
{
struct memory_failure_cpu *mf_cpu;