[PATCH] arm64: efi: Recover from synchronous exceptions occurring in firmware
Ard Biesheuvel
ardb at kernel.org
Wed Nov 2 02:08:28 PDT 2022
On Fri, 28 Oct 2022 at 17:01, Ard Biesheuvel <ardb at kernel.org> wrote:
>
> Unlike x86, which has machinery to deal with page faults that occur
> during the execution of EFI runtime services, arm64 has nothing like
> that, and a synchronous exception raised by firmware code brings down
> the whole system.
>
> With more EFI based systems appearing that were not built to run Linux
> (such as the Windows-on-ARM laptops based on Qualcomm SOCs), as well as
> the introduction of PRM (platform specific firmware routines that are
> callable just like EFI runtime services), we are more likely to run into
> issues of this sort, and it is much more likely that we can identify and
> work around such issues if they don't bring down the system entirely.
>
> Since we already use a EFI runtime services call wrapper in assembler,
> we can quite easily add some code that captures the execution state at
> the point where the call is made, allowing us to revert to this state
> and proceed execution if the call triggered a synchronous exception.
>
> Given that the kernel and the firmware don't share any data structures
> that could end up in an indeterminate state, we can happily continue
> running, as long as we mark the EFI runtime services as unavailable from
> that point on.
>
> Signed-off-by: Ard Biesheuvel <ardb at kernel.org>
Does anyone mind if I take this via the EFI tree for v6.1?
> ---
> arch/arm64/include/asm/efi.h | 8 ++++++++
> arch/arm64/kernel/efi-rt-wrapper.S | 33 ++++++++++++++++++++++++++++--
> arch/arm64/kernel/efi.c | 26 +++++++++++++++++++++++
> arch/arm64/mm/fault.c | 4 ++++
> 4 files changed, 69 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/include/asm/efi.h b/arch/arm64/include/asm/efi.h
> index 439e2bc5d5d8..d6cf535d8352 100644
> --- a/arch/arm64/include/asm/efi.h
> +++ b/arch/arm64/include/asm/efi.h
> @@ -14,8 +14,16 @@
>
> #ifdef CONFIG_EFI
> extern void efi_init(void);
> +
> +bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg);
> #else
> #define efi_init()
> +
> +static inline
> +bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg)
> +{
> + return false;
> +}
> #endif
>
> int efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md);
> diff --git a/arch/arm64/kernel/efi-rt-wrapper.S b/arch/arm64/kernel/efi-rt-wrapper.S
> index 75691a2641c1..67babd5f04c2 100644
> --- a/arch/arm64/kernel/efi-rt-wrapper.S
> +++ b/arch/arm64/kernel/efi-rt-wrapper.S
> @@ -6,7 +6,7 @@
> #include <linux/linkage.h>
>
> SYM_FUNC_START(__efi_rt_asm_wrapper)
> - stp x29, x30, [sp, #-32]!
> + stp x29, x30, [sp, #-112]!
> mov x29, sp
>
> /*
> @@ -16,6 +16,20 @@ SYM_FUNC_START(__efi_rt_asm_wrapper)
> */
> stp x1, x18, [sp, #16]
>
> + /*
> + * Preserve all callee saved registers and record the stack pointer
> + * value in a per-CPU variable so we can recover from synchronous
> + * exceptions occurring while running the firmware routines.
> + */
> + stp x19, x20, [sp, #32]
> + stp x21, x22, [sp, #48]
> + stp x23, x24, [sp, #64]
> + stp x25, x26, [sp, #80]
> + stp x27, x28, [sp, #96]
> +
> + adr_this_cpu x8, __efi_rt_asm_recover_sp, x9
> + str x29, [x8]
> +
> /*
> * We are lucky enough that no EFI runtime services take more than
> * 5 arguments, so all are passed in registers rather than via the
> @@ -31,7 +45,7 @@ SYM_FUNC_START(__efi_rt_asm_wrapper)
>
> ldp x1, x2, [sp, #16]
> cmp x2, x18
> - ldp x29, x30, [sp], #32
> + ldp x29, x30, [sp], #112
> b.ne 0f
> ret
> 0:
> @@ -45,3 +59,18 @@ SYM_FUNC_START(__efi_rt_asm_wrapper)
> mov x18, x2
> b efi_handle_corrupted_x18 // tail call
> SYM_FUNC_END(__efi_rt_asm_wrapper)
> +
> +SYM_FUNC_START(__efi_rt_asm_recover)
> + ldr_this_cpu x8, __efi_rt_asm_recover_sp, x9
> + mov sp, x8
> +
> + ldp x0, x18, [sp, #16]
> + ldp x19, x20, [sp, #32]
> + ldp x21, x22, [sp, #48]
> + ldp x23, x24, [sp, #64]
> + ldp x25, x26, [sp, #80]
> + ldp x27, x28, [sp, #96]
> + ldp x29, x30, [sp], #112
> +
> + b efi_handle_runtime_exception
> +SYM_FUNC_END(__efi_rt_asm_recover)
> diff --git a/arch/arm64/kernel/efi.c b/arch/arm64/kernel/efi.c
> index e1be6c429810..7c1e62a20e1e 100644
> --- a/arch/arm64/kernel/efi.c
> +++ b/arch/arm64/kernel/efi.c
> @@ -9,6 +9,7 @@
>
> #include <linux/efi.h>
> #include <linux/init.h>
> +#include <linux/percpu.h>
>
> #include <asm/efi.h>
>
> @@ -128,3 +129,28 @@ asmlinkage efi_status_t efi_handle_corrupted_x18(efi_status_t s, const char *f)
> pr_err_ratelimited(FW_BUG "register x18 corrupted by EFI %s\n", f);
> return s;
> }
> +
> +asmlinkage DEFINE_PER_CPU(u64, __efi_rt_asm_recover_sp);
> +
> +asmlinkage efi_status_t __efi_rt_asm_recover(void);
> +
> +asmlinkage efi_status_t efi_handle_runtime_exception(const char *f)
> +{
> + pr_err(FW_BUG "Fault occurred in EFI runtime service %s()\n", f);
> + clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
> + return EFI_ABORTED;
> +}
> +
> +bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg)
> +{
> + /* Check whether the exception occurred while running the firmware */
> + if (current_work() != &efi_rts_work.work || regs->pc >= TASK_SIZE_64)
> + return false;
> +
> + pr_err(FW_BUG "Unable to handle %s in EFI runtime service\n", msg);
> + add_taint(TAINT_FIRMWARE_WORKAROUND, LOCKDEP_STILL_OK);
> + dump_stack();
> +
> + regs->pc = (u64)__efi_rt_asm_recover;
> + return true;
> +}
> diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
> index 5b391490e045..3e9cf9826417 100644
> --- a/arch/arm64/mm/fault.c
> +++ b/arch/arm64/mm/fault.c
> @@ -30,6 +30,7 @@
> #include <asm/bug.h>
> #include <asm/cmpxchg.h>
> #include <asm/cpufeature.h>
> +#include <asm/efi.h>
> #include <asm/exception.h>
> #include <asm/daifflags.h>
> #include <asm/debug-monitors.h>
> @@ -391,6 +392,9 @@ static void __do_kernel_fault(unsigned long addr, unsigned long esr,
> msg = "paging request";
> }
>
> + if (efi_runtime_fixup_exception(regs, msg))
> + return;
> +
> die_kernel_fault(msg, addr, esr, regs);
> }
>
> --
> 2.35.1
>
More information about the linux-arm-kernel
mailing list