Skip to content

Commit 2d2d7c6

Browse files
Merge pull request #326 from rust-embedded/rvrt-asm-alternative
`riscv-rt`: Preserve `a0-a2` in startup only when startup functions are expected
2 parents cdd76e9 + df61545 commit 2d2d7c6

File tree

3 files changed

+96
-51
lines changed

3 files changed

+96
-51
lines changed

riscv-rt/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3131
- Now, `_start_rust` jumps to `hal_main` instead of `main` directly. At linker level,
3232
`hal_main` maps to `main` if not defined. However, we now allow HALs to inject
3333
additional configuration code before jumping to the final user's `main` function.
34+
- Now, `a0-a2` are preserved in `s0-s2` during the startup process. In RVE targets,
35+
`a2` is preserved in `a5`, as there are only two callee-saved registers.
36+
New documentation of startup functions (`_mp_hook` and `__pre_init`) now provide
37+
additional implementation guidelines to ensure a correct behavior of the runtime.
38+
39+
### Removed
40+
41+
- Removed usage of the stack before `_start_rust`. This was unsound, as in the `.init`
42+
section RAM is still uninitialized.
3443

3544
### Fixed
3645

riscv-rt/src/asm.rs

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,18 @@ _abs_start:
8585
bgeu t0, a0, 1f
8686
la t0, abort // If hart_id > _max_hart_id, jump to abort
8787
jr t0
88-
1:", // only valid harts reach this point
89-
90-
// INITIALIZE GLOBAL POINTER, STACK POINTER, AND FRAME POINTER
88+
1:", // Only valid hart IDs reach this point
89+
#[cfg(any(feature = "pre-init", not(feature = "single-hart")))]
90+
// If startup functions are expected, preserve a0-a2 in s0-s2
91+
{
92+
"mv s0, a0
93+
mv s1, a1",
94+
#[cfg(riscvi)]
95+
"mv s2, a2",
96+
#[cfg(not(riscvi))]
97+
"mv a5, a2", // RVE does not include s2, so we preserve a2 in a5
98+
}
99+
// INITIALIZE GLOBAL POINTER AND STACK POINTER
91100
".option push
92101
.option norelax
93102
la gp, __global_pointer$
@@ -111,21 +120,8 @@ _abs_start:
111120
"la t1, _stack_start",
112121
#[cfg(not(feature = "single-hart"))]
113122
"sub t1, t1, t0",
114-
"andi sp, t1, -16 // align stack to 16-bytes
115-
add s0, sp, zero",
116-
// STORE A0..A2 IN THE STACK, AS THEY WILL BE NEEDED LATER BY _start_rust
117-
#[cfg(target_arch = "riscv32")]
118-
"addi sp, sp, -4 * 4 // we must keep stack aligned to 16-bytes
119-
sw a0, 4 * 0(sp)
120-
sw a1, 4 * 1(sp)
121-
sw a2, 4 * 2(sp)",
122-
#[cfg(target_arch = "riscv64")]
123-
"addi sp, sp, -8 * 4 // we must keep stack aligned to 16-bytes
124-
sd a0, 8 * 0(sp)
125-
sd a1, 8 * 1(sp)
126-
sd a2, 8 * 2(sp)",
123+
"andi sp, t1, -16", // align stack to 16-bytes
127124

128-
// CALL __pre_init (IF ENABLED) AND INITIALIZE RAM
129125
#[cfg(not(feature = "single-hart"))]
130126
// Skip RAM initialization if current hart is not the boot hart
131127
"call _mp_hook
@@ -134,22 +130,22 @@ _abs_start:
134130
"call __pre_init",
135131
"// Copy .data from flash to RAM
136132
la t0, __sdata
137-
la a0, __edata
133+
la a3, __edata
138134
la t1, __sidata
139-
bgeu t0, a0, 2f
135+
bgeu t0, a3, 2f
140136
1: ",
141137
#[cfg(target_arch = "riscv32")]
142138
"lw t2, 0(t1)
143139
addi t1, t1, 4
144140
sw t2, 0(t0)
145141
addi t0, t0, 4
146-
bltu t0, a0, 1b",
142+
bltu t0, a3, 1b",
147143
#[cfg(target_arch = "riscv64")]
148144
"ld t2, 0(t1)
149145
addi t1, t1, 8
150146
sd t2, 0(t0)
151147
addi t0, t0, 8
152-
bltu t0, a0, 1b",
148+
bltu t0, a3, 1b",
153149
"
154150
2: // Zero out .bss
155151
la t0, __sbss
@@ -165,7 +161,7 @@ _abs_start:
165161
addi t0, t0, 8
166162
bltu t0, t2, 3b",
167163
"
168-
4: // RAM initialized",
164+
4:", // RAM initialized
169165

170166
// INITIALIZE FLOATING POINT UNIT
171167
#[cfg(any(riscvf, riscvd))]
@@ -183,18 +179,18 @@ _abs_start:
183179
"fscsr x0",
184180
}
185181

186-
// RESTORE a0..a2, AND JUMP TO _start_rust FUNCTION
187-
#[cfg(target_arch = "riscv32")]
188-
"lw a0, 4 * 0(sp)
189-
lw a1, 4 * 1(sp)
190-
lw a2, 4 * 2(sp)
191-
addi sp, sp, 4 * 4",
192-
#[cfg(target_arch = "riscv64")]
193-
"ld a0, 8 * 0(sp)
194-
ld a1, 8 * 1(sp)
195-
ld a2, 8 * 2(sp)
196-
addi sp, sp, 8 * 4",
197-
"la t0, _start_rust
182+
#[cfg(any(feature = "pre-init", not(feature = "single-hart")))]
183+
// If startup functions are expected, restore a0-a2 from s0-s2
184+
{ "mv a0, s0
185+
mv a1, s1",
186+
#[cfg(riscvi)]
187+
"mv a2, s2",
188+
#[cfg(not(riscvi))]
189+
"mv a2, a5", // RVE does not include s2, so we use a5 to preserve a2
190+
}
191+
// INITIALIZE FRAME POINTER AND JUMP TO _start_rust FUNCTION
192+
"mv s0, sp
193+
la t0, _start_rust
198194
jr t0
199195
.cfi_endproc",
200196

riscv-rt/src/lib.rs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//!
66
//! # Features
77
//!
8-
//! This crates takes care of:
8+
//! This crate takes care of:
99
//!
1010
//! - The memory layout of the program.
1111
//!
@@ -327,21 +327,11 @@
327327
//! Furthermore, as this function is expected to behave like a trap handler, it is
328328
//! necessary to make it be 4-byte aligned.
329329
//!
330-
//! ## `_mp_hook`
330+
//! ## `_mp_hook` (for multi-core targets only)
331331
//!
332332
//! This function is called from all the harts and must return true only for one hart,
333333
//! which will perform memory initialization. For other harts it must return false
334334
//! and implement wake-up in platform-dependent way (e.g., after waiting for a user interrupt).
335-
//! The parameter `hartid` specifies the hartid of the caller.
336-
//!
337-
//! This function can be redefined in the following way:
338-
//!
339-
//! ``` no_run
340-
//! #[export_name = "_mp_hook"]
341-
//! pub extern "Rust" fn mp_hook(hartid: usize) -> bool {
342-
//! // ...
343-
//! }
344-
//! ```
345335
//!
346336
//! Default implementation of this function wakes hart 0 and busy-loops all the other harts.
347337
//!
@@ -350,6 +340,37 @@
350340
//! `_mp_hook` is only necessary in multi-core targets. If the `single-hart` feature is enabled,
351341
//! `_mp_hook` is not included in the binary.
352342
//!
343+
//! ### Important implementation guidelines
344+
//!
345+
//! This function is called during the early boot process. Thus, when implementing it, you **MUST** follow these guidelines:
346+
//!
347+
//! - Implement it in assembly (no Rust code is allowed at this point).
348+
//! - Allocate this function within the `.init` section.
349+
//! - You can get the hart id from the `a0` register.
350+
//! - You must set the return value in the `a0` register.
351+
//! - Do **NOT** use callee-saved registers `s0-s2`, as they are used to preserve the initial values of `a0-a2` registers.
352+
//! - In RVE targets, do **NOT** use the `a5` register, as it is used to preserve the `a2` register.
353+
//!
354+
//! **Violating these constraints will result in incorrect arguments being passed to `main()`.**
355+
//!
356+
//! ### Implementation example
357+
//!
358+
//! The following example shows how to implement the `_mp_hook` function in assembly.
359+
//!
360+
//! ``` no_run
361+
//! core::arch::global_asm!(
362+
//! r#".section .init.mp_hook, "ax"
363+
//! .global _mp_hook
364+
//! _mp_hook:
365+
//! beqz a0, 2f // check if hartid is 0
366+
//! 1: wfi // If not, wait for interrupt in a loop
367+
//! j 1b
368+
//! 2: li a0, 1 // Otherwise, return true
369+
//! ret
370+
//! "#
371+
//! );
372+
//! ```
373+
//!
353374
//! ## `_setup_interrupts`
354375
//!
355376
//! This function is called right before the main function and is responsible for setting up
@@ -506,12 +527,31 @@
506527
//! If the feature is enabled, the `__pre_init` function must be defined in the user code (i.e., no default implementation is
507528
//! provided by this crate). If the feature is disabled, the `__pre_init` function is not required.
508529
//!
509-
//! As `__pre_init` runs before RAM is initialised, it is not sound to use a Rust function for `__pre_init`, and
510-
//! instead it should typically be written in assembly using `global_asm` or an external assembly file.
530+
//! ### Important implementation guidelines
531+
//!
532+
//! This function is called during the early boot process. Thus, when implementing it, you **MUST** follow these guidelines:
533+
//!
534+
//! - Implement it in assembly (no Rust code is allowed at this point).
535+
//! - Allocate this function within the `.init` section.
536+
//! - Do **NOT** use callee-saved registers `s0-s2`, as they are used to preserve the initial values of `a0-a2` registers.
537+
//! - In RVE targets, do **NOT** use the `a5` register, as it is used to preserve the `a2` register.
511538
//!
512-
//! Alternatively, you can use the [`#[pre_init]`][attr-pre-init] attribute to define a pre-init function with Rust.
513-
//! Note that using this macro is discouraged, as it may lead to undefined behavior.
514-
//! We left this option for backwards compatibility, but it is subject to removal in the future.
539+
//! **Violating these constraints will result in incorrect arguments being passed to `main()`.**
540+
//!
541+
//! ### Implementation example
542+
//!
543+
//! The following example shows how to implement the `__pre_init` function in assembly.
544+
//!
545+
//! ``` no_run
546+
//! core::arch::global_asm!(
547+
//! r#".section .init.pre_init, "ax"
548+
//! .global __pre_init
549+
//! __pre_init:
550+
//! // Do some pre-initialization work here and return
551+
//! ret
552+
//! "#
553+
//! );
554+
//! ```
515555
//!
516556
//! ## `single-hart`
517557
//!

0 commit comments

Comments
 (0)