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}