Atmosphere/thermosphere/src/start.s
2018-04-20 04:10:44 -06:00

254 lines
7.1 KiB
ArmAsm

/**
* Thermosphère hypervisor entry points
* This file contains all entry points (e.g. when launching or switching ELs).
*
* Copyright (c) Kate J. Temkin <k@ktemkin.com>
*/
.section ".text"
.global _start
/**
* Simple macro to help with generating vector table entries.
*/
.macro ventry label
.align 7
b \label
.endm
/**
* Start of day code. This is the first code that executes after we're launched
* by the bootloader. We use this only to set up a C environment.
*/
_start:
// Create a simple stack for the hypervisor, while executing in EL2.
ldr x1, =el2_stack_end
mov sp, x1
// Clear out our binary's bss.
stp x0, x1, [sp, #-16]!
bl _clear_bss
ldp x0, x1, [sp], #16
// Run the main routine. This shouldn't return.
b main
// We shouldn't ever reach here; trap.
1: b 1b
/*
* Vector table for interrupts/exceptions that reach EL2.
*/
.align 11
.global el2_vector_table;
el2_vector_table:
ventry _unhandled_vector // Synchronous EL2t
ventry _unhandled_vector // IRQ EL2t
ventry _unhandled_vector // FIQ EL2t
ventry _unhandled_vector // Error EL2t
ventry _unhandled_vector // Synchronous EL2h
ventry _unhandled_vector // IRQ EL2h
ventry _unhandled_vector // FIQ EL2h
ventry _unhandled_vector // Error EL2h
ventry _handle_hypercall // Synchronous 64-bit EL0/EL1
ventry _unhandled_vector // IRQ 64-bit EL0/EL1
ventry _unhandled_vector // FIQ 64-bit EL0/EL1
ventry _unhandled_vector // Error 64-bit EL0/EL1
ventry _unhandled_vector // Synchronous 32-bit EL0/EL1
ventry _unhandled_vector // IRQ 32-bit EL0/EL1
ventry _unhandled_vector // FIQ 32-bit EL0/EL1
ventry _unhandled_vector // Error 32-bit EL0/EL1
/*
* Switch down to EL1 and then execute the second half of our stub.
* Implemented in assembly, as this manipulates the stack.
*
* Obliterates the stack, but leaves the rest of memory intact. This should be
* fine, as we should be hiding the EL2 memory from the rest of the system.
*
* x0: The location of the device tree to be passed into EL0.
*/
.global switch_to_el1
switch_to_el1:
// Set up a post-EL1-switch return address...
ldr x2, =_post_el1_switch
msr elr_el2, x2
// .. and set up the CPSR after we switch to EL1.
// We overwrite the saved program status register. Note that setting
// this with the EL = EL1 is what actually causes the switch.
mov x2, #0x3c5 // EL1_SP1 | D | A | I | F
msr spsr_el2, x2
// Reset the stack pointer to the very end of the stack, so it's
// fresh and clean for when we jump back up into EL2.
ldr x2, =el2_stack_end
mov sp, x2
// ... and switch down to EL1. (This essentially asks the processor
// to switch down to EL1 and then load ELR_EL2 to the PC.)
eret
/*
* Entry point after the switch to EL1.
*/
.global _post_el1_switch
_post_el1_switch:
// Create a simple stack for us to use while at EL1.
// We use this temporarily to launch Horizon.
ldr x2, =el1_stack_end
mov sp, x2
// Run the main routine. This shouldn't return.
b main_el1
// We shouldn't ever reach here; trap.
1: b 1b
/**
* Push and pop 'psuedo-op' macros that simplify the ARM syntax to make the below pretty.
*/
.macro push, xreg1, xreg2
stp \xreg1, \xreg2, [sp, #-16]!
.endm
.macro pop, xreg1, xreg2
ldp \xreg1, \xreg2, [sp], #16
.endm
/**
* Macro that saves registers onto the stack when entering an exception handler--
* effectively saving the guest state. Once this method is complete, *sp will
* point to a struct guest_state.
*
* You can modify this to save whatever you'd like, but:
* 1) We can only push in pairs due to armv8 architecture quirks.
* 2) Be careful not to trounce registers until after you've saved them.
* 3) r31 is your stack pointer, and doesn't need to be saved. You'll want to
* save the lesser EL's stack pointers separately.
* 4) Make sure any changes you make are reflected both in _restore_registers_
* and in struct guest_state, or things will break pretty badly.
*/
.macro save_registers
// General purpose registers x1 - x30
push x29, x30
push x27, x28
push x25, x26
push x23, x24
push x21, x22
push x19, x20
push x17, x18
push x15, x16
push x13, x14
push x11, x12
push x9, x10
push x7, x8
push x5, x6
push x3, x4
push x1, x2
// x0 and the el2_esr
mrs x20, esr_el2
push x20, x0
// the el1_sp and el0_sp
mrs x0, sp_el0
mrs x1, sp_el1
push x0, x1
// the el1 elr/spsr
mrs x0, elr_el1
mrs x1, spsr_el1
push x0, x1
// the el2 elr/spsr
mrs x0, elr_el2
mrs x1, spsr_el2
push x0, x1
.endm
/**
* Macro that restores registers when returning from EL2.
* Mirrors save_registers.
*/
.macro restore_registers
// the el2 elr/spsr
pop x0, x1
msr elr_el2, x0
msr spsr_el2, x1
// the el1 elr/spsr
pop x0, x1
msr elr_el1, x0
msr spsr_el1, x1
// the el1_sp and el0_sp
pop x0, x1
msr sp_el0, x0
msr sp_el1, x1
// x0, and the el2_esr
// Note that we don't restore el2_esr, as this wouldn't
// have any meaning.
pop x20, x0
// General purpose registers x1 - x30
pop x1, x2
pop x3, x4
pop x5, x6
pop x7, x8
pop x9, x10
pop x11, x12
pop x13, x14
pop x15, x16
pop x17, x18
pop x19, x20
pop x21, x22
pop x23, x24
pop x25, x26
pop x27, x28
pop x29, x30
.endm
/*
* Handler for any vector we're not equipped to handle.
*/
_unhandled_vector:
// TODO: Save interrupt state and turn off interrupts.
save_registers
// Point x0 at our saved registers, and then call our C handler.
mov x0, sp
bl unhandled_vector
restore_registers
eret
/*
* Handler for any synchronous event coming from the guest (any trap-to-EL2).
* This _stub_ only uses this to handle hypercalls-- hence the name.
*/
_handle_hypercall:
// TODO: Save interrupt state and turn off interrupts.
save_registers
// Point x0 at our saved registers, and then call our C handler.
mov x0, sp
bl handle_hypercall
restore_registers
eret