arm64: entry: call exit_to_user_mode() from C
authorMark Rutland <mark.rutland@arm.com>
Mon, 2 Aug 2021 14:07:33 +0000 (15:07 +0100)
committerCatalin Marinas <catalin.marinas@arm.com>
Thu, 5 Aug 2021 13:10:32 +0000 (14:10 +0100)
When handling an exception from EL0, we perform the entry work in that
exception's C handler, and once the C handler has finished, we return
back to the entry assembly. Subsequently in the common `ret_to_user`
assembly we perform the exit work that balances with the entry work.
This can be somewhat difficult to follow, and makes it hard to rework
the return paths (e.g. to pass additional context to the exit code, or
to have exception return logic for specific exceptions).

This patch reworks the entry code such that each EL0 C exception handler
is responsible for both the entry and exit work. This clearly balances
the two (and will permit additional variation in future), and avoids an
unnecessary bounce between assembly and C in the common case, leaving
`ret_from_fork` as the only place assembly has to call the exit code.
This means that the exit work is now inlined into the C handler, which
is already the case for the entry work, and allows the compiler to
generate better code (e.g. by immediately returning when there is no
exit work to perform).

To align with other exception entry/exit helpers, enter_from_user_mode()
is updated to take the EL0 pt_regs as a parameter, though this is
currently unused.

There should be no functional change as a result of this patch. However,
this should lead to slightly better backtraces when an error is
encountered within do_notify_resume(), as the C handler should appear in
the backtrace, indicating the specific exception that the kernel was
entered with.

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Joey Gouly <joey.gouly@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Will Deacon <will@kernel.org>
Reviewed-by: Joey Gouly <joey.gouly@arm.com>
Link: https://lore.kernel.org/r/20210802140733.52716-5-mark.rutland@arm.com
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
arch/arm64/kernel/entry-common.c
arch/arm64/kernel/entry.S

index 3da4859..32f9796 100644 (file)
@@ -104,7 +104,7 @@ static __always_inline void __enter_from_user_mode(void)
        trace_hardirqs_off_finish();
 }
 
-static __always_inline void enter_from_user_mode(void)
+static __always_inline void enter_from_user_mode(struct pt_regs *regs)
 {
        __enter_from_user_mode();
 }
@@ -116,19 +116,12 @@ static __always_inline void enter_from_user_mode(void)
  */
 static __always_inline void __exit_to_user_mode(void)
 {
-
        trace_hardirqs_on_prepare();
        lockdep_hardirqs_on_prepare(CALLER_ADDR0);
        user_enter_irqoff();
        lockdep_hardirqs_on(CALLER_ADDR0);
 }
 
-static __always_inline void exit_to_user_mode(void)
-{
-       mte_check_tfsr_exit();
-       __exit_to_user_mode();
-}
-
 static __always_inline void prepare_exit_to_user_mode(struct pt_regs *regs)
 {
        unsigned long flags;
@@ -140,10 +133,16 @@ static __always_inline void prepare_exit_to_user_mode(struct pt_regs *regs)
                do_notify_resume(regs, flags);
 }
 
-asmlinkage void noinstr asm_exit_to_user_mode(struct pt_regs *regs)
+static __always_inline void exit_to_user_mode(struct pt_regs *regs)
 {
        prepare_exit_to_user_mode(regs);
-       exit_to_user_mode();
+       mte_check_tfsr_exit();
+       __exit_to_user_mode();
+}
+
+asmlinkage void noinstr asm_exit_to_user_mode(struct pt_regs *regs)
+{
+       exit_to_user_mode(regs);
 }
 
 /*
@@ -477,9 +476,10 @@ static void noinstr el0_da(struct pt_regs *regs, unsigned long esr)
 {
        unsigned long far = read_sysreg(far_el1);
 
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_mem_abort(far, esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_ia(struct pt_regs *regs, unsigned long esr)
@@ -494,37 +494,42 @@ static void noinstr el0_ia(struct pt_regs *regs, unsigned long esr)
        if (!is_ttbr0_addr(far))
                arm64_apply_bp_hardening();
 
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_mem_abort(far, esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_fpsimd_acc(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_fpsimd_acc(esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_sve_acc(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_sve_acc(esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_fpsimd_exc(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_fpsimd_exc(esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_sys(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_sysinstr(esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_pc(struct pt_regs *regs, unsigned long esr)
@@ -534,37 +539,42 @@ static void noinstr el0_pc(struct pt_regs *regs, unsigned long esr)
        if (!is_ttbr0_addr(instruction_pointer(regs)))
                arm64_apply_bp_hardening();
 
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_sp_pc_abort(far, esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_sp(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_sp_pc_abort(regs->sp, esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_undef(struct pt_regs *regs)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_undefinstr(regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_bti(struct pt_regs *regs)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_bti(regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_inv(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        bad_el0_sync(regs, 0, esr);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
@@ -572,23 +582,26 @@ static void noinstr el0_dbg(struct pt_regs *regs, unsigned long esr)
        /* Only watchpoints write FAR_EL1, otherwise its UNKNOWN */
        unsigned long far = read_sysreg(far_el1);
 
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        do_debug_exception(far, esr, regs);
        local_daif_restore(DAIF_PROCCTX);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_svc(struct pt_regs *regs)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        cortex_a76_erratum_1463225_svc_handler();
        do_el0_svc(regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_fpac(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_ptrauth_fault(regs, esr);
+       exit_to_user_mode(regs);
 }
 
 asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
@@ -647,7 +660,7 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
 static void noinstr el0_interrupt(struct pt_regs *regs,
                                  void (*handler)(struct pt_regs *))
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
 
        write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
 
@@ -655,6 +668,8 @@ static void noinstr el0_interrupt(struct pt_regs *regs,
                arm64_apply_bp_hardening();
 
        do_interrupt_handler(regs, handler);
+
+       exit_to_user_mode(regs);
 }
 
 static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
@@ -681,12 +696,13 @@ static void noinstr __el0_error_handler_common(struct pt_regs *regs)
 {
        unsigned long esr = read_sysreg(esr_el1);
 
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_ERRCTX);
        arm64_enter_nmi(regs);
        do_serror(regs, esr);
        arm64_exit_nmi(regs);
        local_daif_restore(DAIF_PROCCTX);
+       exit_to_user_mode(regs);
 }
 
 asmlinkage void noinstr el0t_64_error_handler(struct pt_regs *regs)
@@ -697,16 +713,18 @@ asmlinkage void noinstr el0t_64_error_handler(struct pt_regs *regs)
 #ifdef CONFIG_COMPAT
 static void noinstr el0_cp15(struct pt_regs *regs, unsigned long esr)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        local_daif_restore(DAIF_PROCCTX);
        do_cp15instr(esr, regs);
+       exit_to_user_mode(regs);
 }
 
 static void noinstr el0_svc_compat(struct pt_regs *regs)
 {
-       enter_from_user_mode();
+       enter_from_user_mode(regs);
        cortex_a76_erratum_1463225_svc_handler();
        do_el0_svc_compat(regs);
+       exit_to_user_mode(regs);
 }
 
 asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
index b59eee2..1b45065 100644 (file)
@@ -564,8 +564,6 @@ SYM_CODE_START_LOCAL(ret_to_kernel)
 SYM_CODE_END(ret_to_kernel)
 
 SYM_CODE_START_LOCAL(ret_to_user)
-       mov     x0, sp
-       bl      asm_exit_to_user_mode
        /* Ignore asynchronous tag check faults in the uaccess routines */
        clear_mte_async_tcf
        ldr     x19, [tsk, #TSK_TI_FLAGS]       // re-check for single-step
@@ -739,6 +737,8 @@ SYM_CODE_START(ret_from_fork)
        mov     x0, x20
        blr     x19
 1:     get_current_task tsk
+       mov     x0, sp
+       bl      asm_exit_to_user_mode
        b       ret_to_user
 SYM_CODE_END(ret_from_fork)
 NOKPROBE(ret_from_fork)