Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
BACKPORT: ACPI / APEI: Kick the memory_failure() queue for synchronou…
Browse files Browse the repository at this point in the history
…s errors

memory_failure() offlines or repairs pages of memory that have been
discovered to be corrupt. These may be detected by an external
component, (e.g. the memory controller), and notified via an IRQ.
In this case the work is queued as not all of memory_failure()s work
can happen in IRQ context.

If the error was detected as a result of user-space accessing a
corrupt memory location the CPU may take an abort instead. On arm64
this is a 'synchronous external abort', and on a firmware first
system it is replayed using NOTIFY_SEA.

This notification has NMI like properties, (it can interrupt
IRQ-masked code), so the memory_failure() work is queued. If we
return to user-space before the queued memory_failure() work is
processed, we will take the fault again. This loop may cause platform
firmware to exceed some threshold and reboot when Linux could have
recovered from this error.

For NMIlike notifications keep track of whether memory_failure() work
was queued, and make task_work pending to flush out the queue.
To save memory allocations, the task_work is allocated as part of
the ghes_estatus_node, and free()ing it back to the pool is deferred.

This patch is needed because Quicksilver firmware-first error handling
uses the SDEI notification type for communication between trusted
firmware and the OS. This adds needed NMI and SDEI functionality so
that the SDEI path in the kernel through APEI acts as an NMI and is
properly wired up to the APEI interfaces.

Backported from: https://patchwork.kernel.org/patch/10786981/

Signed-off-by: James Morse <[email protected]>
Signed-off-by: Tyler Baicar <[email protected]>
  • Loading branch information
James Morse authored and tphan-ampere committed Apr 21, 2020
1 parent f845034 commit ac5d13a
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 11 deletions.
68 changes: 57 additions & 11 deletions drivers/acpi/apei/ghes.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <linux/sched/clock.h>
#include <linux/uuid.h>
#include <linux/ras.h>
#include <linux/task_work.h>

#include <acpi/actbl1.h>
#include <acpi/ghes.h>
Expand Down Expand Up @@ -399,23 +400,46 @@ static void ghes_clear_estatus(struct ghes *ghes,
ghes_ack_error(ghes->generic_v2);
}

static void ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, int sev)
/*
* 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.
*/
static void ghes_kick_task_work(struct callback_head *head)
{
struct acpi_hest_generic_status *estatus;
struct ghes_estatus_node *estatus_node;
u32 node_len;

estatus_node = container_of(head, struct ghes_estatus_node, task_work);
memory_failure_queue_kick(estatus_node->task_work_cpu);

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);
}

static bool ghes_handle_memory_failure(struct ghes *ghes,
struct acpi_hest_generic_data *gdata,
int sev)
{
#ifdef CONFIG_ACPI_APEI_MEMORY_FAILURE
unsigned long pfn;
int flags = -1;
int sec_sev = ghes_severity(gdata->error_severity);
struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);

if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
return false;

if (!(mem_err->validation_bits & CPER_MEM_VALID_PA))
return;
return false;

pfn = mem_err->physical_addr >> PAGE_SHIFT;
if (!pfn_valid(pfn)) {
pr_warn_ratelimited(FW_WARN GHES_PFX
"Invalid address in generic error data: %#llx\n",
mem_err->physical_addr);
return;
return false;
}

/* iff following two events can be handled properly by now */
Expand All @@ -425,9 +449,12 @@ static void ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, int
if (sev == GHES_SEV_RECOVERABLE && sec_sev == GHES_SEV_RECOVERABLE)
flags = 0;

if (flags != -1)
if (flags != -1) {
memory_failure_queue(pfn, flags);
#endif
return true;
}

return false;
}

/*
Expand Down Expand Up @@ -475,11 +502,12 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
#endif
}

static void ghes_do_proc(struct ghes *ghes,
static bool ghes_do_proc(struct ghes *ghes,
const struct acpi_hest_generic_status *estatus)
{
int sev, sec_sev;
struct acpi_hest_generic_data *gdata;
bool work_queued = false;
guid_t *sec_type;
guid_t *fru_id = &NULL_UUID_LE;
char *fru_text = "";
Expand All @@ -500,7 +528,8 @@ static void ghes_do_proc(struct ghes *ghes,
ghes_edac_report_mem_error(sev, mem_err);

arch_apei_report_mem_error(sev, mem_err);
ghes_handle_memory_failure(gdata, sev);
if (ghes_handle_memory_failure(ghes, gdata, sev))
work_queued = true;
}
else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
ghes_handle_aer(gdata);
Expand All @@ -517,6 +546,8 @@ static void ghes_do_proc(struct ghes *ghes,
gdata->error_data_length);
}
}

return work_queued;
}

static void __ghes_print_estatus(const char *pfx,
Expand Down Expand Up @@ -812,7 +843,9 @@ 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);
/*
Expand All @@ -827,14 +860,26 @@ 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);
ghes_do_proc(estatus_node->ghes, estatus);
task_work_pending = 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);
}
gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
node_len);

if (task_work_pending && current->mm != &init_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,
true);
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);

llnode = next;
}
}
Expand Down Expand Up @@ -894,6 +939,7 @@ 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)) {
Expand Down
3 changes: 3 additions & 0 deletions include/acpi/ghes.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ 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 {
Expand Down

0 comments on commit ac5d13a

Please sign in to comment.