Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions riscv-rt/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
64 changes: 30 additions & 34 deletions riscv-rt/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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$
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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))]
Expand All @@ -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",

Expand Down
74 changes: 57 additions & 17 deletions riscv-rt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! # Features
//!
//! This crates takes care of:
//! This crate takes care of:
//!
//! - The memory layout of the program.
//!
Expand Down Expand Up @@ -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.
//!
Expand All @@ -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
Expand Down Expand 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`
//!
Expand Down