diff options
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | arch/arm/include/asm/kvm_ras.h | 14 | ||||
-rw-r--r-- | arch/arm/include/asm/system_misc.h | 5 | ||||
-rw-r--r-- | arch/arm64/include/asm/acpi.h | 4 | ||||
-rw-r--r-- | arch/arm64/include/asm/daifflags.h | 1 | ||||
-rw-r--r-- | arch/arm64/include/asm/fixmap.h | 6 | ||||
-rw-r--r-- | arch/arm64/include/asm/kvm_ras.h | 25 | ||||
-rw-r--r-- | arch/arm64/include/asm/system_misc.h | 2 | ||||
-rw-r--r-- | arch/arm64/kernel/acpi.c | 31 | ||||
-rw-r--r-- | arch/arm64/mm/fault.c | 24 | ||||
-rw-r--r-- | drivers/acpi/apei/Kconfig | 12 | ||||
-rw-r--r-- | drivers/acpi/apei/bert.c | 23 | ||||
-rw-r--r-- | drivers/acpi/apei/einj.c | 15 | ||||
-rw-r--r-- | drivers/acpi/apei/erst.c | 27 | ||||
-rw-r--r-- | drivers/acpi/apei/ghes.c | 655 | ||||
-rw-r--r-- | drivers/acpi/apei/hest.c | 16 | ||||
-rw-r--r-- | drivers/firmware/arm_sdei.c | 68 | ||||
-rw-r--r-- | drivers/firmware/efi/cper.c | 13 | ||||
-rw-r--r-- | include/acpi/ghes.h | 4 | ||||
-rw-r--r-- | include/linux/arm_sdei.h | 9 | ||||
-rw-r--r-- | virt/kvm/arm/mmu.c | 4 |
21 files changed, 591 insertions, 368 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index dce5c099f43c..3a183d3ac888 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -331,6 +331,7 @@ ACPI APEI M: "Rafael J. Wysocki" <rjw@rjwysocki.net> M: Len Brown <lenb@kernel.org> L: linux-acpi@vger.kernel.org +R: James Morse <james.morse@arm.com> R: Tony Luck <tony.luck@intel.com> R: Borislav Petkov <bp@alien8.de> F: drivers/acpi/apei/ diff --git a/arch/arm/include/asm/kvm_ras.h b/arch/arm/include/asm/kvm_ras.h new file mode 100644 index 000000000000..e9577292dfe4 --- /dev/null +++ b/arch/arm/include/asm/kvm_ras.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2018 - Arm Ltd */ + +#ifndef __ARM_KVM_RAS_H__ +#define __ARM_KVM_RAS_H__ + +#include <linux/types.h> + +static inline int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr) +{ + return -1; +} + +#endif /* __ARM_KVM_RAS_H__ */ diff --git a/arch/arm/include/asm/system_misc.h b/arch/arm/include/asm/system_misc.h index 8e76db83c498..66f6a3ae68d2 100644 --- a/arch/arm/include/asm/system_misc.h +++ b/arch/arm/include/asm/system_misc.h @@ -38,11 +38,6 @@ static inline void harden_branch_predictor(void) extern unsigned int user_debug; -static inline int handle_guest_sea(phys_addr_t addr, unsigned int esr) -{ - return -1; -} - #endif /* !__ASSEMBLY__ */ #endif /* __ASM_ARM_SYSTEM_MISC_H */ diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 2def77ec14be..7628efbe6c12 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -18,6 +18,7 @@ #include <asm/cputype.h> #include <asm/io.h> +#include <asm/ptrace.h> #include <asm/smp_plat.h> #include <asm/tlbflush.h> @@ -110,9 +111,10 @@ static inline u32 get_acpi_id_for_cpu(unsigned int cpu) static inline void arch_fix_phys_package_id(int num, u32 slot) { } void __init acpi_init_cpus(void); - +int apei_claim_sea(struct pt_regs *regs); #else static inline void acpi_init_cpus(void) { } +static inline int apei_claim_sea(struct pt_regs *regs) { return -ENOENT; } #endif /* CONFIG_ACPI */ #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL diff --git a/arch/arm64/include/asm/daifflags.h b/arch/arm64/include/asm/daifflags.h index 8d91f2233135..fa90779fc752 100644 --- a/arch/arm64/include/asm/daifflags.h +++ b/arch/arm64/include/asm/daifflags.h @@ -20,6 +20,7 @@ #define DAIF_PROCCTX 0 #define DAIF_PROCCTX_NOIRQ PSR_I_BIT +#define DAIF_ERRCTX (PSR_I_BIT | PSR_A_BIT) /* mask/save/unmask/restore all exceptions, including interrupts. */ static inline void local_daif_mask(void) diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h index ec1e6d6fa14c..f987b8a8f325 100644 --- a/arch/arm64/include/asm/fixmap.h +++ b/arch/arm64/include/asm/fixmap.h @@ -55,7 +55,11 @@ enum fixed_addresses { #ifdef CONFIG_ACPI_APEI_GHES /* Used for GHES mapping from assorted contexts */ FIX_APEI_GHES_IRQ, - FIX_APEI_GHES_NMI, + FIX_APEI_GHES_SEA, +#ifdef CONFIG_ARM_SDE_INTERFACE + FIX_APEI_GHES_SDEI_NORMAL, + FIX_APEI_GHES_SDEI_CRITICAL, +#endif #endif /* CONFIG_ACPI_APEI_GHES */ #ifdef CONFIG_UNMAP_KERNEL_AT_EL0 diff --git a/arch/arm64/include/asm/kvm_ras.h b/arch/arm64/include/asm/kvm_ras.h new file mode 100644 index 000000000000..8ac6ee77437c --- /dev/null +++ b/arch/arm64/include/asm/kvm_ras.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2018 - Arm Ltd */ + +#ifndef __ARM64_KVM_RAS_H__ +#define __ARM64_KVM_RAS_H__ + +#include <linux/acpi.h> +#include <linux/errno.h> +#include <linux/types.h> + +#include <asm/acpi.h> + +/* + * Was this synchronous external abort a RAS notification? + * Returns '0' for errors handled by some RAS subsystem, or -ENOENT. + */ +static inline int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr) +{ + /* apei_claim_sea(NULL) expects to mask interrupts itself */ + lockdep_assert_irqs_enabled(); + + return apei_claim_sea(NULL); +} + +#endif /* __ARM64_KVM_RAS_H__ */ diff --git a/arch/arm64/include/asm/system_misc.h b/arch/arm64/include/asm/system_misc.h index 0e2a0ecaf484..32693f34f431 100644 --- a/arch/arm64/include/asm/system_misc.h +++ b/arch/arm64/include/asm/system_misc.h @@ -46,8 +46,6 @@ extern void __show_regs(struct pt_regs *); extern void (*arm_pm_restart)(enum reboot_mode reboot_mode, const char *cmd); -int handle_guest_sea(phys_addr_t addr, unsigned int esr); - #endif /* __ASSEMBLY__ */ #endif /* __ASM_SYSTEM_MISC_H */ diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 44e3c351e1ea..803f0494dd3e 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -27,8 +27,10 @@ #include <linux/smp.h> #include <linux/serial_core.h> +#include <acpi/ghes.h> #include <asm/cputype.h> #include <asm/cpu_ops.h> +#include <asm/daifflags.h> #include <asm/pgtable.h> #include <asm/smp_plat.h> @@ -256,3 +258,32 @@ pgprot_t __acpi_get_mem_attribute(phys_addr_t addr) return __pgprot(PROT_NORMAL_NC); return __pgprot(PROT_DEVICE_nGnRnE); } + +/* + * Claim Synchronous External Aborts as a firmware first notification. + * + * Used by KVM and the arch do_sea handler. + * @regs may be NULL when called from process context. + */ +int apei_claim_sea(struct pt_regs *regs) +{ + int err = -ENOENT; + unsigned long current_flags; + + if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) + return err; + + current_flags = arch_local_save_flags(); + + /* + * SEA can interrupt SError, mask it and describe this as an NMI so + * that APEI defers the handling. + */ + local_daif_restore(DAIF_ERRCTX); + nmi_enter(); + err = ghes_notify_sea(); + nmi_exit(); + local_daif_restore(current_flags); + + return err; +} diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index efb7b2cbead5..e1c84c2e1cab 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -18,6 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <linux/acpi.h> #include <linux/extable.h> #include <linux/signal.h> #include <linux/mm.h> @@ -33,6 +34,7 @@ #include <linux/preempt.h> #include <linux/hugetlb.h> +#include <asm/acpi.h> #include <asm/bug.h> #include <asm/cmpxchg.h> #include <asm/cpufeature.h> @@ -47,8 +49,6 @@ #include <asm/tlbflush.h> #include <asm/traps.h> -#include <acpi/ghes.h> - struct fault_info { int (*fn)(unsigned long addr, unsigned int esr, struct pt_regs *regs); @@ -643,19 +643,10 @@ static int do_sea(unsigned long addr, unsigned int esr, struct pt_regs *regs) inf = esr_to_fault_info(esr); /* - * Synchronous aborts may interrupt code which had interrupts masked. - * Before calling out into the wider kernel tell the interested - * subsystems. + * Return value ignored as we rely on signal merging. + * Future patches will make this more robust. */ - if (IS_ENABLED(CONFIG_ACPI_APEI_SEA)) { - if (interrupts_enabled(regs)) - nmi_enter(); - - ghes_notify_sea(); - - if (interrupts_enabled(regs)) - nmi_exit(); - } + apei_claim_sea(regs); if (esr & ESR_ELx_FnV) siaddr = NULL; @@ -733,11 +724,6 @@ static const struct fault_info fault_info[] = { { do_bad, SIGKILL, SI_KERNEL, "unknown 63" }, }; -int handle_guest_sea(phys_addr_t addr, unsigned int esr) -{ - return ghes_notify_sea(); -} - asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs) { diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index 52ae5438edeb..6b18f8bc7be3 100644 --- a/drivers/acpi/apei/Kconfig +++ b/drivers/acpi/apei/Kconfig @@ -41,19 +41,9 @@ config ACPI_APEI_PCIEAER Turn on this option to enable the corresponding support. config ACPI_APEI_SEA - bool "APEI Synchronous External Abort logging/recovering support" + bool depends on ARM64 && ACPI_APEI_GHES default y - help - This option should be enabled if the system supports - firmware first handling of SEA (Synchronous External Abort). - SEA happens with certain faults of data abort or instruction - abort synchronous exceptions on ARMv8 systems. If a system - supports firmware first handling of SEA, the platform analyzes - and handles hardware error notifications from SEA, and it may then - form a HW error record for the OS to parse and handle. This - option allows the OS to look for such hardware error record, and - take appropriate action. config ACPI_APEI_MEMORY_FAILURE bool "APEI memory error recovering support" diff --git a/drivers/acpi/apei/bert.c b/drivers/acpi/apei/bert.c index 12771fcf0417..0d948d0a41af 100644 --- a/drivers/acpi/apei/bert.c +++ b/drivers/acpi/apei/bert.c @@ -42,15 +42,7 @@ static void __init bert_print_all(struct acpi_bert_region *region, int remain = region_len; u32 estatus_len; - if (!estatus->block_status) - return; - - while (remain > sizeof(struct acpi_bert_region)) { - if (cper_estatus_check(estatus)) { - pr_err(FW_BUG "Invalid error record.\n"); - return; - } - + while (remain >= sizeof(struct acpi_bert_region)) { estatus_len = cper_estatus_len(estatus); if (remain < estatus_len) { pr_err(FW_BUG "Truncated status block (length: %u).\n", @@ -58,6 +50,15 @@ static void __init bert_print_all(struct acpi_bert_region *region, return; } + /* No more error records. */ + if (!estatus->block_status) + return; + + if (cper_estatus_check(estatus)) { + pr_err(FW_BUG "Invalid error record.\n"); + return; + } + pr_info_once("Error records from previous boot:\n"); cper_estatus_print(KERN_INFO HW_ERR, estatus); @@ -70,10 +71,6 @@ static void __init bert_print_all(struct acpi_bert_region *region, estatus->block_status = 0; estatus = (void *)estatus + estatus_len; - /* No more error records. */ - if (!estatus->block_status) - return; - remain -= estatus_len; } } diff --git a/drivers/acpi/apei/einj.c b/drivers/acpi/apei/einj.c index c42299e048e4..2d4be94f8c00 100644 --- a/drivers/acpi/apei/einj.c +++ b/drivers/acpi/apei/einj.c @@ -644,8 +644,8 @@ static int error_type_set(void *data, u64 val) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(error_type_fops, error_type_get, - error_type_set, "0x%llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(error_type_fops, error_type_get, error_type_set, + "0x%llx\n"); static int error_inject_set(void *data, u64 val) { @@ -656,8 +656,7 @@ static int error_inject_set(void *data, u64 val) error_param3, error_param4); } -DEFINE_SIMPLE_ATTRIBUTE(error_inject_fops, NULL, - error_inject_set, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(error_inject_fops, NULL, error_inject_set, "%llu\n"); static int einj_check_table(struct acpi_table_einj *einj_tab) { @@ -709,10 +708,10 @@ static int __init einj_init(void) debugfs_create_file("available_error_type", S_IRUSR, einj_debug_dir, NULL, &available_error_type_fops); - debugfs_create_file("error_type", S_IRUSR | S_IWUSR, einj_debug_dir, - NULL, &error_type_fops); - debugfs_create_file("error_inject", S_IWUSR, einj_debug_dir, - NULL, &error_inject_fops); + debugfs_create_file_unsafe("error_type", 0600, einj_debug_dir, + NULL, &error_type_fops); + debugfs_create_file_unsafe("error_inject", 0200, einj_debug_dir, + NULL, &error_inject_fops); apei_resources_init(&einj_resources); einj_exec_ctx_init(&ctx); diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c index 9953e50667ec..389d88e35ffb 100644 --- a/drivers/acpi/apei/erst.c +++ b/drivers/acpi/apei/erst.c @@ -938,17 +938,17 @@ static struct pstore_info erst_info = { }; #define CPER_CREATOR_PSTORE \ - UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \ - 0x64, 0x90, 0xb8, 0x9d) + GUID_INIT(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \ + 0x64, 0x90, 0xb8, 0x9d) #define CPER_SECTION_TYPE_DMESG \ - UUID_LE(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54, \ - 0x94, 0x19, 0xeb, 0x12) + GUID_INIT(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54, \ + 0x94, 0x19, 0xeb, 0x12) #define CPER_SECTION_TYPE_DMESG_Z \ - UUID_LE(0x4f118707, 0x04dd, 0x4055, 0xb5, 0xdd, 0x95, 0x6d, \ - 0x34, 0xdd, 0xfa, 0xc6) + GUID_INIT(0x4f118707, 0x04dd, 0x4055, 0xb5, 0xdd, 0x95, 0x6d, \ + 0x34, 0xdd, 0xfa, 0xc6) #define CPER_SECTION_TYPE_MCE \ - UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \ - 0x04, 0x4a, 0x38, 0xfc) + GUID_INIT(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \ + 0x04, 0x4a, 0x38, 0xfc) struct cper_pstore_record { struct cper_record_header hdr; @@ -1012,7 +1012,7 @@ skip: rc = -EIO; goto out; } - if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0) + if (!guid_equal(&rcd->hdr.creator_id, &CPER_CREATOR_PSTORE)) goto skip; record->buf = kmalloc(len, GFP_KERNEL); @@ -1024,15 +1024,12 @@ skip: record->id = record_id; record->compressed = false; record->ecc_notice_size = 0; - if (uuid_le_cmp(rcd->sec_hdr.section_type, - CPER_SECTION_TYPE_DMESG_Z) == 0) { + if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_DMESG_Z)) { record->type = PSTORE_TYPE_DMESG; record->compressed = true; - } else if (uuid_le_cmp(rcd->sec_hdr.section_type, - CPER_SECTION_TYPE_DMESG) == 0) + } else if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_DMESG)) record->type = PSTORE_TYPE_DMESG; - else if (uuid_le_cmp(rcd->sec_hdr.section_type, - CPER_SECTION_TYPE_MCE) == 0) + else if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_MCE)) record->type = PSTORE_TYPE_MCE; else record->type = PSTORE_TYPE_MAX; diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index f008ba7c9ced..0b5ae91fd0fb 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -25,6 +25,7 @@ * GNU General Public License for more details. */ +#include <linux/arm_sdei.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/init.h> @@ -33,7 +34,6 @@ #include <linux/interrupt.h> #include <linux/timer.h> #include <linux/cper.h> -#include <linux/kdebug.h> #include <linux/platform_device.h> #include <linux/mutex.h> #include <linux/ratelimit.h> @@ -42,6 +42,7 @@ #include <linux/llist.h> #include <linux/genalloc.h> #include <linux/pci.h> +#include <linux/pfn.h> #include <linux/aer.h> #include <linux/nmi.h> #include <linux/sched/clock.h> @@ -85,6 +86,15 @@ ((struct acpi_hest_generic_status *) \ ((struct ghes_estatus_node *)(estatus_node) + 1)) +/* + * NMI-like notifications vary by architecture, before the compiler can prune + * unused static functions it needs a value for these enums. + */ +#ifndef CONFIG_ARM_SDE_INTERFACE +#define FIX_APEI_GHES_SDEI_NORMAL __end_of_fixed_addresses +#define FIX_APEI_GHES_SDEI_CRITICAL __end_of_fixed_addresses +#endif + static inline bool is_hest_type_generic_v2(struct ghes *ghes) { return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2; @@ -115,11 +125,10 @@ static DEFINE_MUTEX(ghes_list_mutex); * handler, but general ioremap can not be used in atomic context, so * the fixmap is used instead. * - * These 2 spinlocks are used to prevent the fixmap entries from being used + * This spinlock is used to prevent the fixmap entry from being used * simultaneously. */ -static DEFINE_RAW_SPINLOCK(ghes_ioremap_lock_nmi); -static DEFINE_SPINLOCK(ghes_ioremap_lock_irq); +static DEFINE_SPINLOCK(ghes_notify_lock_irq); static struct gen_pool *ghes_estatus_pool; static unsigned long ghes_estatus_pool_size_request; @@ -129,82 +138,49 @@ static atomic_t ghes_estatus_cache_alloced; static int ghes_panic_timeout __read_mostly = 30; -static void __iomem *ghes_ioremap_pfn_nmi(u64 pfn) +static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx) { phys_addr_t paddr; pgprot_t prot; - paddr = pfn << PAGE_SHIFT; + paddr = PFN_PHYS(pfn); prot = arch_apei_get_mem_attribute(paddr); - __set_fixmap(FIX_APEI_GHES_NMI, paddr, prot); + __set_fixmap(fixmap_idx, paddr, prot); - return (void __iomem *) fix_to_virt(FIX_APEI_GHES_NMI); + return (void __iomem *) __fix_to_virt(fixmap_idx); } -static void __iomem *ghes_ioremap_pfn_irq(u64 pfn) +static void ghes_unmap(void __iomem *vaddr, enum fixed_addresses fixmap_idx) { - phys_addr_t paddr; - pgprot_t prot; - - paddr = pfn << PAGE_SHIFT; - prot = arch_apei_get_mem_attribute(paddr); - __set_fixmap(FIX_APEI_GHES_IRQ, paddr, prot); - - return (void __iomem *) fix_to_virt(FIX_APEI_GHES_IRQ); -} + int _idx = virt_to_fix((unsigned long)vaddr); -static void ghes_iounmap_nmi(void) -{ - clear_fixmap(FIX_APEI_GHES_NMI); + WARN_ON_ONCE(fixmap_idx != _idx); + clear_fixmap(fixmap_idx); } -static void ghes_iounmap_irq(void) +int ghes_estatus_pool_init(int num_ghes) { - clear_fixmap(FIX_APEI_GHES_IRQ); -} + unsigned long addr, len; -static int ghes_estatus_pool_init(void) -{ ghes_estatus_pool = gen_pool_create(GHES_ESTATUS_POOL_MIN_ALLOC_ORDER, -1); if (!ghes_estatus_pool) return -ENOMEM; - return 0; -} -static void ghes_estatus_pool_free_chunk_page(struct gen_pool *pool, - struct gen_pool_chunk *chunk, - void *data) -{ - free_page(chunk->start_addr); -} - -static void ghes_estatus_pool_exit(void) -{ - gen_pool_for_each_chunk(ghes_estatus_pool, - ghes_estatus_pool_free_chunk_page, NULL); - gen_pool_destroy(ghes_estatus_pool); -} + len = GHES_ESTATUS_CACHE_AVG_SIZE * GHES_ESTATUS_CACHE_ALLOCED_MAX; + len += (num_ghes * GHES_ESOURCE_PREALLOC_MAX_SIZE); -static int ghes_estatus_pool_expand(unsigned long len) -{ - unsigned long i, pages, size, addr; - int ret; + ghes_estatus_pool_size_request = PAGE_ALIGN(len); + addr = (unsigned long)vmalloc(PAGE_ALIGN(len)); + if (!addr) + return -ENOMEM; - ghes_estatus_pool_size_request += PAGE_ALIGN(len); - size = gen_pool_size(ghes_estatus_pool); - if (size >= ghes_estatus_pool_size_request) - return 0; - pages = (ghes_estatus_pool_size_request - size) / PAGE_SIZE; - for (i = 0; i < pages; i++) { - addr = __get_free_page(GFP_KERNEL); - if (!addr) - return -ENOMEM; - ret = gen_pool_add(ghes_estatus_pool, addr, PAGE_SIZE, -1); - if (ret) - return ret; - } + /* + * New allocation must be visible in all pgd before it can be found by + * an NMI allocating from the pool. + */ + vmalloc_sync_all(); - return 0; + return gen_pool_add(ghes_estatus_pool, addr, PAGE_ALIGN(len), -1); } static int map_gen_v2(struct ghes *ghes) @@ -217,6 +193,21 @@ static void unmap_gen_v2(struct ghes *ghes) apei_unmap_generic_address(&ghes->generic_v2->read_ack_register); } +static void 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; + + val &= gv2->read_ack_preserve << gv2->read_ack_register.bit_offset; + val |= gv2->read_ack_write << gv2->read_ack_register.bit_offset; + + apei_write(val, &gv2->read_ack_register); +} + static struct ghes *ghes_new(struct acpi_hest_generic *generic) { struct ghes *ghes; @@ -289,23 +280,16 @@ static inline int ghes_severity(int severity) } static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, - int from_phys) + int from_phys, + enum fixed_addresses fixmap_idx) { void __iomem *vaddr; - unsigned long flags = 0; - int in_nmi = in_nmi(); u64 offset; u32 trunk; while (len > 0) { offset = paddr - (paddr & PAGE_MASK); - if (in_nmi) { - raw_spin_lock(&ghes_ioremap_lock_nmi); - vaddr = ghes_ioremap_pfn_nmi(paddr >> PAGE_SHIFT); - } else { - spin_lock_irqsave(&ghes_ioremap_lock_irq, flags); - vaddr = ghes_ioremap_pfn_irq(paddr >> PAGE_SHIFT); - } + vaddr = ghes_map(PHYS_PFN(paddr), fixmap_idx); trunk = PAGE_SIZE - offset; trunk = min(trunk, len); if (from_phys) @@ -315,72 +299,114 @@ static void ghes_copy_tofrom_phys(void *buffer, u64 paddr, u32 len, len -= trunk; paddr += trunk; buffer += trunk; - if (in_nmi) { - ghes_iounmap_nmi(); - raw_spin_unlock(&ghes_ioremap_lock_nmi); - } else { - ghes_iounmap_irq(); - spin_unlock_irqrestore(&ghes_ioremap_lock_irq, flags); - } + ghes_unmap(vaddr, fixmap_idx); + } +} + +/* Check the top-level record header has an appropriate size. */ +static int __ghes_check_estatus(struct ghes *ghes, + struct acpi_hest_generic_status *estatus) +{ + u32 len = cper_estatus_len(estatus); + + if (len < sizeof(*estatus)) { + pr_warn_ratelimited(FW_WARN GHES_PFX "Truncated error status block!\n"); + return -EIO; + } + + if (len > ghes->generic->error_block_length) { + pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid error status block length!\n"); + return -EIO; + } + + if (cper_estatus_check_header(estatus)) { + pr_warn_ratelimited(FW_WARN GHES_PFX "Invalid CPER header!\n"); + return -EIO; } + + return 0; } -static int ghes_read_estatus(struct ghes *ghes, int silent) +/* Read the CPER block, returning its address, and header in estatus. */ +static int __ghes_peek_estatus(struct ghes *ghes, + struct acpi_hest_generic_status *estatus, + u64 *buf_paddr, enum fixed_addresses fixmap_idx) { struct acpi_hest_generic *g = ghes->generic; - u64 buf_paddr; - u32 len; int rc; - rc = apei_read(&buf_paddr, &g->error_status_address); + rc = apei_read(buf_paddr, &g->error_status_address); if (rc) { - if (!silent && printk_ratelimit()) - pr_warning(FW_WARN GHES_PFX + *buf_paddr = 0; + pr_warn_ratelimited(FW_WARN GHES_PFX "Failed to read error status block address for hardware error source: %d.\n", g->header.source_id); return -EIO; } - if (!buf_paddr) + if (!*buf_paddr) return -ENOENT; - ghes_copy_tofrom_phys(ghes->estatus, buf_paddr, - sizeof(*ghes->estatus), 1); - if (!ghes->estatus->block_status) + ghes_copy_tofrom_phys(estatus, *buf_paddr, sizeof(*estatus), 1, + fixmap_idx); + if (!estatus->block_status) { + *buf_paddr = 0; return -ENOENT; + } - ghes->buffer_paddr = buf_paddr; - ghes->flags |= GHES_TO_CLEAR; + return __ghes_check_estatus(ghes, estatus); +} - rc = -EIO; - len = cper_estatus_len(ghes->estatus); - if (len < sizeof(*ghes->estatus)) - goto err_read_block; - if (len > ghes->generic->error_block_length) - goto err_read_block; - if (cper_estatus_check_header(ghes->estatus)) - goto err_read_block; - ghes_copy_tofrom_phys(ghes->estatus + 1, - buf_paddr + sizeof(*ghes->estatus), - len - sizeof(*ghes->estatus), 1); - if (cper_estatus_check(ghes->estatus)) - goto err_read_block; - rc = 0; - -err_read_block: - if (rc && !silent && printk_ratelimit()) - pr_warning(FW_WARN GHES_PFX - "Failed to read error status block!\n"); - return rc; +static int __ghes_read_estatus(struct acpi_hest_generic_status *estatus, + u64 buf_paddr, enum fixed_addresses fixmap_idx, + size_t buf_len) +{ + ghes_copy_tofrom_phys(estatus, buf_paddr, buf_len, 1, fixmap_idx); + if (cper_estatus_check(estatus)) { + pr_warn_ratelimited(FW_WARN GHES_PFX + "Failed to read error status block!\n"); + return -EIO; + } + + return 0; } -static void ghes_clear_estatus(struct ghes *ghes) +static int ghes_read_estatus(struct ghes *ghes, + struct acpi_hest_generic_status *estatus, + u64 *buf_paddr, enum fixed_addresses fixmap_idx) { - ghes->estatus->block_status = 0; - if (!(ghes->flags & GHES_TO_CLEAR)) + int rc; + + rc = __ghes_peek_estatus(ghes, estatus, buf_paddr, fixmap_idx); + if (rc) + return rc; + + rc = __ghes_check_estatus(ghes, estatus); + if (rc) + return rc; + + return __ghes_read_estatus(estatus, *buf_paddr, fixmap_idx, + cper_estatus_len(estatus)); +} + +static void ghes_clear_estatus(struct ghes *ghes, + struct acpi_hest_generic_status *estatus, + u64 buf_paddr, enum fixed_addresses fixmap_idx) +{ + estatus->block_status = 0; + + if (!buf_paddr) return; - ghes_copy_tofrom_phys(ghes->estatus, ghes->buffer_paddr, - sizeof(ghes->estatus->block_status), 0); - ghes->flags &= ~GHES_TO_CLEAR; + + ghes_copy_tofrom_phys(estatus, buf_paddr, + sizeof(estatus->block_status), 0, + fixmap_idx); + + /* + * 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)) + ghes_ack_error(ghes->generic_v2); } static void ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata, int sev) @@ -672,26 +698,13 @@ static void ghes_estatus_cache_add( rcu_read_unlock(); } -static int ghes_ack_error(struct acpi_hest_generic_v2 *gv2) +static void __ghes_panic(struct ghes *ghes, + struct acpi_hest_generic_status *estatus, + u64 buf_paddr, enum fixed_addresses fixmap_idx) { - int rc; - u64 val = 0; + __ghes_print_estatus(KERN_EMERG, ghes->generic, estatus); - 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 void __ghes_panic(struct ghes *ghes) -{ - __ghes_print_estatus(KERN_EMERG, ghes->generic, ghes->estatus); - - ghes_clear_estatus(ghes); + ghes_clear_estatus(ghes, estatus, buf_paddr, fixmap_idx); /* reboot to log the error! */ if (!panic_timeout) @@ -701,34 +714,25 @@ static void __ghes_panic(struct ghes *ghes) static int ghes_proc(struct ghes *ghes) { + struct acpi_hest_generic_status *estatus = ghes->estatus; + u64 buf_paddr; int rc; - rc = ghes_read_estatus(ghes, 0); + rc = ghes_read_estatus(ghes, estatus, &buf_paddr, FIX_APEI_GHES_IRQ); if (rc) goto out; - if (ghes_severity(ghes->estatus->error_severity) >= GHES_SEV_PANIC) { - __ghes_panic(ghes); - } + if (ghes_severity(estatus->error_severity) >= GHES_SEV_PANIC) + __ghes_panic(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ); - if (!ghes_estatus_cached(ghes->estatus)) { - if (ghes_print_estatus(NULL, ghes->generic, ghes->estatus)) - ghes_estatus_cache_add(ghes->generic, ghes->estatus); + if (!ghes_estatus_cached(estatus)) { + if (ghes_print_estatus(NULL, ghes->generic, estatus)) + ghes_estatus_cache_add(ghes->generic, estatus); } - ghes_do_proc(ghes, ghes->estatus); + ghes_do_proc(ghes, estatus); out: - ghes_clear_estatus(ghes); - - if (rc == -ENOENT) - return rc; - - /* - * 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)) - return ghes_ack_error(ghes->generic_v2); + ghes_clear_estatus(ghes, estatus, buf_paddr, FIX_APEI_GHES_IRQ); return rc; } @@ -751,8 +755,11 @@ static void ghes_add_timer(struct ghes *ghes) static void ghes_poll_func(struct timer_list *t) { struct ghes *ghes = from_timer(ghes, t, timer); + unsigned long flags; + spin_lock_irqsave(&ghes_notify_lock_irq, flags); ghes_proc(ghes); + spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); if (!(ghes->flags & GHES_EXITING)) ghes_add_timer(ghes); } @@ -760,9 +767,12 @@ static void ghes_poll_func(struct timer_list *t) static irqreturn_t ghes_irq_func(int irq, void *data) { struct ghes *ghes = data; + unsigned long flags; int rc; + spin_lock_irqsave(&ghes_notify_lock_irq, flags); rc = ghes_proc(ghes); + spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); if (rc) return IRQ_NONE; @@ -773,14 +783,17 @@ static int ghes_notify_hed(struct notifier_block *this, unsigned long event, void *data) { struct ghes *ghes; + unsigned long flags; int ret = NOTIFY_DONE; + spin_lock_irqsave(&ghes_notify_lock_irq, flags); rcu_read_lock(); list_for_each_entry_rcu(ghes, &ghes_hed, list) { if (!ghes_proc(ghes)) ret = NOTIFY_OK; } rcu_read_unlock(); + spin_unlock_irqrestore(&ghes_notify_lock_irq, flags); return ret; } @@ -789,66 +802,20 @@ static struct notifier_block ghes_notifier_hed = { .notifier_call = ghes_notify_hed, }; -#ifdef CONFIG_ACPI_APEI_SEA -static LIST_HEAD(ghes_sea); - -/* - * Return 0 only if one of the SEA error sources successfully reported an error - * record sent from the firmware. |