mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-23 12:51:13 +00:00
254 lines
7.1 KiB
ArmAsm
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
|