hpm_riscv_rt/
lib.rs

1//! HPMicro RISC-V Runtime
2//!
3//! This crate provides complete runtime support for HPMicro RISC-V MCUs,
4//! with HPMicro-specific customizations for PLIC vectored interrupt mode.
5//!
6//! ## Usage
7//!
8//! Add this crate as a dependency:
9//!
10//! ```toml
11//! [dependencies]
12//! hpm-riscv-rt = "0.2"
13//! ```
14//!
15//! Configure linker scripts in `.cargo/config.toml`:
16//!
17//! ```toml
18//! rustflags = [
19//!     "-C", "link-arg=-Tmemory.x",   # User-provided memory layout
20//!     "-C", "link-arg=-Tdevice.x",   # From hpm-metapac (__INTERRUPTS)
21//!     "-C", "link-arg=-Tlink.x",     # From hpm-riscv-rt
22//! ]
23//! ```
24//!
25//! Use the provided macros:
26//!
27//! ```ignore
28//! use hpm_riscv_rt::{entry, pre_init, fast};
29//!
30//! #[pre_init]
31//! unsafe fn before_main() {
32//!     // Called before RAM is initialized
33//! }
34//!
35//! #[entry]
36//! fn main() -> ! {
37//!     loop {}
38//! }
39//!
40//! #[fast]
41//! fn critical_function() {
42//!     // Runs from ILM for better performance
43//! }
44//! ```
45
46#![no_std]
47
48mod asm;
49pub mod trap;
50
51use andes_riscv::{
52    plic::{Plic, PlicExt},
53    register::mmisc_ctl,
54};
55use riscv::register::{mcounteren, mie, mstatus, mtvec::{self, Mtvec, TrapMode}};
56
57// Re-export macros
58pub use hpm_riscv_rt_macros::{entry, pre_init, fast, external_interrupt};
59
60/// HPMicro PLIC base address (same for all series)
61const PLIC_BASE: usize = 0xE400_0000;
62
63// ============ TrapFrame ============
64
65/// Registers saved during a trap.
66///
67/// This struct contains the caller-saved registers that are preserved
68/// when entering a trap handler.
69#[repr(C)]
70pub struct TrapFrame {
71    /// Return address
72    pub ra: usize,
73    /// Temporary register t0
74    pub t0: usize,
75    /// Temporary register t1
76    pub t1: usize,
77    /// Temporary register t2
78    pub t2: usize,
79    /// Temporary register t3
80    pub t3: usize,
81    /// Temporary register t4
82    pub t4: usize,
83    /// Temporary register t5
84    pub t5: usize,
85    /// Temporary register t6
86    pub t6: usize,
87    /// Argument/return register a0
88    pub a0: usize,
89    /// Argument register a1
90    pub a1: usize,
91    /// Argument register a2
92    pub a2: usize,
93    /// Argument register a3
94    pub a3: usize,
95    /// Argument register a4
96    pub a4: usize,
97    /// Argument register a5
98    pub a5: usize,
99    /// Argument register a6
100    pub a6: usize,
101    /// Argument register a7
102    pub a7: usize,
103}
104
105// ============ Rust Startup Code ============
106
107/// Rust startup function called from assembly after RAM is initialized.
108///
109/// This function:
110/// 1. Enables FPU
111/// 2. Enables L1 Cache
112/// 3. Initializes non-cacheable sections
113/// 4. Sets up interrupts (PLIC vectored mode)
114/// 5. Calls `main`
115#[no_mangle]
116pub unsafe extern "C" fn _hpm_start_rust() -> ! {
117    extern "Rust" {
118        fn main() -> !;
119    }
120
121    extern "C" {
122        fn _setup_interrupts();
123    }
124
125    // 1. Enable FPU (all HPMicro MCUs have FPU)
126    mstatus::set_fs(mstatus::FS::Initial);
127
128    // 2. Enable L1 Cache
129    andes_riscv::l1c::ic_enable();
130    andes_riscv::l1c::dc_enable();
131    andes_riscv::l1c::dc_invalidate_all();
132
133    // 3. Initialize non-cacheable sections
134    init_noncacheable_sections();
135
136    // 4. Setup interrupts (PLIC vectored mode)
137    _setup_interrupts();
138
139    // 5. Jump to main
140    main()
141}
142
143/// Initialize non-cacheable data and bss sections.
144#[inline(always)]
145unsafe fn init_noncacheable_sections() {
146    extern "C" {
147        static mut __noncacheable_data_start__: u32;
148        static mut __noncacheable_data_end__: u32;
149        static __noncacheable_data_load_addr__: u32;
150        static mut __noncacheable_bss_start__: u32;
151        static mut __noncacheable_bss_end__: u32;
152    }
153
154    // Copy .noncacheable.data
155    let mut src = core::ptr::addr_of!(__noncacheable_data_load_addr__) as *const u32;
156    let mut dst = core::ptr::addr_of_mut!(__noncacheable_data_start__);
157    let end = core::ptr::addr_of!(__noncacheable_data_end__) as *const u32;
158    while (dst as *const u32) < end {
159        dst.write_volatile(src.read_volatile());
160        src = src.add(1);
161        dst = dst.add(1);
162    }
163
164    // Zero .noncacheable.bss
165    let mut dst = core::ptr::addr_of_mut!(__noncacheable_bss_start__);
166    let end = core::ptr::addr_of!(__noncacheable_bss_end__) as *const u32;
167    while (dst as *const u32) < end {
168        dst.write_volatile(0);
169        dst = dst.add(1);
170    }
171}
172
173// ============ Interrupt Setup ============
174
175/// Setup interrupts for HPMicro MCUs.
176///
177/// This function:
178/// 1. Cleans up PLIC state
179/// 2. Enables mcycle counter
180/// 3. Configures mtvec to point to the vector table
181/// 4. Enables PLIC vectored mode via MMISC_CTL
182/// 5. Enables global interrupts
183#[export_name = "_setup_interrupts"]
184pub unsafe fn setup_interrupts() {
185    extern "C" {
186        // Vector table generated by hpm-metapac
187        // Entry 0: CORE_LOCAL (exceptions and core interrupts)
188        // Entry 1+: PLIC external interrupt handlers
189        static __INTERRUPTS: u32;
190    }
191
192    let plic = Plic::from_ptr(PLIC_BASE as *mut ());
193
194    // 1. Clean up PLIC state
195    plic.set_threshold(0);
196    for i in 0..128 {
197        plic.targetconfig(0)
198            .claim()
199            .modify(|w| w.set_interrupt_id(i as u16));
200    }
201    // Clear all interrupt enables
202    for i in 0..4 {
203        plic.targetint(0).inten(i).write(|w| w.0 = 0);
204    }
205
206    // 2. Enable mcycle counter
207    mcounteren::set_cy();
208
209    // 3. Set vector table address
210    let vector_addr = core::ptr::addr_of!(__INTERRUPTS) as usize;
211    // Note: TrapMode is ignored by hardware when MMISC_CTL.VEC_PLIC is set
212    let mtvec_val = Mtvec::new(vector_addr, TrapMode::Direct);
213    mtvec::write(mtvec_val);
214
215    // 4. Enable PLIC vectored mode (Andes-specific)
216    plic.feature().modify(|w| w.set_vectored(true));
217    mmisc_ctl().modify(|w| w.set_vec_plic(true));
218
219    // 5. Enable global interrupts
220    mstatus::set_mie();
221    mstatus::set_sie();
222    mie::set_mext();
223}
224
225// ============ Default Handlers ============
226
227/// Default exception handler - loops forever.
228#[no_mangle]
229#[allow(non_snake_case)]
230pub extern "C" fn DefaultExceptionHandler(_trap_frame: &TrapFrame) -> ! {
231    loop {
232        core::hint::spin_loop();
233    }
234}
235
236/// Default interrupt handler - loops forever.
237#[no_mangle]
238#[allow(non_snake_case)]
239pub extern "C" fn DefaultInterruptHandler() {
240    loop {
241        core::hint::spin_loop();
242    }
243}