blob: 83b03745e06521468069b9cfb163b929ebe037a3 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2014 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <arch/arch_thread.h>
#include <arch/regs.h>
#include <arch/arm64.h>
#include <arch/arm64/asm.h>
#include <arch/asm_macros.h>
#include <lib/syscalls/arm64.h>
#include <lib/syscalls/zx-syscall-numbers.h>
#include <zircon/compiler.h>
.text
#define DW_REG_lr 30
#define DW_REG_sp 31
// The "current mode exception link register", which for our purposes is elr_el1.
#define DW_REG_ELR_mode 33
#define lr x30
#define elr1 DW_REG_ELR_mode
// Save the full register state on the stack.
// NOTE: Must stay in sync with the iframe_t definition in arch/regs.h
.macro regsave
// The first push decrements the SP as a side effect. In the case of a stack
// overflow will result in an immediate fault, instead of 'walking the stack'
// downwards in an exception loop if the sp were decremented first.
stp x0, x1, [sp, -ARM64_IFRAME_SIZE]!
.cfi_adjust_cfa_offset -ARM64_IFRAME_SIZE
stp x2, x3, [sp, ARM64_IFRAME_OFFSET_R + (2 * 8)]
stp x4, x5, [sp, ARM64_IFRAME_OFFSET_R + (4 * 8)]
stp x6, x7, [sp, ARM64_IFRAME_OFFSET_R + (6 * 8)]
stp x8, x9, [sp, ARM64_IFRAME_OFFSET_R + (8 * 8)]
stp x10, x11, [sp, ARM64_IFRAME_OFFSET_R + (10 * 8)]
stp x12, x13, [sp, ARM64_IFRAME_OFFSET_R + (12 * 8)]
stp x14, x15, [sp, ARM64_IFRAME_OFFSET_R + (14 * 8)]
stp x16, x17, [sp, ARM64_IFRAME_OFFSET_R + (16 * 8)]
stp x18, x19, [sp, ARM64_IFRAME_OFFSET_R + (18 * 8)]
stp x20, x21, [sp, ARM64_IFRAME_OFFSET_R + (20 * 8)]
stp x22, x23, [sp, ARM64_IFRAME_OFFSET_R + (22 * 8)]
stp x24, x25, [sp, ARM64_IFRAME_OFFSET_R + (24 * 8)]
stp x26, x27, [sp, ARM64_IFRAME_OFFSET_R + (26 * 8)]
stp x28, x29, [sp, ARM64_IFRAME_OFFSET_R + (28 * 8)]
// Stay away from x0-x7, since they may be holding syscall arguments.
mrs x9, sp_el0
// x10 (containing elr_el1) is used in syscall handler
mrs x10, elr_el1
mrs x11, spsr_el1
mrs x12, mdscr_el1
stp lr, x9, [sp, ARM64_IFRAME_OFFSET_LR]
.cfi_rel_offset lr, ARM64_IFRAME_OFFSET_LR
.cfi_rel_offset sp, ARM64_IFRAME_OFFSET_USP
stp x10, x11, [sp, ARM64_IFRAME_OFFSET_ELR]
.cfi_rel_offset elr1, ARM64_IFRAME_OFFSET_ELR
stp x12, xzr,[sp, ARM64_IFRAME_OFFSET_MDSCR]
.endm
// Once we pop the stack past the saved sp_el0, elr_el1 the userspace values
// are inaccessible.
.macro mark_lr_sp_inaccessible
// TODO(dje): gdb tries to use some value for these even if "undefined",
// as a workaround set their values to zero which will cause gdb to
// terminate the backtrace. Need to revisit, file gdb bug if necessary.
cfi_register_is_zero DW_REG_sp
cfi_register_is_zero DW_REG_ELR_mode
.endm
// Reverse regsave above, popping the full register state off of the stack.
.macro regrestore, from_lower_el_64
ldp lr, x9, [sp, ARM64_IFRAME_OFFSET_LR]
ldp x10, x11, [sp, ARM64_IFRAME_OFFSET_ELR]
ldr x12, [sp, ARM64_IFRAME_OFFSET_MDSCR]
msr sp_el0, x9
msr elr_el1, x10
msr spsr_el1, x11
msr mdscr_el1, x12
ldp x0, x1, [sp, ARM64_IFRAME_OFFSET_R]
ldp x2, x3, [sp, ARM64_IFRAME_OFFSET_R + (2 * 8)]
ldp x4, x5, [sp, ARM64_IFRAME_OFFSET_R + (4 * 8)]
ldp x6, x7, [sp, ARM64_IFRAME_OFFSET_R + (6 * 8)]
ldp x8, x9, [sp, ARM64_IFRAME_OFFSET_R + (8 * 8)]
ldp x10, x11, [sp, ARM64_IFRAME_OFFSET_R + (10 * 8)]
ldp x12, x13, [sp, ARM64_IFRAME_OFFSET_R + (12 * 8)]
ldp x14, x15, [sp, ARM64_IFRAME_OFFSET_R + (14 * 8)]
ldp x16, x17, [sp, ARM64_IFRAME_OFFSET_R + (16 * 8)]
ldp x18, x19, [sp, ARM64_IFRAME_OFFSET_R + (18 * 8)]
.if \from_lower_el_64
ldp x20, x21, [sp, ARM64_IFRAME_OFFSET_R + (20 * 8)]
.else
// If restoring from EL1 -> EL1, leave x20 alone since it's holding the current
// per cpu pointer. It may have changed since we originally took the exception
// if we had been rescheduled to another cpu.
ldp xzr, x21, [sp, ARM64_IFRAME_OFFSET_R + (20 * 8)]
.endif
ldp x22, x23, [sp, ARM64_IFRAME_OFFSET_R + (22 * 8)]
ldp x24, x25, [sp, ARM64_IFRAME_OFFSET_R + (24 * 8)]
ldp x26, x27, [sp, ARM64_IFRAME_OFFSET_R + (26 * 8)]
ldp x28, x29, [sp, ARM64_IFRAME_OFFSET_R + (28 * 8)]
.cfi_same_value lr
add_to_sp ARM64_IFRAME_SIZE
mark_lr_sp_inaccessible
.endm
.macro start_isr_cfi
.cfi_startproc simple
.cfi_signal_frame
// The return address is in elr_el1, not lr.
.cfi_return_column elr1
.cfi_def_cfa sp, 0
.endm
.macro start_isr_func_cfi
start_isr_cfi
ALL_CFI_SAME_VALUE
.cfi_undefined elr1
.endm
.macro start_helper_cfi
.cfi_startproc simple
.cfi_signal_frame
.cfi_def_cfa sp, ARM64_IFRAME_SIZE
.endm
// The CFA offset of integer register |regno| (regno = 0-29).
#define REG_CFA_OFFSET(regno) .cfi_offset x##regno, -((4 * 8) + ((30 - (regno)) * 8))
// Mark the locations of the registers based on the CFA so that the
// location doesn't change as the regs are popped.
.macro setup_helper_cfi
REG_CFA_OFFSET(0)
REG_CFA_OFFSET(1)
REG_CFA_OFFSET(2)
REG_CFA_OFFSET(3)
REG_CFA_OFFSET(4)
REG_CFA_OFFSET(5)
REG_CFA_OFFSET(6)
REG_CFA_OFFSET(7)
REG_CFA_OFFSET(8)
REG_CFA_OFFSET(9)
REG_CFA_OFFSET(10)
REG_CFA_OFFSET(11)
REG_CFA_OFFSET(12)
REG_CFA_OFFSET(13)
REG_CFA_OFFSET(14)
REG_CFA_OFFSET(15)
REG_CFA_OFFSET(16)
REG_CFA_OFFSET(17)
REG_CFA_OFFSET(18)
REG_CFA_OFFSET(19)
REG_CFA_OFFSET(20)
REG_CFA_OFFSET(21)
REG_CFA_OFFSET(22)
REG_CFA_OFFSET(23)
REG_CFA_OFFSET(24)
REG_CFA_OFFSET(25)
REG_CFA_OFFSET(26)
REG_CFA_OFFSET(27)
REG_CFA_OFFSET(28)
REG_CFA_OFFSET(29)
.cfi_offset lr, ARM64_IFRAME_OFFSET_LR
.cfi_offset sp, ARM64_IFRAME_OFFSET_USP
.endm
.macro start_helper
start_helper_cfi
setup_helper_cfi
.endm
// All normal C code in the kernel expects the invariants that the fixed
// registers assigned to the percpu_ptr and the shadow-call-stack pointer have
// the correct values for the current CPU and kernel thread. When an exception
// happens in the kernel, only percpu_ptr needs to be reloaded. (In fact, it
// would be disastrous to reload the shadow-call-stack pointer because the
// correct value to reflect the interrupted thread's kernel call stack exists
// only in the register!) But when an exception happens in a lower EL (i.e. user
// mode), these registers must be reloaded from the struct arch_thread
// accessible via TPIDR_EL1 before reaching any C functions.
.macro restore_fixed_regs temp
mrs \temp, tpidr_el1
#if __has_feature(shadow_call_stack)
# if CURRENT_SCSP_OFFSET != CURRENT_PERCPU_PTR_OFFSET + 8
# error "shadow_call_sp must follow current_percpu_ptr in struct arch_thread"
# endif
ldp percpu_ptr, shadow_call_sp, [\temp, #CURRENT_PERCPU_PTR_OFFSET]
#else
ldr percpu_ptr, [\temp, #CURRENT_PERCPU_PTR_OFFSET]
#endif
.endm
// Lighter version of restore_fixed_regs for EL1-to-EL1 exceptions. Strictly
// speaking not required, but in case of a firmware call via SMC or HVC accidentally
// trashing the register, preserve this fixed register by reloading it from the
// current thread pointer.
.macro restore_percpu_ptr temp
mrs \temp, tpidr_el1
ldr percpu_ptr, [\temp, #CURRENT_PERCPU_PTR_OFFSET]
.endm
// The shadow-call-stack pointer (x18) is saved/restored in struct arch_thread
// on context switch. On entry from a lower EL (i.e. user mode), it gets
// reloaded from there via the restore_fixed_regs macro above. So when
// returning to user mode, we must make sure to write back the current value
// (which should always be the base, since returning to user should be the
// base of the call stack) so that the next kernel entry reloads that instead
// of whatever was current last time this thread context-switched out.
.macro save_shadow_call_sp temp
#if __has_feature(shadow_call_stack)
mrs \temp, tpidr_el1
str shadow_call_sp, [\temp, #CURRENT_SCSP_OFFSET]
#endif
.endm
// Unhandled exception or irq. Save the full state and pass the which value through to
// the inner routine.
.macro invalid_exception, which
start_isr_func_cfi
regsave
restore_fixed_regs x9
mov x0, sp
mov x1, #\which
bl arm64_invalid_exception
b .
.endm
.macro irq_exception, from_lower_el_64
start_isr_func_cfi
regsave
.if \from_lower_el_64
restore_fixed_regs x9
mov x1, #ARM64_EXCEPTION_FLAG_LOWER_EL
.else
restore_percpu_ptr x9
mov x1, #0
.endif
mov x0, sp
bl arm64_irq
.if \from_lower_el_64
b arm64_exc_shared_restore_lower_el
.else
b arm64_exc_shared_restore
.endif
.endm
// TODO: find the appropriate place to reenable FIQs here when they're needed.
.macro sync_exception, from_lower_el_64
start_isr_func_cfi
regsave
mrs x9, esr_el1
.if \from_lower_el_64
restore_fixed_regs x11
// If this is a syscall, x0-x7 contain args and x16 contains syscall num.
// x10 contains elr_el1.
lsr x11, x9, #26 // shift esr right 26 bits to get ec
cmp x11, #0x15 // check for 64-bit syscall
beq arm64_syscall_dispatcher // and jump to syscall handler
.else
restore_percpu_ptr x11
.endif
// Prepare the default sync_exception args
mov w2, w9
.if \from_lower_el_64
// We've just run out of space to fit in the 0x80 bytes of the sync exception vector.
// branch to another block of code later in the file that will finish getting ready
// and call arm64_sync_exception.
b arm64_sync_exception_lower_el
.else
mov x0, sp
mov x1, #0
bl arm64_sync_exception
b arm64_exc_shared_restore
.endif
.endm
.macro serror_exception, from_lower_el_64
start_isr_func_cfi
regsave
.if \from_lower_el_64
restore_fixed_regs x9
mov x1, #ARM64_EXCEPTION_FLAG_LOWER_EL
.else
restore_percpu_ptr x9
mov x1, #0
.endif
mov x0, sp
bl arm64_serror_exception
.if \from_lower_el_64
b arm64_exc_shared_restore_lower_el
.else
b arm64_exc_shared_restore
.endif
.endm
// main vector table, aligned on 2048 byte boundary
.align 11
FUNCTION_LABEL(arm64_el1_exception_base)
// exceptions from current EL, using SP0
// the first 4 are invalid because SP0 is not used in EL1
.org 0x000
LOCAL_FUNCTION_LABEL(arm64_el1_sync_exc_current_el_SP0)
invalid_exception 0
END_FUNCTION(arm64_el1_sync_exc_current_el_SP0)
.org 0x080
LOCAL_FUNCTION_LABEL(arm64_el1_irq_current_el_SP0)
invalid_exception 1
END_FUNCTION(arm64_el1_irq_current_el_SP0)
.org 0x100
LOCAL_FUNCTION_LABEL(arm64_el1_fiq_current_el_SP0)
invalid_exception 2
END_FUNCTION(arm64_el1_fiq_current_el_SP0)
.org 0x180
LOCAL_FUNCTION_LABEL(arm64_el1_err_exc_current_el_SP0)
invalid_exception 3
END_FUNCTION(arm64_el1_err_exc_current_el_SP0)
// exceptions from current EL, using SPx
.org 0x200
LOCAL_FUNCTION_LABEL(arm64_el1_sync_exc_current_el_SPx)
sync_exception 0 // same EL, arm64
END_FUNCTION(arm64_el1_sync_exc_current_el_SPx)
.org 0x280
LOCAL_FUNCTION_LABEL(arm64_el1_irq_current_el_SPx)
irq_exception 0 // same EL, arm64
END_FUNCTION(arm64_el1_irq_current_el_SPx)
.org 0x300
LOCAL_FUNCTION_LABEL(arm64_el1_fiq_current_el_SPx)
invalid_exception 0x12
END_FUNCTION(arm64_el1_fiq_current_el_SPx)
.org 0x380
LOCAL_FUNCTION_LABEL(arm64_el1_err_exc_current_el_SPx)
serror_exception 0 // same EL, arm64
END_FUNCTION(arm64_el1_err_exc_current_el_SPx)
// exceptions from lower EL, running arm64
.org 0x400
LOCAL_FUNCTION_LABEL(arm64_el1_sync_exc_lower_el_64)
sync_exception 1 // lower EL, arm64
END_FUNCTION(arm64_el1_sync_exc_lower_el_64)
.org 0x480
LOCAL_FUNCTION_LABEL(arm64_el1_irq_lower_el_64)
irq_exception 1 // lower EL, arm64
END_FUNCTION(arm64_el1_irq_lower_el_64)
.org 0x500
LOCAL_FUNCTION_LABEL(arm64_el1_fiq_lower_el_64)
invalid_exception 0x22
END_FUNCTION(arm64_el1_fiq_lower_el_64)
.org 0x580
LOCAL_FUNCTION_LABEL(arm64_el1_err_exc_lower_el_64)
serror_exception 1 // lower EL, arm64
END_FUNCTION(arm64_el1_err_exc_lower_el_64)
// exceptions from lower EL, running arm32
// all of these are invalid since there is no arm32 user space
.org 0x600
LOCAL_FUNCTION_LABEL(arm64_el1_sync_exc_lower_el_32)
invalid_exception 0x30
END_FUNCTION(arm64_el1_sync_exc_lower_el_32)
.org 0x680
LOCAL_FUNCTION_LABEL(arm64_el1_irq_lower_el_32)
invalid_exception 0x31
END_FUNCTION(arm64_el1_irq_lower_el_32)
.org 0x700
LOCAL_FUNCTION_LABEL(arm64_el1_fiq_lower_el_32)
invalid_exception 0x32
END_FUNCTION(arm64_el1_fiq_lower_el_32)
.org 0x780
LOCAL_FUNCTION_LABEL(arm64_el1_err_exc_lower_el_32)
invalid_exception 0x33
END_FUNCTION(arm64_el1_err_exc_lower_el_32)
LOCAL_FUNCTION_LABEL(arm64_sync_exception_lower_el)
start_isr_func_cfi
mov x0, sp
mov x1, #ARM64_EXCEPTION_FLAG_LOWER_EL
bl arm64_sync_exception
b arm64_exc_shared_restore_lower_el
END_FUNCTION(arm64_sync_exception_lower_el)
// The first entry point is used when returning to user mode,
// the second when returning to kernel.
LOCAL_FUNCTION_LABEL(arm64_exc_shared_restore_lower_el)
start_helper
save_shadow_call_sp x9
regrestore 1 // lower EL
eret
// Prevent speculation through ERET
SPECULATION_POSTFENCE
END_FUNCTION(arm64_exc_shared_restore_lower_el)
LOCAL_FUNCTION_LABEL(arm64_exc_shared_restore)
start_helper
regrestore 0 // same EL
eret
// Prevent speculation through ERET
SPECULATION_POSTFENCE
END_FUNCTION(arm64_exc_shared_restore)
//
// Syscall args are in x0-x7 already.
// pc is in x10 and needs to go in the next available register,
// or the stack if the regs are full.
//
.macro syscall_dispatcher nargs, syscall
.balign 16
.if \nargs == 8
push_regs x10, xzr // push twice to maintain alignment
bl wrapper_\syscall
add_to_sp 16
.else
mov x\nargs, x10
bl wrapper_\syscall
.endif
b .Lpost_syscall
.endm
//
// Expected state prior to arm64_syscall_dispatcher branch:
//
// percpu and shadow call stack registers have been restored
//
// x0-x7 - contains syscall arguments
// x9 - contains esr_el1
// x10 - contains elr_el1
// x16 - contains syscall_num
// sp - points to base of iframe
//
// Expected state prior to branching to syscall_dispatcher macro:
//
// x0-x7 - contains syscall arguments
// x10 - contains userspace pc
//
LOCAL_FUNCTION_LABEL(arm64_syscall_dispatcher)
start_isr_func_cfi
// Verify syscall number and call the unknown handler if bad.
cmp x16, #ZX_SYS_COUNT
bhs .Lunknown_syscall
// Spectre V1: If syscall number >= ZX_SYS_COUNT, replace it with zero. The branch/test above
// means this can only occur in wrong-path speculative executions.
csel x16, xzr, x16, hs
csdb
// Jump to the right syscall wrapper. The syscall table is an
// array of 16 byte aligned routines for each syscall. Each routine
// marshalls some arguments, bls to the routine, and then branches
// back to .Lpost_syscall (see syscall_dispatcher macro above).
adr x12, .Lsyscall_table
add x12, x12, x16, lsl #4
br x12
// Prevent speculation through BR
SPECULATION_POSTFENCE
.Lunknown_syscall:
mov x0, x16 // move the syscall number into the 0 arg slot
mov x1, x10 // pc into arg 1
bl unknown_syscall
// fall through
.Lpost_syscall:
// Upon return from syscall, x0 = status, x1 = thread signalled
// Move the status to frame->r[0] for return to userspace.
str x0, [sp, ARM64_IFRAME_OFFSET_R]
// Spectre: ARM64 CPUs may speculatively execute instructions after an SVC instruction.
// The userspace entry code has a speculation barrier; advance ELR_EL1 past in on the return
// since it has already done its job.
.ifne ARM64_SYSCALL_SPECULATION_BARRIER_SIZE - 12
.error "Syscall speculation barrier must be 12 bytes"
.endif
ldr x10, [sp, ARM64_IFRAME_OFFSET_ELR]
add x10, x10, ARM64_SYSCALL_SPECULATION_BARRIER_SIZE
str x10, [sp, ARM64_IFRAME_OFFSET_ELR]
// Check for pending signals. If none, just return.
cbz x1, arm64_exc_shared_restore_lower_el
mov x0, sp
bl arch_iframe_process_pending_signals
b arm64_exc_shared_restore_lower_el
// Emit a small trampoline to branch to the wrapper routine for the syscall.
.macro syscall_dispatch nargs, syscall
syscall_dispatcher \nargs, \syscall
.endm
// Adds the label for the jump table.
.macro start_syscall_dispatch
.balign 16
.Lsyscall_table:
.endm
// One of these macros is invoked by kernel.inc for each syscall.
// These don't have kernel entry points.
#define VDSO_SYSCALL(...)
// These are the direct kernel entry points.
#define KERNEL_SYSCALL(name, type, attrs, nargs, arglist, prototype) \
syscall_dispatch nargs, name
#define INTERNAL_SYSCALL(...) KERNEL_SYSCALL(__VA_ARGS__)
#define BLOCKING_SYSCALL(...) KERNEL_SYSCALL(__VA_ARGS__)
start_syscall_dispatch
#include <lib/syscalls/kernel.inc>
#undef VDSO_SYSCALL
#undef KERNEL_SYSCALL
#undef INTERNAL_SYSCALL
#undef BLOCKING_SYSCALL
END_FUNCTION(arm64_syscall_dispatcher)