Add boot code for RPi and QEMU
This commit is contained in:
		
							parent
							
								
									be3131f666
								
							
						
					
					
						commit
						f485629fb6
					
				| 
						 | 
					@ -4,11 +4,11 @@
 | 
				
			||||||
 * Original code distributed under MIT, additional changes are under BlueOak-1.0.0
 | 
					 * Original code distributed under MIT, additional changes are under BlueOak-1.0.0
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRY(karch_start);
 | 
					ENTRY(_boot_cores);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Symbols between __boot_start and __boot_end should be dropped after init is complete.
 | 
					/* Symbols between __boot_start and __boot_end should be dropped after init is complete.
 | 
				
			||||||
   Symbols between __ro_start and __ro_end are the kernel code.
 | 
					   Symbols between __ro_start and __ro_end are the kernel code.
 | 
				
			||||||
   Symbols between __bss_start and __bss_end must be initialized to zero by r0 code in kernel.
 | 
					   Symbols between __BSS_START and __BSS_END must be initialized to zero by r0 code in kernel.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
SECTIONS
 | 
					SECTIONS
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -49,11 +49,11 @@ SECTIONS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .bss ALIGN(8):
 | 
					    .bss ALIGN(8):
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        __bss_start = .;
 | 
					        __BSS_START = .;
 | 
				
			||||||
        *(.bss .bss.*)
 | 
					        *(.bss .bss.*)
 | 
				
			||||||
        *(COMMON)
 | 
					        *(COMMON)
 | 
				
			||||||
        . = ALIGN(4096); /* Align up to 4KiB */
 | 
					        . = ALIGN(4096); /* Align up to 4KiB */
 | 
				
			||||||
        __bss_end = .;
 | 
					        __BSS_END = .;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
 | 
					    /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,204 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: BlueOak-1.0.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Based on ideas from Jorge Aparicio, Andre Richter, Phil Oppenheimer.
 | 
				
			||||||
 | 
					 * Copyright (c) 2019 Berkus Decker <berkus+vesper@metta.systems>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#![deny(missing_docs)]
 | 
				
			||||||
 | 
					#![deny(warnings)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use {
 | 
				
			||||||
 | 
					    crate::endless_sleep,
 | 
				
			||||||
 | 
					    cortex_a::{asm, regs::*},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Low-level boot of the Raspberry's processor
 | 
				
			||||||
 | 
					/// http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Type check the user-supplied entry function.
 | 
				
			||||||
 | 
					#[macro_export]
 | 
				
			||||||
 | 
					macro_rules! entry {
 | 
				
			||||||
 | 
					    ($path:path) => {
 | 
				
			||||||
 | 
					        /// # Safety
 | 
				
			||||||
 | 
					        /// Only type-checks!
 | 
				
			||||||
 | 
					        #[export_name = "main"]
 | 
				
			||||||
 | 
					        pub unsafe fn __main() -> ! {
 | 
				
			||||||
 | 
					            // type check the given path
 | 
				
			||||||
 | 
					            let f: fn() -> ! = $path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            f()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reset function.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Initializes the bss section before calling into the user's `main()`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// # Safety
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Totally unsafe! We're in the hardware land.
 | 
				
			||||||
 | 
					#[link_section = ".text.boot"]
 | 
				
			||||||
 | 
					unsafe fn reset() -> ! {
 | 
				
			||||||
 | 
					    extern "C" {
 | 
				
			||||||
 | 
					        // Boundaries of the .bss section, provided by the linker script
 | 
				
			||||||
 | 
					        static mut __BSS_START: u64;
 | 
				
			||||||
 | 
					        static mut __BSS_END: u64;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set stack pointer. Used in case we started in EL1.
 | 
				
			||||||
 | 
					    const STACK_START: u64 = 0x80_000;
 | 
				
			||||||
 | 
					    SP.set(STACK_START);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Zeroes the .bss section
 | 
				
			||||||
 | 
					    r0::zero_bss(&mut __BSS_START, &mut __BSS_END);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    extern "Rust" {
 | 
				
			||||||
 | 
					        fn main() -> !;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Real hardware boot-up sequence.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Prepare and execute transition from EL2 to EL1.
 | 
				
			||||||
 | 
					#[link_section = ".text.boot"]
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn setup_and_enter_el1_from_el2() -> ! {
 | 
				
			||||||
 | 
					    // Enable timer counter registers for EL1
 | 
				
			||||||
 | 
					    CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // No virtual offset for reading the counters
 | 
				
			||||||
 | 
					    CNTVOFF_EL2.set(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set EL1 execution state to AArch64
 | 
				
			||||||
 | 
					    // @todo Explain the SWIO bit (SWIO hardwired on Pi3)
 | 
				
			||||||
 | 
					    HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64 + HCR_EL2::SWIO::SET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set up a simulated exception return.
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // First, fake a saved program status, where all interrupts were
 | 
				
			||||||
 | 
					    // masked and SP_EL1 was used as a stack pointer.
 | 
				
			||||||
 | 
					    SPSR_EL2.write(
 | 
				
			||||||
 | 
					        SPSR_EL2::D::Masked
 | 
				
			||||||
 | 
					            + SPSR_EL2::A::Masked
 | 
				
			||||||
 | 
					            + SPSR_EL2::I::Masked
 | 
				
			||||||
 | 
					            + SPSR_EL2::F::Masked
 | 
				
			||||||
 | 
					            + SPSR_EL2::M::EL1h, // Use SP_EL1
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Second, let the link register point to reset().
 | 
				
			||||||
 | 
					    ELR_EL2.set(reset as *const () as u64);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set up SP_EL1 (stack pointer), which will be used by EL1 once
 | 
				
			||||||
 | 
					    // we "return" to it.
 | 
				
			||||||
 | 
					    const STACK_START: u64 = 0x80_000;
 | 
				
			||||||
 | 
					    SP_EL1.set(STACK_START);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Use `eret` to "return" to EL1. This will result in execution of
 | 
				
			||||||
 | 
					    // `reset()` in EL1.
 | 
				
			||||||
 | 
					    asm::eret()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// QEMU boot-up sequence.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Processors enter EL3 after reset.
 | 
				
			||||||
 | 
					/// ref: http://infocenter.arm.com/help/topic/com.arm.doc.dai0527a/DAI0527A_baremetal_boot_code_for_ARMv8_A_processors.pdf
 | 
				
			||||||
 | 
					/// section: 5.5.1
 | 
				
			||||||
 | 
					/// However, GPU init code must be switching it down to EL2.
 | 
				
			||||||
 | 
					/// QEMU can't emulate Raspberry Pi properly (no VC boot code), so it starts in EL3.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Prepare and execute transition from EL3 to EL1.
 | 
				
			||||||
 | 
					/// (from https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson02/rpi-os.md)
 | 
				
			||||||
 | 
					#[cfg(qemu)]
 | 
				
			||||||
 | 
					#[link_section = ".text.boot"]
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn setup_and_enter_el1_from_el3() -> ! {
 | 
				
			||||||
 | 
					    use crate::arch::aarch64::regs::{ELR_EL3, SCR_EL3, SPSR_EL3};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Enable timer counter registers for EL1
 | 
				
			||||||
 | 
					    CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // No virtual offset for reading the counters
 | 
				
			||||||
 | 
					    CNTVOFF_EL2.set(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set System Control Register (EL1)
 | 
				
			||||||
 | 
					    // Make memory non-cacheable and disable MMU mapping.
 | 
				
			||||||
 | 
					    SCTLR_EL1
 | 
				
			||||||
 | 
					        .write(SCTLR_EL1::I::NonCacheable + SCTLR_EL1::C::NonCacheable + SCTLR_EL1::M::Disable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set Hypervisor Configuration Register (EL2)
 | 
				
			||||||
 | 
					    // Set EL1 execution state to AArch64
 | 
				
			||||||
 | 
					    // TODO: Explain the SWIO bit (SWIO hardwired on Pi3)
 | 
				
			||||||
 | 
					    HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64 + HCR_EL2::SWIO::SET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        use crate::register::cpu::RegisterReadWrite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set Secure Configuration Register (EL3)
 | 
				
			||||||
 | 
					        SCR_EL3.write(SCR_EL3::RW::NextELIsAarch64 + SCR_EL3::NS::NonSecure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set Saved Program Status Register (EL3)
 | 
				
			||||||
 | 
					        // Set up a simulated exception return.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // First, fake a saved program status, where all interrupts were
 | 
				
			||||||
 | 
					        // masked and SP_EL1 was used as a stack pointer.
 | 
				
			||||||
 | 
					        SPSR_EL3.write(
 | 
				
			||||||
 | 
					            SPSR_EL3::D::Masked
 | 
				
			||||||
 | 
					                + SPSR_EL3::A::Masked
 | 
				
			||||||
 | 
					                + SPSR_EL3::I::Masked
 | 
				
			||||||
 | 
					                + SPSR_EL3::F::Masked
 | 
				
			||||||
 | 
					                + SPSR_EL3::M::EL1h, // Use SP_EL1
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make the Exception Link Register (EL3) point to reset().
 | 
				
			||||||
 | 
					        ELR_EL3.set(reset as *const () as u64);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set up SP_EL1 (stack pointer), which will be used by EL1 once
 | 
				
			||||||
 | 
					    // we "return" to it.
 | 
				
			||||||
 | 
					    const STACK_START: u64 = 0x80_000;
 | 
				
			||||||
 | 
					    SP_EL1.set(STACK_START);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Use `eret` to "return" to EL1. This will result in execution of
 | 
				
			||||||
 | 
					    // `reset()` in EL1.
 | 
				
			||||||
 | 
					    asm::eret()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Entrypoint of the processor.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Parks all cores except core0 and checks if we started in EL2/EL3. If
 | 
				
			||||||
 | 
					/// so, proceeds with setting up EL1.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This is invoked from the linker script, does arch-specific init
 | 
				
			||||||
 | 
					/// and passes control to the kernel boot function reset().
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Dissection of various RPi core boot stubs is available
 | 
				
			||||||
 | 
					/// [here](https://leiradel.github.io/2019/01/20/Raspberry-Pi-Stubs.html).
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					#[no_mangle]
 | 
				
			||||||
 | 
					#[link_section = ".text.boot"]
 | 
				
			||||||
 | 
					pub unsafe extern "C" fn _boot_cores() -> ! {
 | 
				
			||||||
 | 
					    const CORE_0: u64 = 0;
 | 
				
			||||||
 | 
					    const CORE_MASK: u64 = 0x3;
 | 
				
			||||||
 | 
					    // Can't match values with dots in match, so use intermediate consts.
 | 
				
			||||||
 | 
					    #[cfg(qemu)]
 | 
				
			||||||
 | 
					    const EL3: u32 = CurrentEL::EL::EL3.value;
 | 
				
			||||||
 | 
					    const EL2: u32 = CurrentEL::EL::EL2.value;
 | 
				
			||||||
 | 
					    const EL1: u32 = CurrentEL::EL::EL1.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE_0 == MPIDR_EL1.get() & CORE_MASK {
 | 
				
			||||||
 | 
					        match CurrentEL.get() {
 | 
				
			||||||
 | 
					            #[cfg(qemu)]
 | 
				
			||||||
 | 
					            EL3 => setup_and_enter_el1_from_el3(),
 | 
				
			||||||
 | 
					            EL2 => setup_and_enter_el1_from_el2(),
 | 
				
			||||||
 | 
					            EL1 => reset(),
 | 
				
			||||||
 | 
					            _ => endless_sleep(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // if not core0 or not EL3/EL2/EL1, infinitely wait for events
 | 
				
			||||||
 | 
					    endless_sleep()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,41 +1,11 @@
 | 
				
			||||||
use {
 | 
					/*
 | 
				
			||||||
    crate::kmain,
 | 
					 * SPDX-License-Identifier: BlueOak-1.0.0
 | 
				
			||||||
    cortex_a::{
 | 
					 */
 | 
				
			||||||
        asm,
 | 
					mod boot;
 | 
				
			||||||
        regs::{RegisterReadOnly, RegisterReadWrite, MPIDR_EL1, SP},
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// The entry to Rust, all things must be initialized
 | 
					 | 
				
			||||||
/// This is invoked from the linker script, does arch-specific init
 | 
					 | 
				
			||||||
/// and passes control to the kernel boot function kmain().
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// # Safety
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
/// Totally unsafe! We're in the hardware land.
 | 
					 | 
				
			||||||
///
 | 
					 | 
				
			||||||
#[no_mangle]
 | 
					 | 
				
			||||||
pub unsafe extern "C" fn karch_start() -> ! {
 | 
					 | 
				
			||||||
    // Set sp to 0x80000 (just before kernel start)
 | 
					 | 
				
			||||||
    const STACK_START: u64 = 0x8_0000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SP.set(STACK_START);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match read_cpu_id() {
 | 
					 | 
				
			||||||
        0 => kmain(),
 | 
					 | 
				
			||||||
        _ => endless_sleep(), // if not core0, indefinitely wait for events
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn read_cpu_id() -> u64 {
 | 
					 | 
				
			||||||
    const CORE_MASK: u64 = 0x3;
 | 
					 | 
				
			||||||
    MPIDR_EL1.get() & CORE_MASK
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[inline]
 | 
					#[inline]
 | 
				
			||||||
pub fn endless_sleep() -> ! {
 | 
					pub fn endless_sleep() -> ! {
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        asm::wfe();
 | 
					        cortex_a::asm::wfe();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,6 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: BlueOak-1.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
#[cfg(target_arch = "aarch64")]
 | 
					#[cfg(target_arch = "aarch64")]
 | 
				
			||||||
#[macro_use]
 | 
					#[macro_use]
 | 
				
			||||||
pub mod aarch64;
 | 
					pub mod aarch64;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,6 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: BlueOak-1.0.0
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
#![no_std]
 | 
					#![no_std]
 | 
				
			||||||
#![no_main]
 | 
					#![no_main]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +11,11 @@ use architecture_not_supported_sorry;
 | 
				
			||||||
pub mod arch;
 | 
					pub mod arch;
 | 
				
			||||||
pub use arch::*;
 | 
					pub use arch::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					entry!(kmain);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Kernel entry point
 | 
					// Kernel entry point
 | 
				
			||||||
// arch crate is responsible for calling this
 | 
					// arch crate is responsible for calling this
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
pub fn kmain() -> ! {
 | 
					pub fn kmain() -> ! {
 | 
				
			||||||
    endless_sleep()
 | 
					    endless_sleep()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue