ruspiro-boot 0.5.4

Bare metal boot strapper code for the Raspberry Pi 3 to conviniently start a custom kernel within the Rust environment without the need to deal with all the initial setup like stack pointers, switch to the appropriate exeption level and getting all cores kicked off for processing of code compiled from Rust.
/***********************************************************************************************************************
 * Raspberry Pi bootstrap code.
 * This is the minimal preparation to brach into the "Rust" code line for further initialization
 * and setup for the current kernel to be run at the Raspberry Pi.
 * 
 * This is the Aarch64 version of the bootstrapping. It assumes:
 * 1. There is actually only the main core entering this code
 * 2. The bootcode.bin/start.elf have parked the other cores of the CPU
 * 3. The current core is entering this code in EL2
 * 4. The start address of the entry point is 0x8_0000 which has to be ensured by the linker script
 *
 *
 ***********************************************************************************************************************
 * Copyright (c) 2020 by the authors
 *
 * Author: André Borrmann <pspwizard@gmx.de>
 * License: Apache License 2.0 / MIT
 **********************************************************************************************************************/

.global __boot // global entry point
.global __hang // helper to savely "hang" a core with nothing else to do

/***************************************************************************************************
 * main entry point using specific section that is ensured to be linked against the entrypoint
 * address 0x8_0000
 **************************************************************************************************/
.section .text.boot
__boot:
    // the very first thing to do is to setup the stack pointer.
    mrs 	x0, mpidr_el1   // get core id to calculate core distinct stack pointers
    and 	x0, x0, #3      
    
    ldr		x1,=__stack_top_core0__
	ldr		x2,=__stack_top_core1__
	subs    x1, x1, x2	// offset = core0 - core1
	mul 	x2, x1, x0  // core specific offset for the stack

    ldr		x1,=__stack_top_EL2__
    sub     sp, x1, x2

    // once done we clear the BSS section which contains any static field defined
    // in the Rust code line. This need to be properly initialized as it is expected
    // to be 0 when first accessed
    // as we might want to kickof other cores at a later point to also run the initial
    // bootstrap we check for the current core. As all cores share the same memory the
    // bss section need to and shall be cleared only once...
	cbnz	x0, .bss_done	    // only continue with bss clear on core 0

	ldr		x0, =__bss_start__  // linker file ensures alignment to 16Bit's for start and end
	ldr		x2, =__bss_end__ 
    sub     x2, x2, x0
    lsr     x2, x2, #4
    cbz     x2, .bss_done       // if bss section size is 0 -> skip initialization
.bss_zero_loop:
    
	stp     xzr, xzr, [x0], #16
    sub     x2, x2, #1
    cbnz    x2, .bss_zero_loop

.bss_done:
    // next step will switch from EL2 to EL1 which will be the one the kernel will be executed at
    bl      __switch_el2_to_el1
    // next we setup the exception vector table that will act as a trampoline for
    // all exceptions into the handler written in Rust code
    adr     x0, __ExceptionVectorTable
    msr     vbar_el1, x0    // set exception vector table adress in EL1

    // as rust compiler optimizations quite likely result in FP/NEON instructions
    // ensure they are not trapped
    mrs    x1, cpacr_el1
    mov    x0, #(3 << 20)
    orr    x0, x1, x0
    msr    cpacr_el1, x0

    // now call rust code entry point.
    mrs     x0, mpidr_el1       // read CoreId from register
	and     x0, x0, #3          // mask coreId value
    b   __rust_entry

    // usually this will never return. However to be an the save side, when ever we got back
    // safely hang this core
    b   __hang

/***************************************************************************************************
 * switch the current exception level EL2 to EL1. The EL1 return address is
 * the return to the caller
 **************************************************************************************************/
.global __switch_el2_to_el1
__switch_el2_to_el1:
    mrs     x0, currentEl   // get the current exception level
    cmp     x0, #(1 << 2)          // if already in EL1 no switch necessary
    beq     .SwitchReturn

    msr     sctlr_el1, xzr  // initialize SCTRL_EL1 register before switching to EL1 	
     // enable AArch64 when switching to EL1 (otherwise EL1 would be executed in aarch32)
    mov     x0, #(1 << 31)      // AArch64
    orr     x0, x0, #(1 << 1)   // SWIO hardwired on Pi3
    msr     hcr_el2, x0
    mrs     x0, hcr_el2

	mrs     x2, cnthctl_el2 // enable CNTP for EL1
    orr     x2, x2, #3
    msr     cnthctl_el2, x2
    msr     cntvoff_el2, xzr

    // set the SPSR_EL2 to a valid value before returning to EL1
    // this would have been usually set when capturing an exception from EL1 to EL2
    // as we would like to return we set the values as we would like to find them
    // configured once we are in EL1
    mov     x2, #(0b0101 << 0 | /* M[3:0] exception taken from El1h  */ \
                  0 << 4 | /* exception taken from aarch64 */ \
                  1 << 6 | /* mask FIQ */ \
                  1 << 7 | /* Mask IRQ */ \
                  1 << 8 | /* Mask Abort */ \
                  1 << 9)  /* Mask Debug */
    //mov     x2, #0x3c4 //#0b00101    // set DAIF to 0 and M[4] to 0 (exception from aarch64, M[3:0] to 0101 -> Exception from EL1h)
    msr     spsr_el2, x2
    
    // before returning to EL1 also ensure that interrupts are no longer routet to EL2
    mrs     x0, hcr_el2
    bic     x0, x0, #(1 << 3 | 1 << 4 | 1 << 5) // don't route Abort, IRQ and FIQ to EL2
    msr     hcr_el2, x0

    // we cannot directly return to the caller as the EL1 stackpointer
    // is not yet setup
    adr     x1, .SwitchReturn
    msr     elr_el2, x1
    eret    // return from EL2 -> EL1
.SwitchReturn:
    ldr     x1, =__stack_top_EL1__ // get the EL1 stack base address
    // use the core id to get the core specific stack pointer
    mrs 	x0, mpidr_el1   // get CPU id
    and 	x0, x0, #3

    ldr		x2,=__stack_top_core0__
	ldr		x3,=__stack_top_core1__
	subs    x2, x2, x3	// offset = core0 - core1
	mul 	x2, x2, x0  // core specific offset for the stack
    sub     x0, x1, x2  // from the top base substract the core offset to get final stack top
    mov     sp, x0
    ret

/***************************************************************************************************
 * safely hang the core
 * use the WFE instruction to save power while waiting for any event
 * wfe is triggered by any exception/interrupt raised, but as long as there is no event
 * the core sleeps....
 **************************************************************************************************/
 .section .text
 __hang:
    wfe
    b   __hang