diff --git a/.github/workflows/riscv-rt.yaml b/.github/workflows/riscv-rt.yaml index 8e716b52..0c20eb31 100644 --- a/.github/workflows/riscv-rt.yaml +++ b/.github/workflows/riscv-rt.yaml @@ -1,6 +1,6 @@ on: push: - branches: [ master ] + branches: [ master, riscv-rt-asm ] pull_request: merge_group: diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 4a72f025..d482443d 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed + +- Moved all the assembly code to `asm.rs` + +### Removed + +- `start_rust` is no longer needed, as it is now written in assembly + ## [v0.12.2] - 2024-02-15 ### Added @@ -33,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Removed _start_rust. Now, assembly directly jumps to main - Removed U-mode interrupts to align with latest RISC-V specification - Changed `Vector` union. Now, it uses `Option`, which is more idiomatic in Rust - Removed riscv-target dependency for build diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index 67e4b9e3..1c33bea1 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riscv-rt" -version = "0.12.2" +version = "0.13.0" rust-version = "1.60" repository = "https://github.com/rust-embedded/riscv" authors = ["The RISC-V Team "] diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index b48cc111..14021902 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -212,7 +212,8 @@ pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream { struct AsmLoopArgs { asm_template: String, - count: usize, + count_from: usize, + count_to: usize, } impl Parse for AsmLoopArgs { @@ -220,24 +221,35 @@ impl Parse for AsmLoopArgs { let template: LitStr = input.parse().unwrap(); _ = input.parse::().unwrap(); let count: LitInt = input.parse().unwrap(); - - Ok(Self { - asm_template: template.value(), - count: count.base10_parse().unwrap(), - }) + if input.parse::().is_ok() { + let count_to: LitInt = input.parse().unwrap(); + Ok(Self { + asm_template: template.value(), + count_from: count.base10_parse().unwrap(), + count_to: count_to.base10_parse().unwrap(), + }) + } else { + Ok(Self { + asm_template: template.value(), + count_from: 0, + count_to: count.base10_parse().unwrap(), + }) + } } } /// Loops an asm expression n times. /// -/// `loop_asm!` takes 2 arguments, the first is a string literal and the second is a number literal -/// See [the formatting syntax documentation in `std::fmt`](../std/fmt/index.html) -/// for details. +/// `loop_asm!` takes 2 or 3 arguments, the first is a string literal and the rest are a number literal +/// See [the formatting syntax documentation in `std::fmt`](../std/fmt/index.html) for details. /// /// Argument 1 is an assembly expression, all "{}" in this assembly expression will be replaced with the /// current loop index. /// -/// Argument 2 is the number of loops to do with the provided expression. +/// If 2 arguments are provided, the loop will start at 0 and end at the number provided in argument 2. +/// +/// If 3 arguments are provided, the loop will start at the number provided in argument 2 and end at +/// the number provided in argument 3. /// /// # Examples /// @@ -245,13 +257,14 @@ impl Parse for AsmLoopArgs { /// # use riscv_rt_macros::loop_asm; /// unsafe { /// loop_asm!("fmv.w.x f{}, x0", 32); // => core::arch::asm!("fmv.w.x f0, x0") ... core::arch::asm!("fmv.w.x f31, x0") +/// loop_asm!("fmv.w.x f{}, x0", 1, 32); // => core::arch::asm!("fmv.w.x f1, x0") ... core::arch::asm!("fmv.w.x f31, x0") /// } /// ``` #[proc_macro] pub fn loop_asm(input: TokenStream) -> TokenStream { let args = parse_macro_input!(input as AsmLoopArgs); - let tokens = (0..args.count) + let tokens = (args.count_from..args.count_to) .map(|i| { let i = i.to_string(); let asm = args.asm_template.replace("{}", &i); @@ -261,3 +274,41 @@ pub fn loop_asm(input: TokenStream) -> TokenStream { .join("\n"); tokens.parse().unwrap() } + +/// Loops a global_asm expression n times. +/// +/// `loop_global_asm!` takes 2 or 3 arguments, the first is a string literal and the rest are a number literal +/// See [the formatting syntax documentation in `std::fmt`](../std/fmt/index.html) for details. +/// +/// Argument 1 is an assembly expression, all "{}" in this assembly expression will be replaced with the +/// current loop index. +/// +/// If 2 arguments are provided, the loop will start at 0 and end at the number provided in argument 2. +/// +/// If 3 arguments are provided, the loop will start at the number provided in argument 2 and end at +/// the number provided in argument 3. +/// +/// # Examples +/// +/// ``` +/// # use riscv_rt_macros::loop_global_asm; +/// unsafe { +/// loop_global_asm!("fmv.w.x f{}, x0", 32); // => core::arch::global_asm!("fmv.w.x f0, x0") ... core::arch::global_asm!("fmv.w.x f31, x0") +/// loop_global_asm!("fmv.w.x f{}, x0", 1, 32); // => core::arch::global_asm!("fmv.w.x f1, x0") ... core::arch::global_asm!("fmv.w.x f31, x0") +/// } +/// ``` +#[proc_macro] +pub fn loop_global_asm(input: TokenStream) -> TokenStream { + let args = parse_macro_input!(input as AsmLoopArgs); + + let instructions = (args.count_from..args.count_to) + .map(|i| { + let i = i.to_string(); + args.asm_template.replace("{}", &i) + }) + .collect::>() + .join("\n"); + + let res = format!("core::arch::global_asm!(\n\"{}\"\n);", instructions); + res.parse().unwrap() +} diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index ea4b5233..820b17f5 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -72,54 +72,34 @@ _abs_start: #[cfg(not(feature = "s-mode"))] "csrw mie, 0 csrw mip, 0", - "li x1, 0 - li x2, 0 - li x3, 0 - li x4, 0 - li x5, 0 - li x6, 0 - li x7, 0 - li x8, 0 - li x9, 0 - // a0..a2 (x10..x12) skipped - li x13, 0 - li x14, 0 - li x15, 0 - li x16, 0 - li x17, 0 - li x18, 0 - li x19, 0 - li x20, 0 - li x21, 0 - li x22, 0 - li x23, 0 - li x24, 0 - li x25, 0 - li x26, 0 - li x27, 0 - li x28, 0 - li x29, 0 - li x30, 0 - li x31, 0 +); + +// ZERO OUT GENERAL-PURPOSE REGISTERS +riscv_rt_macros::loop_global_asm!(" li x{}, 0", 1, 10); +// a0..a2 (x10..x12) skipped +riscv_rt_macros::loop_global_asm!(" li x{}, 0", 13, 32); - .option push +// INITIALIZE GLOBAL POINTER, STACK POINTER, AND FRAME POINTER +cfg_global_asm!( + ".option push .option norelax la gp, __global_pointer$ - .option pop - // Allocate stacks", - #[cfg(all(not(feature = "single-hart"), feature = "s-mode"))] + .option pop", +); +#[cfg(not(feature = "single-hart"))] +cfg_global_asm!( + #[cfg(feature = "s-mode")] "mv t2, a0 // the hartid is passed as parameter by SMODE", - #[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))] + #[cfg(not(feature = "s-mode"))] "csrr t2, mhartid", - #[cfg(not(feature = "single-hart"))] "lui t0, %hi(_max_hart_id) add t0, t0, %lo(_max_hart_id) bgtu t2, t0, abort lui t0, %hi(_hart_stack_size) add t0, t0, %lo(_hart_stack_size)", - #[cfg(all(not(feature = "single-hart"), riscvm))] + #[cfg(riscvm)] "mul t0, t2, t0", - #[cfg(all(not(feature = "single-hart"), not(riscvm)))] + #[cfg(not(riscvm))] "beqz t2, 2f // Jump if single-hart mv t1, t2 mv t3, t0 @@ -128,15 +108,115 @@ _abs_start: addi t1, t1, -1 bnez t1, 1b 2: ", +); +cfg_global_asm!( "la t1, _stack_start", #[cfg(not(feature = "single-hart"))] "sub t1, t1, t0", - "andi sp, t1, -16 // Force 16-byte alignment - // Set frame pointer - add s0, sp, zero + "andi sp, t1, -16 // align stack to 16-bytes + add s0, sp, zero", +); - jal zero, _start_rust +// STORE A0..A2 IN THE STACK, AS THEY WILL BE NEEDED LATER BY main +cfg_global_asm!( + #[cfg(riscv32)] + "addi sp, sp, -4 * 3 + sw a0, 4 * 0(sp) + sw a1, 4 * 1(sp) + sw a2, 4 * 2(sp)", + #[cfg(riscv64)] + "addi sp, sp, -8 * 3 + sd a0, 8 * 0(sp) + sd a1, 8 * 1(sp) + sd a2, 8 * 2(sp)", +); +// SKIP RAM INITIALIZATION IF CURRENT HART IS NOT THE BOOT HART +#[cfg(not(feature = "single-hart"))] +cfg_global_asm!( + #[cfg(not(feature = "s-mode"))] + "csrr a0, mhartid", + "call _mp_hook + mv t0, a0 + + beqz a0, 4f", +); +// IF CURRENT HART IS THE BOOT HART CALL __pre_init AND INITIALIZE RAM +cfg_global_asm!( + "call __pre_init + // Copy .data from flash to RAM + la t0, _sdata + la t2, _edata + la t1, _sidata + bgeu t0, t2, 2f +1: ", + #[cfg(target_arch = "riscv32")] + "lw t3, 0(t1) + addi t1, t1, 4 + sw t3, 0(t0) + addi t0, t0, 4 + bltu t0, t2, 1b", + #[cfg(target_arch = "riscv64")] + "ld t3, 0(t1) + addi t1, t1, 8 + sd t3, 0(t0) + addi t0, t0, 8 + bltu t0, t2, 1b", + " +2: // Zero out .bss + la t0, _sbss + la t2, _ebss + bgeu t0, t2, 4f +3: ", + #[cfg(target_arch = "riscv32")] + "sw zero, 0(t0) + addi t0, t0, 4 + bltu t0, t2, 3b", + #[cfg(target_arch = "riscv64")] + "sd zero, 0(t0) + addi t0, t0, 8 + bltu t0, t2, 3b", + " +4: // RAM initilized", +); + +// INITIALIZE FLOATING POINT UNIT +#[cfg(any(riscvf, riscvd))] +cfg_global_asm!( + " + li t0, 0x4000 // bit 14 is FS most significant bit + li t2, 0x2000 // bit 13 is FS least significant bit + ", + #[cfg(feature = "s-mode")] + "csrrc x0, sstatus, t0 + csrrs x0, sstatus, t2", + #[cfg(not(feature = "s-mode"))] + "csrrc x0, mstatus, t0 + csrrs x0, mstatus, t2", + "fscsr x0", +); +// ZERO OUT FLOATING POINT REGISTERS +#[cfg(all(riscv32, riscvd))] +riscv_rt_macros::loop_global_asm!(" fcvt.d.w f{}, x0", 32); +#[cfg(all(riscv64, riscvd))] +riscv_rt_macros::loop_global_asm!(" fmv.d.x f{}, x0", 32); +#[cfg(all(riscvf, not(riscvd)))] +riscv_rt_macros::loop_global_asm!(" fmv.w.x f{}, x0", 32); + +// SET UP INTERRUPTS, RESTORE a0..a2, AND JUMP TO MAIN RUST FUNCTION +cfg_global_asm!( + "call _setup_interrupts", + #[cfg(riscv32)] + "lw a0, 4 * 0(sp) + lw a1, 4 * 1(sp) + lw a2, 4 * 2(sp) + addi sp, sp, 4 * 3", + #[cfg(riscv64)] + "ld a0, 8 * 0(sp) + ld a1, 8 * 1(sp) + ld a2, 8 * 2(sp) + addi sp, sp, 8 * 3", + "jal zero, main .cfi_endproc", ); diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 164b39bc..863e7818 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -22,7 +22,8 @@ //! //! - A `_sheap` symbol at whose address you can locate a heap. //! -//! - Support for a runtime in supervisor mode, that can be bootstrapped via [Supervisor Binary Interface (SBI)](https://github.com/riscv-non-isa/riscv-sbi-doc) +//! - Support for a runtime in supervisor mode, that can be bootstrapped via +//! [Supervisor Binary Interface (SBI)](https://github.com/riscv-non-isa/riscv-sbi-doc). //! //! ``` text //! $ cargo new --bin app && cd $_ @@ -30,7 +31,7 @@ //! $ # add this crate as a dependency //! $ edit Cargo.toml && cat $_ //! [dependencies] -//! riscv-rt = "0.6.1" +//! riscv-rt = "0.13.0" //! panic-halt = "0.2.0" //! //! $ # memory layout of the device @@ -245,6 +246,8 @@ //! //! Default implementation of this function wakes hart 0 and busy-loops all the other harts. //! +//! `_mp_hook` is only necessary in multi-core targets. If the `single-hart` feature is enabled, +//! `_mp_hook` is not called, as it is assumed that there is only one hart on the target. //! //! ### Core exception handlers //! @@ -311,13 +314,10 @@ //! //! This functions are called when corresponding interrupt is occured. //! You can define an interrupt handler with one of the following names: -//! * `UserSoft` //! * `SupervisorSoft` //! * `MachineSoft` -//! * `UserTimer` //! * `SupervisorTimer` //! * `MachineTimer` -//! * `UserExternal` //! * `SupervisorExternal` //! * `MachineExternal` //! @@ -361,11 +361,12 @@ //! //! Default implementation of this function stucks in a busy-loop. //! -//! # Features +//! # Cargo Features //! //! ## `single-hart` //! //! This feature saves a little code size if there is only one hart on the target. +//! If the `single-hart` feature is enabled, `_mp_hook` is not called. //! //! ## `s-mode` //! @@ -376,9 +377,7 @@ //! [dependencies] //! riscv-rt = {features=["s-mode"]} //! ``` -//! Internally, riscv-rt uses different versions of precompiled static libraries -//! for (i) machine mode and (ii) supervisor mode. If the `s-mode` feature was activated, -//! the build script selects the s-mode library. While most registers/instructions have variants for +//! While most registers/instructions have variants for //! both `mcause` and `scause`, the `mhartid` hardware thread register is not available in supervisor //! mode. Instead, the hartid is passed as parameter by a bootstrapping firmware (i.e., SBI). //! @@ -404,23 +403,12 @@ #[cfg(riscv)] mod asm; -use core::sync::atomic::{compiler_fence, Ordering}; - #[cfg(feature = "s-mode")] use riscv::register::{scause as xcause, stvec as xtvec, stvec::TrapMode as xTrapMode}; #[cfg(not(feature = "s-mode"))] use riscv::register::{mcause as xcause, mtvec as xtvec, mtvec::TrapMode as xTrapMode}; -#[cfg(all(not(feature = "single-hart"), not(feature = "s-mode")))] -use riscv::register::mhartid; - -#[cfg(all(feature = "s-mode", any(riscvf, riscvd)))] -use riscv::register::sstatus as xstatus; - -#[cfg(all(not(feature = "s-mode"), any(riscvf, riscvd)))] -use riscv::register::mstatus as xstatus; - pub use riscv_rt_macros::{entry, pre_init}; /// We export this static with an informative name so that if an application attempts to link @@ -431,156 +419,6 @@ pub use riscv_rt_macros::{entry, pre_init}; #[doc(hidden)] pub static __ONCE__: () = (); -/// Rust entry point (_start_rust) -/// -/// Zeros bss section, initializes data section and calls main. This function never returns. -/// -/// # Safety -/// -/// This function must be called only from assembly `_start` function. -/// Do **NOT** call this function directly. -#[link_section = ".init.rust"] -#[export_name = "_start_rust"] -pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! { - #[rustfmt::skip] - extern "Rust" { - // This symbol will be provided by the user via `#[entry]` - fn main(a0: usize, a1: usize, a2: usize) -> !; - - // This symbol will be provided by the user via `#[pre_init]` - fn __pre_init(); - - fn _setup_interrupts(); - - fn _mp_hook(hartid: usize) -> bool; - } - - #[cfg(not(feature = "single-hart"))] - let run_init = { - // sbi passes hartid as first parameter (a0) - #[cfg(feature = "s-mode")] - let hartid = a0; - #[cfg(not(feature = "s-mode"))] - let hartid = mhartid::read(); - - _mp_hook(hartid) - }; - #[cfg(feature = "single-hart")] - let run_init = true; - - if run_init { - __pre_init(); - - // Initialize RAM - // 1. Copy over .data from flash to RAM - // 2. Zero out .bss - - #[cfg(target_arch = "riscv32")] - core::arch::asm!( - " - // Copy over .data - la {start},_sdata - la {end},_edata - la {input},_sidata - - bgeu {start},{end},2f - 1: - lw {a},0({input}) - addi {input},{input},4 - sw {a},0({start}) - addi {start},{start},4 - bltu {start},{end},1b - - 2: - li {a},0 - li {input},0 - - // Zero out .bss - la {start},_sbss - la {end},_ebss - - bgeu {start},{end},3f - 2: - sw zero,0({start}) - addi {start},{start},4 - bltu {start},{end},2b - - 3: - li {start},0 - li {end},0 - ", - start = out(reg) _, - end = out(reg) _, - input = out(reg) _, - a = out(reg) _, - ); - - #[cfg(target_arch = "riscv64")] - core::arch::asm!( - " - // Copy over .data - la {start},_sdata - la {end},_edata - la {input},_sidata - - bgeu {start},{end},2f - - 1: // .data Main Loop - ld {a},0({input}) - addi {input},{input},8 - sd {a},0({start}) - addi {start},{start},8 - bltu {start},{end},1b - - 2: // .data zero registers - li {a},0 - li {input},0 - - la {start},_sbss - la {end},_ebss - - bgeu {start},{end},4f - - 3: // .bss main loop - sd zero,0({start}) - addi {start},{start},8 - bltu {start},{end},3b - - 4: // .bss zero registers - // Zero out used registers - li {start},0 - li {end},0 - ", - start = out(reg) _, - end = out(reg) _, - input = out(reg) _, - a = out(reg) _, - ); - - compiler_fence(Ordering::SeqCst); - } - - #[cfg(any(riscvf, riscvd))] - { - xstatus::set_fs(xstatus::FS::Initial); // Enable fpu in xstatus - core::arch::asm!("fscsr x0"); // Zero out fcsr register csrrw x0, fcsr, x0 - - // Zero out floating point registers - #[cfg(all(target_arch = "riscv32", riscvd))] - riscv_rt_macros::loop_asm!("fcvt.d.w f{}, x0", 32); - - #[cfg(all(target_arch = "riscv64", riscvd))] - riscv_rt_macros::loop_asm!("fmv.d.x f{}, x0", 32); - - #[cfg(not(riscvd))] - riscv_rt_macros::loop_asm!("fmv.w.x f{}, x0", 32); - } - - _setup_interrupts(); - - main(a0, a1, a2); -} - /// Registers saved in trap handler #[allow(missing_docs)] #[repr(C)] @@ -637,7 +475,6 @@ pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { } else { ExceptionHandler(trap_frame); } - ExceptionHandler(trap_frame) } else if code < __INTERRUPTS.len() { let h = &__INTERRUPTS[code]; if let Some(handler) = h {