Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ACPI / APEI: Add a notifier chain for unknown (vendor) CPER records

CPER records describing a firmware-first error are identified by GUID.
The ghes driver currently logs, but ignores any unknown CPER records.
This prevents describing errors that can't be represented by a standard
entry, that would otherwise allow a driver to recover from an error.
The UEFI spec calls these 'Non-standard Section Body' (N.2.3 of
version 2.8).

Add a notifier chain for these non-standard/vendor-records. Callers
must identify their type of records by GUID.

Record data is copied to memory from the ghes_estatus_pool to allow
us to keep it until after the notifier has run.

Co-developed-by: James Morse <james.morse@arm.com>
Link: https://lore.kernel.org/r/20200903123456.1823-2-shiju.jose@huawei.com
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Shiju Jose <shiju.jose@huawei.com>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Acked-by: "Rafael J. Wysocki" <rjw@rjwysocki.net>

authored by

Shiju Jose and committed by
Lorenzo Pieralisi
9aa9cf3e 9123e3a7

+81
+63
drivers/acpi/apei/ghes.c
··· 79 79 ((struct acpi_hest_generic_status *) \ 80 80 ((struct ghes_estatus_node *)(estatus_node) + 1)) 81 81 82 + #define GHES_VENDOR_ENTRY_LEN(gdata_len) \ 83 + (sizeof(struct ghes_vendor_record_entry) + (gdata_len)) 84 + #define GHES_GDATA_FROM_VENDOR_ENTRY(vendor_entry) \ 85 + ((struct acpi_hest_generic_data *) \ 86 + ((struct ghes_vendor_record_entry *)(vendor_entry) + 1)) 87 + 82 88 /* 83 89 * NMI-like notifications vary by architecture, before the compiler can prune 84 90 * unused static functions it needs a value for these enums. ··· 128 122 * simultaneously. 129 123 */ 130 124 static DEFINE_SPINLOCK(ghes_notify_lock_irq); 125 + 126 + struct ghes_vendor_record_entry { 127 + struct work_struct work; 128 + int error_severity; 129 + char vendor_record[]; 130 + }; 131 131 132 132 static struct gen_pool *ghes_estatus_pool; 133 133 static unsigned long ghes_estatus_pool_size_request; ··· 523 511 #endif 524 512 } 525 513 514 + static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list); 515 + 516 + int ghes_register_vendor_record_notifier(struct notifier_block *nb) 517 + { 518 + return blocking_notifier_chain_register(&vendor_record_notify_list, nb); 519 + } 520 + EXPORT_SYMBOL_GPL(ghes_register_vendor_record_notifier); 521 + 522 + void ghes_unregister_vendor_record_notifier(struct notifier_block *nb) 523 + { 524 + blocking_notifier_chain_unregister(&vendor_record_notify_list, nb); 525 + } 526 + EXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier); 527 + 528 + static void ghes_vendor_record_work_func(struct work_struct *work) 529 + { 530 + struct ghes_vendor_record_entry *entry; 531 + struct acpi_hest_generic_data *gdata; 532 + u32 len; 533 + 534 + entry = container_of(work, struct ghes_vendor_record_entry, work); 535 + gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry); 536 + 537 + blocking_notifier_call_chain(&vendor_record_notify_list, 538 + entry->error_severity, gdata); 539 + 540 + len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata)); 541 + gen_pool_free(ghes_estatus_pool, (unsigned long)entry, len); 542 + } 543 + 544 + static void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata, 545 + int sev) 546 + { 547 + struct acpi_hest_generic_data *copied_gdata; 548 + struct ghes_vendor_record_entry *entry; 549 + u32 len; 550 + 551 + len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata)); 552 + entry = (void *)gen_pool_alloc(ghes_estatus_pool, len); 553 + if (!entry) 554 + return; 555 + 556 + copied_gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry); 557 + memcpy(copied_gdata, gdata, acpi_hest_get_record_size(gdata)); 558 + entry->error_severity = sev; 559 + 560 + INIT_WORK(&entry->work, ghes_vendor_record_work_func); 561 + schedule_work(&entry->work); 562 + } 563 + 526 564 static bool ghes_do_proc(struct ghes *ghes, 527 565 const struct acpi_hest_generic_status *estatus) 528 566 { ··· 611 549 } else { 612 550 void *err = acpi_hest_get_payload(gdata); 613 551 552 + ghes_defer_non_standard_event(gdata, sev); 614 553 log_non_standard_event(sec_type, fru_id, fru_text, 615 554 sec_sev, err, 616 555 gdata->error_data_length);
+18
include/acpi/ghes.h
··· 53 53 GHES_SEV_PANIC = 0x3, 54 54 }; 55 55 56 + #ifdef CONFIG_ACPI_APEI_GHES 57 + /** 58 + * ghes_register_vendor_record_notifier - register a notifier for vendor 59 + * records that the kernel would otherwise ignore. 60 + * @nb: pointer to the notifier_block structure of the event handler. 61 + * 62 + * return 0 : SUCCESS, non-zero : FAIL 63 + */ 64 + int ghes_register_vendor_record_notifier(struct notifier_block *nb); 65 + 66 + /** 67 + * ghes_unregister_vendor_record_notifier - unregister the previously 68 + * registered vendor record notifier. 69 + * @nb: pointer to the notifier_block structure of the vendor record handler. 70 + */ 71 + void ghes_unregister_vendor_record_notifier(struct notifier_block *nb); 72 + #endif 73 + 56 74 int ghes_estatus_pool_init(int num_ghes); 57 75 58 76 /* From drivers/edac/ghes_edac.c */