Skip to content

Commit 7f17b4a

Browse files
James Morserafaeljw
authored andcommitted
ACPI: APEI: Kick the memory_failure() queue for synchronous 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. Signed-off-by: James Morse <[email protected]> Tested-by: Tyler Baicar <[email protected]> Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 0620223 commit 7f17b4a

File tree

2 files changed

+59
-11
lines changed

2 files changed

+59
-11
lines changed

drivers/acpi/apei/ghes.c

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <linux/sched/clock.h>
4141
#include <linux/uuid.h>
4242
#include <linux/ras.h>
43+
#include <linux/task_work.h>
4344

4445
#include <acpi/actbl1.h>
4546
#include <acpi/ghes.h>
@@ -414,23 +415,46 @@ static void ghes_clear_estatus(struct ghes *ghes,
414415
ghes_ack_error(ghes->generic_v2);
415416
}
416417

417-
static void ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, int sev)
418+
/*
419+
* Called as task_work before returning to user-space.
420+
* Ensure any queued work has been done before we return to the context that
421+
* triggered the notification.
422+
*/
423+
static void ghes_kick_task_work(struct callback_head *head)
424+
{
425+
struct acpi_hest_generic_status *estatus;
426+
struct ghes_estatus_node *estatus_node;
427+
u32 node_len;
428+
429+
estatus_node = container_of(head, struct ghes_estatus_node, task_work);
430+
if (IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
431+
memory_failure_queue_kick(estatus_node->task_work_cpu);
432+
433+
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
434+
node_len = GHES_ESTATUS_NODE_LEN(cper_estatus_len(estatus));
435+
gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node, node_len);
436+
}
437+
438+
static bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata,
439+
int sev)
418440
{
419-
#ifdef CONFIG_ACPI_APEI_MEMORY_FAILURE
420441
unsigned long pfn;
421442
int flags = -1;
422443
int sec_sev = ghes_severity(gdata->error_severity);
423444
struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
424445

446+
if (!IS_ENABLED(CONFIG_ACPI_APEI_MEMORY_FAILURE))
447+
return false;
448+
425449
if (!(mem_err->validation_bits & CPER_MEM_VALID_PA))
426-
return;
450+
return false;
427451

428452
pfn = mem_err->physical_addr >> PAGE_SHIFT;
429453
if (!pfn_valid(pfn)) {
430454
pr_warn_ratelimited(FW_WARN GHES_PFX
431455
"Invalid address in generic error data: %#llx\n",
432456
mem_err->physical_addr);
433-
return;
457+
return false;
434458
}
435459

436460
/* iff following two events can be handled properly by now */
@@ -440,9 +464,12 @@ static void ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, int
440464
if (sev == GHES_SEV_RECOVERABLE && sec_sev == GHES_SEV_RECOVERABLE)
441465
flags = 0;
442466

443-
if (flags != -1)
467+
if (flags != -1) {
444468
memory_failure_queue(pfn, flags);
445-
#endif
469+
return true;
470+
}
471+
472+
return false;
446473
}
447474

448475
/*
@@ -490,14 +517,15 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
490517
#endif
491518
}
492519

493-
static void ghes_do_proc(struct ghes *ghes,
520+
static bool ghes_do_proc(struct ghes *ghes,
494521
const struct acpi_hest_generic_status *estatus)
495522
{
496523
int sev, sec_sev;
497524
struct acpi_hest_generic_data *gdata;
498525
guid_t *sec_type;
499526
const guid_t *fru_id = &guid_null;
500527
char *fru_text = "";
528+
bool queued = false;
501529

502530
sev = ghes_severity(estatus->error_severity);
503531
apei_estatus_for_each_section(estatus, gdata) {
@@ -515,7 +543,7 @@ static void ghes_do_proc(struct ghes *ghes,
515543
ghes_edac_report_mem_error(sev, mem_err);
516544

517545
arch_apei_report_mem_error(sev, mem_err);
518-
ghes_handle_memory_failure(gdata, sev);
546+
queued = ghes_handle_memory_failure(gdata, sev);
519547
}
520548
else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
521549
ghes_handle_aer(gdata);
@@ -532,6 +560,8 @@ static void ghes_do_proc(struct ghes *ghes,
532560
gdata->error_data_length);
533561
}
534562
}
563+
564+
return queued;
535565
}
536566

537567
static void __ghes_print_estatus(const char *pfx,
@@ -827,7 +857,9 @@ static void ghes_proc_in_irq(struct irq_work *irq_work)
827857
struct ghes_estatus_node *estatus_node;
828858
struct acpi_hest_generic *generic;
829859
struct acpi_hest_generic_status *estatus;
860+
bool task_work_pending;
830861
u32 len, node_len;
862+
int ret;
831863

832864
llnode = llist_del_all(&ghes_estatus_llist);
833865
/*
@@ -842,14 +874,26 @@ static void ghes_proc_in_irq(struct irq_work *irq_work)
842874
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
843875
len = cper_estatus_len(estatus);
844876
node_len = GHES_ESTATUS_NODE_LEN(len);
845-
ghes_do_proc(estatus_node->ghes, estatus);
877+
task_work_pending = ghes_do_proc(estatus_node->ghes, estatus);
846878
if (!ghes_estatus_cached(estatus)) {
847879
generic = estatus_node->generic;
848880
if (ghes_print_estatus(NULL, generic, estatus))
849881
ghes_estatus_cache_add(generic, estatus);
850882
}
851-
gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
852-
node_len);
883+
884+
if (task_work_pending && current->mm != &init_mm) {
885+
estatus_node->task_work.func = ghes_kick_task_work;
886+
estatus_node->task_work_cpu = smp_processor_id();
887+
ret = task_work_add(current, &estatus_node->task_work,
888+
true);
889+
if (ret)
890+
estatus_node->task_work.func = NULL;
891+
}
892+
893+
if (!estatus_node->task_work.func)
894+
gen_pool_free(ghes_estatus_pool,
895+
(unsigned long)estatus_node, node_len);
896+
853897
llnode = next;
854898
}
855899
}
@@ -909,6 +953,7 @@ static int ghes_in_nmi_queue_one_entry(struct ghes *ghes,
909953

910954
estatus_node->ghes = ghes;
911955
estatus_node->generic = ghes->generic;
956+
estatus_node->task_work.func = NULL;
912957
estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
913958

914959
if (__ghes_read_estatus(estatus, buf_paddr, fixmap_idx, len)) {

include/acpi/ghes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ struct ghes_estatus_node {
3333
struct llist_node llnode;
3434
struct acpi_hest_generic *generic;
3535
struct ghes *ghes;
36+
37+
int task_work_cpu;
38+
struct callback_head task_work;
3639
};
3740

3841
struct ghes_estatus_cache {

0 commit comments

Comments
 (0)