There are couple of reasons why such hack was applied: o lack of UEFI thus no firmware region is reserved to exchange info about error between firmware and OS o EINJ table should operate on registers but GPIO interrupts are availabled now
We set aside some pretend physical space (that is described the same in the tables) and fill in with some error info e.g. memory error. Then is should be triggered. The easiest way to trigger hardware error is to call AML method directly which in turn notify HED (hardware error device). Later on, HED call SCI handler, traverse all GHES list, match appropriate GHES and start to parse.
Signed-off-by: Tomasz Nowicki tomasz.nowicki@linaro.org --- arch/arm/boot/asl/exynos5250-arndale.acpi/dsdt.asl | 5 ++ arch/arm/boot/asl/exynos5250-arndale.acpi/hest.asl | 2 +- arch/arm64/boot/asl/foundation-v8.acpi/dsdt.asl | 5 ++ arch/arm64/boot/asl/foundation-v8.acpi/hest.asl | 2 +- drivers/acpi/apei/einj.c | 74 ++++++++++++++++++++ drivers/acpi/apei/ghes.c | 9 ++- include/acpi/ghes.h | 4 ++ 7 files changed, 98 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/asl/exynos5250-arndale.acpi/dsdt.asl b/arch/arm/boot/asl/exynos5250-arndale.acpi/dsdt.asl index 5b5a9f4..84d6d51 100644 --- a/arch/arm/boot/asl/exynos5250-arndale.acpi/dsdt.asl +++ b/arch/arm/boot/asl/exynos5250-arndale.acpi/dsdt.asl @@ -73,5 +73,10 @@ DefinitionBlock ( Name (_HID, EisaId ("PNP0C33")) Name (_UID, 0x00) } + + Method (TRIG, 0, NotSerialized) + { + Notify (HED, 0x80) + } } } diff --git a/arch/arm/boot/asl/exynos5250-arndale.acpi/hest.asl b/arch/arm/boot/asl/exynos5250-arndale.acpi/hest.asl index ad29f6f..4d66762 100644 --- a/arch/arm/boot/asl/exynos5250-arndale.acpi/hest.asl +++ b/arch/arm/boot/asl/exynos5250-arndale.acpi/hest.asl @@ -148,7 +148,7 @@ [0001] Bit Width : 40 [0001] Bit Offset : 00 [0001] Encoded Access Width : 04 [QWord Access:64] -[0008] Address : 0000000000000000 +[0008] Address : 0x42010000
[0028] Notify : [Hardware Error Notification Structure] [0001] Notify Type : 03 [SCI] diff --git a/arch/arm64/boot/asl/foundation-v8.acpi/dsdt.asl b/arch/arm64/boot/asl/foundation-v8.acpi/dsdt.asl index 68f195c..7f8595e 100644 --- a/arch/arm64/boot/asl/foundation-v8.acpi/dsdt.asl +++ b/arch/arm64/boot/asl/foundation-v8.acpi/dsdt.asl @@ -43,5 +43,10 @@ DefinitionBlock ( Name (_HID, EisaId ("PNP0C33")) Name (_UID, 0x00) } + + Method (TRIG, 0, NotSerialized) + { + Notify (HED, 0x80) + } } } diff --git a/arch/arm64/boot/asl/foundation-v8.acpi/hest.asl b/arch/arm64/boot/asl/foundation-v8.acpi/hest.asl index f133704..56d8bc4 100644 --- a/arch/arm64/boot/asl/foundation-v8.acpi/hest.asl +++ b/arch/arm64/boot/asl/foundation-v8.acpi/hest.asl @@ -148,7 +148,7 @@ [0001] Bit Width : 40 [0001] Bit Offset : 00 [0001] Encoded Access Width : 04 [QWord Access:64] -[0008] Address : 0000000000000000 +[0008] Address : 0x88180000
[0028] Notify : [Hardware Error Notification Structure] [0001] Notify Type : 03 [SCI] diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c index 8d457b5..5e8b593 100644 --- a/drivers/acpi/apei/einj.c +++ b/drivers/acpi/apei/einj.c @@ -34,6 +34,10 @@ #include <linux/delay.h> #include <acpi/acpi.h>
+#if defined (CONFIG_ARM) || defined (CONFIG_ARM64) +#include <acpi/ghes.h> +#endif + #include "apei-internal.h"
#define EINJ_PFX "EINJ: " @@ -143,6 +147,10 @@ static DEFINE_MUTEX(einj_mutex);
static void *einj_param;
+#if defined (CONFIG_ARM) || defined (CONFIG_ARM64) +extern struct list_head ghes_sci; +#endif + static void einj_exec_ctx_init(struct apei_exec_context *ctx) { apei_exec_ctx_init(ctx, einj_ins_type, ARRAY_SIZE(einj_ins_type), @@ -613,10 +621,72 @@ DEFINE_SIMPLE_ATTRIBUTE(error_type_fops, error_type_get,
static int error_inject_set(void *data, u64 val) { +#if defined (CONFIG_ARM) || defined (CONFIG_ARM64) + /* + * Simulate error injection by calling AML control method directly. + * We need this hack because of lack in GPIO functionality. + * + * Lets simulate platform memory error. + */ + +#define SIZE sizeof(u64) + \ + sizeof(struct acpi_hest_generic_status) + \ + sizeof(struct acpi_hest_generic_data) + \ + sizeof (struct cper_sec_mem_err) + + char buf[SIZE]; + struct acpi_hest_generic_status *block_ptr; + struct acpi_hest_generic_data *gdata; + struct cper_sec_mem_err *mem_err; + struct ghes *ghes; + u64 paddr; + u64 *add_ptr; + int status; + + list_for_each_entry_rcu(ghes, &ghes_sci, list) { + if (!ghes || !ghes->generic) + return ACPI_EINJ_FAILURE; + + paddr = ghes->generic->error_status_address.address; + + memset(buf, 0, SIZE); + + /* First point to generic error status block */ + add_ptr = (u64 *) buf; + *add_ptr = paddr + sizeof(u64); + + /* Fill in generic error status block */ + block_ptr = (struct acpi_hest_generic_status *) (++add_ptr); + block_ptr->block_status = 1; + block_ptr->data_length = sizeof(struct acpi_hest_generic_data); + block_ptr->error_severity = GHES_SEV_CORRECTED; + + /* Fill in generic error data entry */ + gdata = (struct acpi_hest_generic_data *) (block_ptr + 1); + memcpy(gdata->section_type, (void *) &CPER_SEC_PLATFORM_MEM, + sizeof(uuid_le)); + gdata->error_data_length = sizeof(struct cper_sec_mem_err); + block_ptr->data_length += gdata->error_data_length; + + mem_err = (struct cper_sec_mem_err *) (gdata + 1); + + /* Copy into the physical region */ + ghes_copy_tofrom_phys(buf, paddr, SIZE, 0); + + status = acpi_evaluate_object(NULL, "\_SB.TRIG", NULL, NULL); + if (status != ACPI_EINJ_SUCCESS) { + pr_err("Failure during AML control method.\n"); + break; + } + } + + return status; +#else if (!error_type) return -EINVAL;
return einj_error_inject(error_type, error_param1, error_param2); +#endif }
DEFINE_SIMPLE_ATTRIBUTE(error_inject_fops, NULL, @@ -668,6 +738,7 @@ static int __init einj_init(void) einj_debug_dir = debugfs_create_dir("einj", apei_get_debugfs_dir()); if (!einj_debug_dir) goto err_cleanup; +#if !defined (CONFIG_ARM) && !defined (CONFIG_ARM64) fentry = debugfs_create_file("available_error_type", S_IRUSR, einj_debug_dir, NULL, &available_error_type_fops); @@ -677,11 +748,13 @@ static int __init einj_init(void) einj_debug_dir, NULL, &error_type_fops); if (!fentry) goto err_cleanup; +#endif fentry = debugfs_create_file("error_inject", S_IWUSR, einj_debug_dir, NULL, &error_inject_fops); if (!fentry) goto err_cleanup;
+#if !defined (CONFIG_ARM) && !defined (CONFIG_ARM64) apei_resources_init(&einj_resources); einj_exec_ctx_init(&ctx); rc = apei_exec_collect_resources(&ctx, &einj_resources); @@ -723,6 +796,7 @@ static int __init einj_init(void) if (!fentry) goto err_unmap; } +#endif
pr_info(EINJ_PFX "Error INJection is initialized.\n");
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 5d8c2f9..b59cd4f 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -103,7 +103,11 @@ static int ghes_panic_timeout __read_mostly = 30; * RCU is used for these lists, so ghes_list_mutex is only used for * list changing, not for traversing. */ +#if defined (CONFIG_ARM) || defined (CONFIG_ARM64) +LIST_HEAD(ghes_sci); +#else static LIST_HEAD(ghes_sci); +#endif static LIST_HEAD(ghes_nmi); static DEFINE_MUTEX(ghes_list_mutex);
@@ -324,7 +328,10 @@ static inline int ghes_severity(int severity) } }
-static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, +#if !defined (CONFIG_ARM) && !defined (CONFIG_ARM64) +static +#endif +void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, int from_phys) { void __iomem *vaddr; diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index 720446c..22b8007 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -70,3 +70,7 @@ static inline void ghes_edac_unregister(struct ghes *ghes) { } #endif + +#if defined (CONFIG_ARM) || defined (CONFIG_ARM64) +void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, int from_phys); +#endif