diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3ce58f7e..b40bfba1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -312,6 +312,23 @@ jobs: - name: check esp32s3-hal (async, i2c) run: cd esp32s3-hal/ && cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async + esp-riscv-rt: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imac-unknown-none-elf, riscv32imc-unknown-none-elf + toolchain: nightly + components: rust-src + - uses: Swatinem/rust-cache@v2 + + - name: Check esp-riscv-rt (imc) + run: cd esp-riscv-rt/ && cargo check --target=riscv32imc-unknown-none-elf -Zbuild-std=core + - name: Check esp-riscv-rt (imac) + run: cd esp-riscv-rt/ && cargo check --target=riscv32imac-unknown-none-elf -Zbuild-std=core + # -------------------------------------------------------------------------- # MSRV diff --git a/CHANGELOG.md b/CHANGELOG.md index a06e88be4..58a5fcd1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix Async GPIO not disabling interupts on chips with multiple banks (#572) - Add unified field-based efuse access - Add `timer_interrupt` example in ESP32-H2 and refactor `clk_src` configuration (#576) +- Move `esp-riscv-rt` into esp-hal (#578) ### Changed diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml index 596ca8315..843d815fc 100644 --- a/esp-hal-common/Cargo.toml +++ b/esp-hal-common/Cargo.toml @@ -38,7 +38,7 @@ embassy-time = { version = "0.1.1", features = ["nightly"], optional = tru embassy-futures = { version = "0.1.0", optional = true } # RISC-V -esp-riscv-rt = { version = "0.3.0", optional = true } +esp-riscv-rt = { version = "0.3.0", path = "../esp-riscv-rt", optional = true } riscv-atomic-emulation-trap = { version = "0.4.0", optional = true } # Xtensa diff --git a/esp-riscv-rt/Cargo.toml b/esp-riscv-rt/Cargo.toml new file mode 100644 index 000000000..5960efc0b --- /dev/null +++ b/esp-riscv-rt/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "esp-riscv-rt" +version = "0.3.0" +edition = "2021" +rust-version = "1.60" +description = "Minimal runtime / startup for RISC-V CPUs from Espressif" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" +keywords = ["esp32", "riscv", "runtime", "startup"] +categories = ["embedded", "no-std"] + +[dependencies] +riscv = "0.10.1" +riscv-rt-macros = "0.2.0" + +[dev-dependencies] +panic-halt = "0.2.0" + +[features] +has-mie-mip = [] +zero-bss = [] +zero-rtc-fast-bss = [] +init-data = [] +init-rw-text = [] +init-rtc-fast-data = [] +init-rtc-fast-text = [] diff --git a/esp-riscv-rt/README.md b/esp-riscv-rt/README.md new file mode 100644 index 000000000..100fc6b36 --- /dev/null +++ b/esp-riscv-rt/README.md @@ -0,0 +1,32 @@ +# esp-riscv-rt + +[![Crates.io](https://img.shields.io/crates/v/esp-riscv-rt?color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-riscv-rt) +[![docs.rs](https://img.shields.io/docsrs/esp-riscv-rt?color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-riscv-rt) +![MSRV](https://img.shields.io/badge/MSRV-1.60-blue?style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-riscv-rt?style=flat-square) + +> Minimal runtime / startup for RISC-V CPUs from Espressif. + +Much of the code in this repository originated in the [rust-embedded/riscv-rt](https://github.com/rust-embedded/riscv-rt) repository. + +## [Documentation](https://docs.rs/crate/esp-riscv-rt) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.60 and up. It _might_ +compile with older versions but that may change in any new patch release. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-riscv-rt/build.rs b/esp-riscv-rt/build.rs new file mode 100644 index 000000000..56354ee4c --- /dev/null +++ b/esp-riscv-rt/build.rs @@ -0,0 +1,6 @@ +use std::{env, path::PathBuf}; + +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out_dir.display()); +} diff --git a/esp-riscv-rt/src/lib.rs b/esp-riscv-rt/src/lib.rs new file mode 100644 index 000000000..bfba0f0bc --- /dev/null +++ b/esp-riscv-rt/src/lib.rs @@ -0,0 +1,644 @@ +//! Minimal startup / runtime for RISC-V CPUs from Espressif +//! +//! # Minimum Supported Rust Version (MSRV) +//! +//! This crate is guaranteed to compile on stable Rust 1.60 and up. It *might* +//! compile with older versions but that may change in any new patch release. +//! +//! # Features +//! +//! This crate provides: +//! +//! - Before main initialization of the `.bss` and `.data` sections controlled +//! by features +//! - `#[entry]` to declare the entry point of the program + +// NOTE: Adapted from riscv-rt/src/lib.rs +#![no_std] + +use core::arch::global_asm; + +pub use riscv; +use riscv::register::{ + mcause, + mtvec::{self, TrapMode}, +}; +pub use riscv_rt_macros::{entry, pre_init}; + +pub use self::Interrupt as interrupt; + +#[export_name = "error: esp-riscv-rt appears more than once in the dependency graph"] +#[doc(hidden)] +pub static __ONCE__: () = (); + +extern "C" { + // Boundaries of the .bss section + static mut _bss_end: u32; + static mut _bss_start: u32; + + // Boundaries of the .data section + static mut _data_end: u32; + static mut _data_start: u32; + + // Initial values of the .data section (stored in Flash) + static _sidata: u32; +} + +/// Rust entry point (_start_rust) +/// +/// Zeros bss section, initializes data section and calls main. This function +/// never returns. +#[link_section = ".init.rust"] +#[export_name = "_start_rust"] +pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! { + extern "Rust" { + // This symbol will be provided by the user via `#[entry]` + fn main(a0: usize, a1: usize, a2: usize) -> !; + + fn __post_init(); + + fn _setup_interrupts(); + + } + + __post_init(); + + _setup_interrupts(); + + main(a0, a1, a2); +} + +/// Registers saved in trap handler +#[allow(missing_docs)] +#[derive(Debug, Default, Clone, Copy)] +#[repr(C)] +pub struct TrapFrame { + pub ra: usize, + pub t0: usize, + pub t1: usize, + pub t2: usize, + pub t3: usize, + pub t4: usize, + pub t5: usize, + pub t6: usize, + pub a0: usize, + pub a1: usize, + pub a2: usize, + pub a3: usize, + pub a4: usize, + pub a5: usize, + pub a6: usize, + pub a7: usize, + pub s0: usize, + pub s1: usize, + pub s2: usize, + pub s3: usize, + pub s4: usize, + pub s5: usize, + pub s6: usize, + pub s7: usize, + pub s8: usize, + pub s9: usize, + pub s10: usize, + pub s11: usize, + pub gp: usize, + pub tp: usize, + pub sp: usize, + pub pc: usize, + pub mstatus: usize, + pub mcause: usize, + pub mtval: usize, +} + +/// Trap entry point rust (_start_trap_rust) +/// +/// `scause`/`mcause` is read to determine the cause of the trap. XLEN-1 bit +/// indicates if it's an interrupt or an exception. The result is examined and +/// ExceptionHandler or one of the core interrupt handlers is called. +#[link_section = ".trap.rust"] +#[export_name = "_start_trap_rust"] +pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { + extern "C" { + fn ExceptionHandler(trap_frame: &TrapFrame); + fn DefaultHandler(); + } + + unsafe { + let cause = mcause::read(); + + if cause.is_exception() { + ExceptionHandler(&*trap_frame) + } else if cause.code() < __INTERRUPTS.len() { + let h = &__INTERRUPTS[cause.code()]; + if h.reserved == 0 { + DefaultHandler(); + } else { + (h.handler)(); + } + } else { + DefaultHandler(); + } + } +} + +#[doc(hidden)] +#[no_mangle] +#[allow(unused_variables, non_snake_case)] +pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! { + loop { + // Prevent this from turning into a UDF instruction + // see rust-lang/rust#28728 for details + continue; + } +} + +#[doc(hidden)] +#[no_mangle] +#[allow(unused_variables, non_snake_case)] +pub fn DefaultInterruptHandler() { + loop { + // Prevent this from turning into a UDF instruction + // see rust-lang/rust#28728 for details + continue; + } +} + +// Interrupts +#[doc(hidden)] +pub enum Interrupt { + UserSoft, + SupervisorSoft, + MachineSoft, + UserTimer, + SupervisorTimer, + MachineTimer, + UserExternal, + SupervisorExternal, + MachineExternal, +} + +extern "C" { + fn UserSoft(); + fn SupervisorSoft(); + fn MachineSoft(); + fn UserTimer(); + fn SupervisorTimer(); + fn MachineTimer(); + fn UserExternal(); + fn SupervisorExternal(); + fn MachineExternal(); +} + +#[doc(hidden)] +pub union Vector { + pub handler: unsafe extern "C" fn(), + pub reserved: usize, +} + +#[doc(hidden)] +#[no_mangle] +pub static __INTERRUPTS: [Vector; 12] = [ + Vector { handler: UserSoft }, + Vector { + handler: SupervisorSoft, + }, + Vector { reserved: 0 }, + Vector { + handler: MachineSoft, + }, + Vector { handler: UserTimer }, + Vector { + handler: SupervisorTimer, + }, + Vector { reserved: 0 }, + Vector { + handler: MachineTimer, + }, + Vector { + handler: UserExternal, + }, + Vector { + handler: SupervisorExternal, + }, + Vector { reserved: 0 }, + Vector { + handler: MachineExternal, + }, +]; + +#[doc(hidden)] +#[no_mangle] +#[rustfmt::skip] +pub unsafe extern "Rust" fn default_post_init() {} + +/// Default implementation of `_setup_interrupts` that sets `mtvec`/`stvec` to a +/// trap handler address. +#[doc(hidden)] +#[no_mangle] +#[rustfmt::skip] +pub unsafe extern "Rust" fn default_setup_interrupts() { + extern "C" { + fn _start_trap(); + } + + mtvec::write(_start_trap as usize, TrapMode::Direct); +} + +/// Parse cfg attributes inside a global_asm call. +macro_rules! cfg_global_asm { + {@inner, [$($x:tt)*], } => { + global_asm!{$($x)*} + }; + (@inner, [$($x:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => { + #[cfg($meta)] + cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*} + #[cfg(not($meta))] + cfg_global_asm!{@inner, [$($x)*], $($rest)*} + }; + {@inner, [$($x:tt)*], $asm:literal, $($rest:tt)*} => { + cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*} + }; + {$($asms:tt)*} => { + cfg_global_asm!{@inner, [], $($asms)*} + }; +} + +cfg_global_asm! { + r#" +/* + Entry point of all programs (_start). + + It initializes DWARF call frame information, the stack pointer, the + frame pointer (needed for closures to work in start_rust) and the global + pointer. Then it calls _start_rust. +*/ + +.section .init, "ax" +.global _start + +_start: + /* Jump to the absolute address defined by the linker script. */ + lui ra, %hi(_abs_start) + jr %lo(_abs_start)(ra) + +_abs_start: + .option norelax + .cfi_startproc + .cfi_undefined ra +"#, +#[cfg(feature = "has-mie-mip")] + r#" + csrw mie, 0 + csrw mip, 0 +"#, +#[cfg(feature = "zero-bss")] + r#" + la a0, _bss_start + la a1, _bss_end + mv a3, x0 + 1: + sw a3, 0(a0) + addi a0, a0, 4 + blt a0, a1, 1b +"#, +#[cfg(feature = "zero-rtc-fast-bss")] + r#" + la a0, _rtc_fast_bss_start + la a1, _rtc_fast_bss_end + mv a3, x0 + 1: + sw a3, 0(a0) + addi a0, a0, 4 + blt a0, a1, 1b +"#, +#[cfg(feature = "init-data")] + r#" + la a0, _data_start + la a1, _data_end + la a2, _sidata + 1: + lw a3, 0(a2) + sw a3, 0(a0) + addi a0, a0, 4 + addi a2, a2, 4 + blt a0, a1, 1b +"#, +#[cfg(feature = "init-rw-text")] + r#" + la a0, _srwtext + la a1, _erwtext + la a2, _irwtext + 1: + lw a3, 0(a2) + sw a3, 0(a0) + addi a0, a0, 4 + addi a2, a2, 4 + blt a0, a1, 1b +"#, +#[cfg(feature = "init-rtc-fast-data")] + r#" + la a0, _rtc_fast_data_start + la a1, _rtc_fast_data_end + la a2, _irtc_fast_data + 1: + lw a3, 0(a2) + sw a3, 0(a0) + addi a0, a0, 4 + addi a2, a2, 4 + blt a0, a1, 1b +"#, +#[cfg(feature = "init-rtc-fast-text")] + r#" + la a0, _srtc_fast_text + la a1, _ertc_fast_text + la a2, _irtc_fast_text + 1: + lw a3, 0(a2) + sw a3, 0(a0) + addi a0, a0, 4 + addi a2, a2, 4 + blt a0, a1, 1b +"#, + r#" + 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 + li x10,0 + li x11,0 + li x12,0 + 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 + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + // Check hart ID + csrr t2, mhartid + lui t0, %hi(_max_hart_id) + add t0, t0, %lo(_max_hart_id) + bgtu t2, t0, abort + + // Allocate stacks + la sp, _stack_start + lui t0, %hi(_hart_stack_size) + add t0, t0, %lo(_hart_stack_size) + + beqz t2, 2f // Jump if single-hart + mv t1, t2 + mv t3, t0 +1: + add t0, t0, t3 + addi t1, t1, -1 + bnez t1, 1b +2: + sub sp, sp, t0 + + // Set frame pointer + add s0, sp, zero + + jal zero, _start_rust + + .cfi_endproc + +/* + Trap entry points (_start_trap, _start_trapN for N in 1..=31) + + The default implementation saves all registers to the stack and calls + _start_trap_rust, then restores all saved registers before `mret` +*/ +.section .trap, "ax" +.weak _start_trap +.weak _start_trap1 +.weak _start_trap2 +.weak _start_trap3 +.weak _start_trap4 +.weak _start_trap5 +.weak _start_trap6 +.weak _start_trap7 +.weak _start_trap8 +.weak _start_trap9 +.weak _start_trap10 +.weak _start_trap11 +.weak _start_trap12 +.weak _start_trap13 +.weak _start_trap14 +.weak _start_trap15 +.weak _start_trap16 +.weak _start_trap17 +.weak _start_trap18 +.weak _start_trap19 +.weak _start_trap20 +.weak _start_trap21 +.weak _start_trap22 +.weak _start_trap23 +.weak _start_trap24 +.weak _start_trap25 +.weak _start_trap26 +.weak _start_trap27 +.weak _start_trap28 +.weak _start_trap29 +.weak _start_trap30 +.weak _start_trap31 + +_start_trap1: +_start_trap2: +_start_trap3: +_start_trap4: +_start_trap5: +_start_trap6: +_start_trap7: +_start_trap8: +_start_trap9: +_start_trap10: +_start_trap11: +_start_trap12: +_start_trap13: +_start_trap14: +_start_trap15: +_start_trap16: +_start_trap17: +_start_trap18: +_start_trap19: +_start_trap20: +_start_trap21: +_start_trap22: +_start_trap23: +_start_trap24: +_start_trap25: +_start_trap26: +_start_trap27: +_start_trap28: +_start_trap29: +_start_trap30: +_start_trap31: + +_start_trap: + addi sp, sp, -40*4 + + sw ra, 0*4(sp) + sw t0, 1*4(sp) + sw t1, 2*4(sp) + sw t2, 3*4(sp) + sw t3, 4*4(sp) + sw t4, 5*4(sp) + sw t5, 6*4(sp) + sw t6, 7*4(sp) + sw a0, 8*4(sp) + sw a1, 9*4(sp) + sw a2, 10*4(sp) + sw a3, 11*4(sp) + sw a4, 12*4(sp) + sw a5, 13*4(sp) + sw a6, 14*4(sp) + sw a7, 15*4(sp) + sw s0, 16*4(sp) + sw s1, 17*4(sp) + sw s2, 18*4(sp) + sw s3, 19*4(sp) + sw s4, 20*4(sp) + sw s5, 21*4(sp) + sw s6, 22*4(sp) + sw s7, 23*4(sp) + sw s8, 24*4(sp) + sw s9, 25*4(sp) + sw s10, 26*4(sp) + sw s11, 27*4(sp) + sw gp, 28*4(sp) + sw tp, 29*4(sp) + csrrs t1, mepc, x0 + sw t1, 31*4(sp) + csrrs t1, mstatus, x0 + sw t1, 32*4(sp) + csrrs t1, mcause, x0 + sw t1, 33*4(sp) + csrrs t1, mtval, x0 + sw t1, 34*4(sp) + + addi s0, sp, 40*4 + sw s0, 30*4(sp) + + add a0, sp, zero + jal ra, _start_trap_rust_hal + + lw t1, 31*4(sp) + csrrw x0, mepc, t1 + + lw t1, 32*4(sp) + csrrw x0, mstatus, t1 + + lw ra, 0*4(sp) + lw t0, 1*4(sp) + lw t1, 2*4(sp) + lw t2, 3*4(sp) + lw t3, 4*4(sp) + lw t4, 5*4(sp) + lw t5, 6*4(sp) + lw t6, 7*4(sp) + lw a0, 8*4(sp) + lw a1, 9*4(sp) + lw a2, 10*4(sp) + lw a3, 11*4(sp) + lw a4, 12*4(sp) + lw a5, 13*4(sp) + lw a6, 14*4(sp) + lw a7, 15*4(sp) + lw s0, 16*4(sp) + lw s1, 17*4(sp) + lw s2, 18*4(sp) + lw s3, 19*4(sp) + lw s4, 20*4(sp) + lw s5, 21*4(sp) + lw s6, 22*4(sp) + lw s7, 23*4(sp) + lw s8, 24*4(sp) + lw s9, 25*4(sp) + lw s10, 26*4(sp) + lw s11, 27*4(sp) + lw gp, 28*4(sp) + lw tp, 29*4(sp) + lw sp, 30*4(sp) + + # SP was restored from the original SP + mret + +/* Make sure there is an abort when linking */ +.section .text.abort +.globl abort +abort: + j abort + +/* + Interrupt vector table (_vector_table) +*/ + +.section .trap, "ax" +.weak _vector_table +.type _vector_table, @function + +.option push +.balign 0x100 +.option norelax +.option norvc + +_vector_table: + j _start_trap + j _start_trap1 + j _start_trap2 + j _start_trap3 + j _start_trap4 + j _start_trap5 + j _start_trap6 + j _start_trap7 + j _start_trap8 + j _start_trap9 + j _start_trap10 + j _start_trap11 + j _start_trap12 + j _start_trap13 + j _start_trap14 + j _start_trap15 + j _start_trap16 + j _start_trap17 + j _start_trap18 + j _start_trap19 + j _start_trap20 + j _start_trap21 + j _start_trap22 + j _start_trap23 + j _start_trap24 + j _start_trap25 + j _start_trap26 + j _start_trap27 + j _start_trap28 + j _start_trap29 + j _start_trap30 + j _start_trap31 + +.option pop +"#, +}