|
@@ -46,6 +46,7 @@
|
|
|
#include <linux/nmi.h>
|
|
|
#include <linux/sched/clock.h>
|
|
|
|
|
|
+#include <acpi/actbl1.h>
|
|
|
#include <acpi/ghes.h>
|
|
|
#include <acpi/apei.h>
|
|
|
#include <asm/tlbflush.h>
|
|
@@ -80,6 +81,11 @@
|
|
|
((struct acpi_hest_generic_status *) \
|
|
|
((struct ghes_estatus_node *)(estatus_node) + 1))
|
|
|
|
|
|
+static inline bool is_hest_type_generic_v2(struct ghes *ghes)
|
|
|
+{
|
|
|
+ return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* This driver isn't really modular, however for the time being,
|
|
|
* continuing to use module_param is the easiest way to remain
|
|
@@ -240,6 +246,16 @@ static int ghes_estatus_pool_expand(unsigned long len)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int map_gen_v2(struct ghes *ghes)
|
|
|
+{
|
|
|
+ return apei_map_generic_address(&ghes->generic_v2->read_ack_register);
|
|
|
+}
|
|
|
+
|
|
|
+static void unmap_gen_v2(struct ghes *ghes)
|
|
|
+{
|
|
|
+ apei_unmap_generic_address(&ghes->generic_v2->read_ack_register);
|
|
|
+}
|
|
|
+
|
|
|
static struct ghes *ghes_new(struct acpi_hest_generic *generic)
|
|
|
{
|
|
|
struct ghes *ghes;
|
|
@@ -249,10 +265,17 @@ static struct ghes *ghes_new(struct acpi_hest_generic *generic)
|
|
|
ghes = kzalloc(sizeof(*ghes), GFP_KERNEL);
|
|
|
if (!ghes)
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
+
|
|
|
ghes->generic = generic;
|
|
|
+ if (is_hest_type_generic_v2(ghes)) {
|
|
|
+ rc = map_gen_v2(ghes);
|
|
|
+ if (rc)
|
|
|
+ goto err_free;
|
|
|
+ }
|
|
|
+
|
|
|
rc = apei_map_generic_address(&generic->error_status_address);
|
|
|
if (rc)
|
|
|
- goto err_free;
|
|
|
+ goto err_unmap_read_ack_addr;
|
|
|
error_block_length = generic->error_block_length;
|
|
|
if (error_block_length > GHES_ESTATUS_MAX_SIZE) {
|
|
|
pr_warning(FW_WARN GHES_PFX
|
|
@@ -264,13 +287,16 @@ static struct ghes *ghes_new(struct acpi_hest_generic *generic)
|
|
|
ghes->estatus = kmalloc(error_block_length, GFP_KERNEL);
|
|
|
if (!ghes->estatus) {
|
|
|
rc = -ENOMEM;
|
|
|
- goto err_unmap;
|
|
|
+ goto err_unmap_status_addr;
|
|
|
}
|
|
|
|
|
|
return ghes;
|
|
|
|
|
|
-err_unmap:
|
|
|
+err_unmap_status_addr:
|
|
|
apei_unmap_generic_address(&generic->error_status_address);
|
|
|
+err_unmap_read_ack_addr:
|
|
|
+ if (is_hest_type_generic_v2(ghes))
|
|
|
+ unmap_gen_v2(ghes);
|
|
|
err_free:
|
|
|
kfree(ghes);
|
|
|
return ERR_PTR(rc);
|
|
@@ -280,6 +306,8 @@ static void ghes_fini(struct ghes *ghes)
|
|
|
{
|
|
|
kfree(ghes->estatus);
|
|
|
apei_unmap_generic_address(&ghes->generic->error_status_address);
|
|
|
+ if (is_hest_type_generic_v2(ghes))
|
|
|
+ unmap_gen_v2(ghes);
|
|
|
}
|
|
|
|
|
|
static inline int ghes_severity(int severity)
|
|
@@ -649,6 +677,21 @@ static void ghes_estatus_cache_add(
|
|
|
rcu_read_unlock();
|
|
|
}
|
|
|
|
|
|
+static int ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
|
|
|
+{
|
|
|
+ int rc;
|
|
|
+ u64 val = 0;
|
|
|
+
|
|
|
+ rc = apei_read(&val, &gv2->read_ack_register);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
+
|
|
|
+ val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset;
|
|
|
+ val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset;
|
|
|
+
|
|
|
+ return apei_write(val, &gv2->read_ack_register);
|
|
|
+}
|
|
|
+
|
|
|
static int ghes_proc(struct ghes *ghes)
|
|
|
{
|
|
|
int rc;
|
|
@@ -661,6 +704,16 @@ static int ghes_proc(struct ghes *ghes)
|
|
|
ghes_estatus_cache_add(ghes->generic, ghes->estatus);
|
|
|
}
|
|
|
ghes_do_proc(ghes, ghes->estatus);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * GHESv2 type HEST entries introduce support for error acknowledgment,
|
|
|
+ * so only acknowledge the error if this support is present.
|
|
|
+ */
|
|
|
+ if (is_hest_type_generic_v2(ghes)) {
|
|
|
+ rc = ghes_ack_error(ghes->generic_v2);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
out:
|
|
|
ghes_clear_estatus(ghes);
|
|
|
return rc;
|