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

arm64: KVM/mm: Move SEA handling behind a single 'claim' interface

To split up APEIs in_nmi() path, the caller needs to always be
in_nmi(). Add a helper to do the work and claim the notification.

When KVM or the arch code takes an exception that might be a RAS
notification, it asks the APEI firmware-first code whether it wants
to claim the exception. A future kernel-first mechanism may be queried
afterwards, and claim the notification, otherwise we fall through
to the existing default behaviour.

The NOTIFY_SEA code was merged before considering multiple, possibly
interacting, NMI-like notifications and the need to consider kernel
first in the future. Make the 'claiming' behaviour explicit.

Restructuring the APEI code to allow multiple NMI-like notifications
means any notification that might interrupt interrupts-masked
code must always be wrapped in nmi_enter()/nmi_exit(). This will
allow APEI to use in_nmi() to use the right fixmap entries.

Mask SError over this window to prevent an asynchronous RAS error
arriving and tripping 'nmi_enter()'s BUG_ON(in_nmi()).

Signed-off-by: James Morse <james.morse@arm.com>
Acked-by: Marc Zyngier <marc.zyngier@arm.com>
Tested-by: Tyler Baicar <tbaicar@codeaurora.org>
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

James Morse and committed by
Rafael J. Wysocki
d44f1b8d 0db5e022

+55 -21
+3 -1
arch/arm64/include/asm/acpi.h
··· 18 18 19 19 #include <asm/cputype.h> 20 20 #include <asm/io.h> 21 + #include <asm/ptrace.h> 21 22 #include <asm/smp_plat.h> 22 23 #include <asm/tlbflush.h> 23 24 ··· 111 110 112 111 static inline void arch_fix_phys_package_id(int num, u32 slot) { } 113 112 void __init acpi_init_cpus(void); 114 - 113 + int apei_claim_sea(struct pt_regs *regs); 115 114 #else 116 115 static inline void acpi_init_cpus(void) { } 116 + static inline int apei_claim_sea(struct pt_regs *regs) { return -ENOENT; } 117 117 #endif /* CONFIG_ACPI */ 118 118 119 119 #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL
+1
arch/arm64/include/asm/daifflags.h
··· 20 20 21 21 #define DAIF_PROCCTX 0 22 22 #define DAIF_PROCCTX_NOIRQ PSR_I_BIT 23 + #define DAIF_ERRCTX (PSR_I_BIT | PSR_A_BIT) 23 24 24 25 /* mask/save/unmask/restore all exceptions, including interrupts. */ 25 26 static inline void local_daif_mask(void)
+15 -1
arch/arm64/include/asm/kvm_ras.h
··· 4 4 #ifndef __ARM64_KVM_RAS_H__ 5 5 #define __ARM64_KVM_RAS_H__ 6 6 7 + #include <linux/acpi.h> 8 + #include <linux/errno.h> 7 9 #include <linux/types.h> 8 10 9 - int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr); 11 + #include <asm/acpi.h> 12 + 13 + /* 14 + * Was this synchronous external abort a RAS notification? 15 + * Returns '0' for errors handled by some RAS subsystem, or -ENOENT. 16 + */ 17 + static inline int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr) 18 + { 19 + /* apei_claim_sea(NULL) expects to mask interrupts itself */ 20 + lockdep_assert_irqs_enabled(); 21 + 22 + return apei_claim_sea(NULL); 23 + } 10 24 11 25 #endif /* __ARM64_KVM_RAS_H__ */
+31
arch/arm64/kernel/acpi.c
··· 27 27 #include <linux/smp.h> 28 28 #include <linux/serial_core.h> 29 29 30 + #include <acpi/ghes.h> 30 31 #include <asm/cputype.h> 31 32 #include <asm/cpu_ops.h> 33 + #include <asm/daifflags.h> 32 34 #include <asm/pgtable.h> 33 35 #include <asm/smp_plat.h> 34 36 ··· 257 255 if (attr & EFI_MEMORY_WC) 258 256 return __pgprot(PROT_NORMAL_NC); 259 257 return __pgprot(PROT_DEVICE_nGnRnE); 258 + } 259 + 260 + /* 261 + * Claim Synchronous External Aborts as a firmware first notification. 262 + * 263 + * Used by KVM and the arch do_sea handler. 264 + * @regs may be NULL when called from process context. 265 + */ 266 + int apei_claim_sea(struct pt_regs *regs) 267 + { 268 + int err = -ENOENT; 269 + unsigned long current_flags; 270 + 271 + if (!IS_ENABLED(CONFIG_ACPI_APEI_GHES)) 272 + return err; 273 + 274 + current_flags = arch_local_save_flags(); 275 + 276 + /* 277 + * SEA can interrupt SError, mask it and describe this as an NMI so 278 + * that APEI defers the handling. 279 + */ 280 + local_daif_restore(DAIF_ERRCTX); 281 + nmi_enter(); 282 + err = ghes_notify_sea(); 283 + nmi_exit(); 284 + local_daif_restore(current_flags); 285 + 286 + return err; 260 287 }
+5 -19
arch/arm64/mm/fault.c
··· 18 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 19 */ 20 20 21 + #include <linux/acpi.h> 21 22 #include <linux/extable.h> 22 23 #include <linux/signal.h> 23 24 #include <linux/mm.h> ··· 34 33 #include <linux/preempt.h> 35 34 #include <linux/hugetlb.h> 36 35 36 + #include <asm/acpi.h> 37 37 #include <asm/bug.h> 38 38 #include <asm/cmpxchg.h> 39 39 #include <asm/cpufeature.h> ··· 48 46 #include <asm/pgtable.h> 49 47 #include <asm/tlbflush.h> 50 48 #include <asm/traps.h> 51 - 52 - #include <acpi/ghes.h> 53 49 54 50 struct fault_info { 55 51 int (*fn)(unsigned long addr, unsigned int esr, ··· 643 643 inf = esr_to_fault_info(esr); 644 644 645 645 /* 646 - * Synchronous aborts may interrupt code which had interrupts masked. 647 - * Before calling out into the wider kernel tell the interested 648 - * subsystems. 646 + * Return value ignored as we rely on signal merging. 647 + * Future patches will make this more robust. 649 648 */ 650 - if (IS_ENABLED(CONFIG_ACPI_APEI_SEA)) { 651 - if (interrupts_enabled(regs)) 652 - nmi_enter(); 653 - 654 - ghes_notify_sea(); 655 - 656 - if (interrupts_enabled(regs)) 657 - nmi_exit(); 658 - } 649 + apei_claim_sea(regs); 659 650 660 651 if (esr & ESR_ELx_FnV) 661 652 siaddr = NULL; ··· 723 732 { do_bad, SIGKILL, SI_KERNEL, "page domain fault" }, 724 733 { do_bad, SIGKILL, SI_KERNEL, "unknown 63" }, 725 734 }; 726 - 727 - int kvm_handle_guest_sea(phys_addr_t addr, unsigned int esr) 728 - { 729 - return ghes_notify_sea(); 730 - } 731 735 732 736 asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr, 733 737 struct pt_regs *regs)