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:
@@ -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
@@ -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/
|
||||
|
||||
|
||||
@@ -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
@@ -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, ¬rigger);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user