diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index de94d400..658b77ef 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -31,6 +31,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Now, `_start_rust` jumps to `hal_main` instead of `main` directly. At linker level, `hal_main` maps to `main` if not defined. However, we now allow HALs to inject additional configuration code before jumping to the final user's `main` function. +- Now, `a0-a2` are preserved in `s0-s2` during the startup process. In RVE targets, + `a2` is preserved in `a5`, as there are only two callee-saved registers. + New documentation of startup functions (`_mp_hook` and `__pre_init`) now provide + additional implementation guidelines to ensure a correct behavior of the runtime. + +### Removed + +- Removed usage of the stack before `_start_rust`. This was unsound, as in the `.init` + section RAM is still uninitialized. ### Fixed diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index fe2c9c07..bec41067 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -85,9 +85,18 @@ _abs_start: bgeu t0, a0, 1f la t0, abort // If hart_id > _max_hart_id, jump to abort jr t0 -1:", // only valid harts reach this point - -// INITIALIZE GLOBAL POINTER, STACK POINTER, AND FRAME POINTER +1:", // Only valid hart IDs reach this point +#[cfg(any(feature = "pre-init", not(feature = "single-hart")))] +// If startup functions are expected, preserve a0-a2 in s0-s2 +{ + "mv s0, a0 + mv s1, a1", + #[cfg(riscvi)] + "mv s2, a2", + #[cfg(not(riscvi))] + "mv a5, a2", // RVE does not include s2, so we preserve a2 in a5 +} +// INITIALIZE GLOBAL POINTER AND STACK POINTER ".option push .option norelax la gp, __global_pointer$ @@ -111,21 +120,8 @@ _abs_start: "la t1, _stack_start", #[cfg(not(feature = "single-hart"))] "sub t1, t1, t0", - "andi sp, t1, -16 // align stack to 16-bytes - add s0, sp, zero", -// STORE A0..A2 IN THE STACK, AS THEY WILL BE NEEDED LATER BY _start_rust - #[cfg(target_arch = "riscv32")] - "addi sp, sp, -4 * 4 // we must keep stack aligned to 16-bytes - sw a0, 4 * 0(sp) - sw a1, 4 * 1(sp) - sw a2, 4 * 2(sp)", - #[cfg(target_arch = "riscv64")] - "addi sp, sp, -8 * 4 // we must keep stack aligned to 16-bytes - sd a0, 8 * 0(sp) - sd a1, 8 * 1(sp) - sd a2, 8 * 2(sp)", + "andi sp, t1, -16", // align stack to 16-bytes -// CALL __pre_init (IF ENABLED) AND INITIALIZE RAM #[cfg(not(feature = "single-hart"))] // Skip RAM initialization if current hart is not the boot hart "call _mp_hook @@ -134,22 +130,22 @@ _abs_start: "call __pre_init", "// Copy .data from flash to RAM la t0, __sdata - la a0, __edata + la a3, __edata la t1, __sidata - bgeu t0, a0, 2f + bgeu t0, a3, 2f 1: ", #[cfg(target_arch = "riscv32")] "lw t2, 0(t1) addi t1, t1, 4 sw t2, 0(t0) addi t0, t0, 4 - bltu t0, a0, 1b", + bltu t0, a3, 1b", #[cfg(target_arch = "riscv64")] "ld t2, 0(t1) addi t1, t1, 8 sd t2, 0(t0) addi t0, t0, 8 - bltu t0, a0, 1b", + bltu t0, a3, 1b", " 2: // Zero out .bss la t0, __sbss @@ -165,7 +161,7 @@ _abs_start: addi t0, t0, 8 bltu t0, t2, 3b", " -4: // RAM initialized", +4:", // RAM initialized // INITIALIZE FLOATING POINT UNIT #[cfg(any(riscvf, riscvd))] @@ -183,18 +179,18 @@ _abs_start: "fscsr x0", } -// RESTORE a0..a2, AND JUMP TO _start_rust FUNCTION - #[cfg(target_arch = "riscv32")] - "lw a0, 4 * 0(sp) - lw a1, 4 * 1(sp) - lw a2, 4 * 2(sp) - addi sp, sp, 4 * 4", - #[cfg(target_arch = "riscv64")] - "ld a0, 8 * 0(sp) - ld a1, 8 * 1(sp) - ld a2, 8 * 2(sp) - addi sp, sp, 8 * 4", - "la t0, _start_rust +#[cfg(any(feature = "pre-init", not(feature = "single-hart")))] +// If startup functions are expected, restore a0-a2 from s0-s2 +{ "mv a0, s0 + mv a1, s1", + #[cfg(riscvi)] + "mv a2, s2", + #[cfg(not(riscvi))] + "mv a2, a5", // RVE does not include s2, so we use a5 to preserve a2 +} + // INITIALIZE FRAME POINTER AND JUMP TO _start_rust FUNCTION + "mv s0, sp + la t0, _start_rust jr t0 .cfi_endproc", diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 934dbd21..a65001e1 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -5,7 +5,7 @@ //! //! # Features //! -//! This crates takes care of: +//! This crate takes care of: //! //! - The memory layout of the program. //! @@ -327,21 +327,11 @@ //! Furthermore, as this function is expected to behave like a trap handler, it is //! necessary to make it be 4-byte aligned. //! -//! ## `_mp_hook` +//! ## `_mp_hook` (for multi-core targets only) //! //! This function is called from all the harts and must return true only for one hart, //! which will perform memory initialization. For other harts it must return false //! and implement wake-up in platform-dependent way (e.g., after waiting for a user interrupt). -//! The parameter `hartid` specifies the hartid of the caller. -//! -//! This function can be redefined in the following way: -//! -//! ``` no_run -//! #[export_name = "_mp_hook"] -//! pub extern "Rust" fn mp_hook(hartid: usize) -> bool { -//! // ... -//! } -//! ``` //! //! Default implementation of this function wakes hart 0 and busy-loops all the other harts. //! @@ -350,6 +340,37 @@ //! `_mp_hook` is only necessary in multi-core targets. If the `single-hart` feature is enabled, //! `_mp_hook` is not included in the binary. //! +//! ### Important implementation guidelines +//! +//! This function is called during the early boot process. Thus, when implementing it, you **MUST** follow these guidelines: +//! +//! - Implement it in assembly (no Rust code is allowed at this point). +//! - Allocate this function within the `.init` section. +//! - You can get the hart id from the `a0` register. +//! - You must set the return value in the `a0` register. +//! - Do **NOT** use callee-saved registers `s0-s2`, as they are used to preserve the initial values of `a0-a2` registers. +//! - In RVE targets, do **NOT** use the `a5` register, as it is used to preserve the `a2` register. +//! +//! **Violating these constraints will result in incorrect arguments being passed to `main()`.** +//! +//! ### Implementation example +//! +//! The following example shows how to implement the `_mp_hook` function in assembly. +//! +//! ``` no_run +//! core::arch::global_asm!( +//! r#".section .init.mp_hook, "ax" +//! .global _mp_hook +//! _mp_hook: +//! beqz a0, 2f // check if hartid is 0 +//! 1: wfi // If not, wait for interrupt in a loop +//! j 1b +//! 2: li a0, 1 // Otherwise, return true +//! ret +//! "# +//! ); +//! ``` +//! //! ## `_setup_interrupts` //! //! This function is called right before the main function and is responsible for setting up @@ -506,12 +527,31 @@ //! If the feature is enabled, the `__pre_init` function must be defined in the user code (i.e., no default implementation is //! provided by this crate). If the feature is disabled, the `__pre_init` function is not required. //! -//! As `__pre_init` runs before RAM is initialised, it is not sound to use a Rust function for `__pre_init`, and -//! instead it should typically be written in assembly using `global_asm` or an external assembly file. +//! ### Important implementation guidelines +//! +//! This function is called during the early boot process. Thus, when implementing it, you **MUST** follow these guidelines: +//! +//! - Implement it in assembly (no Rust code is allowed at this point). +//! - Allocate this function within the `.init` section. +//! - Do **NOT** use callee-saved registers `s0-s2`, as they are used to preserve the initial values of `a0-a2` registers. +//! - In RVE targets, do **NOT** use the `a5` register, as it is used to preserve the `a2` register. //! -//! Alternatively, you can use the [`#[pre_init]`][attr-pre-init] attribute to define a pre-init function with Rust. -//! Note that using this macro is discouraged, as it may lead to undefined behavior. -//! We left this option for backwards compatibility, but it is subject to removal in the future. +//! **Violating these constraints will result in incorrect arguments being passed to `main()`.** +//! +//! ### Implementation example +//! +//! The following example shows how to implement the `__pre_init` function in assembly. +//! +//! ``` no_run +//! core::arch::global_asm!( +//! r#".section .init.pre_init, "ax" +//! .global __pre_init +//! __pre_init: +//! // Do some pre-initialization work here and return +//! ret +//! "# +//! ); +//! ``` //! //! ## `single-hart` //!