picorv32_rt/lib.rs
1//! Minimal startup / runtime for PicoRV32 RISC-V CPU
2//!
3//! # Minimum Supported Rust Version (MSRV)
4//!
5//! This crate is guaranteed to compile on stable Rust 1.32 and up. It *might*
6//! compile with older versions but that may change in any new patch release.
7//!
8//! # Features
9//!
10//! This crate provides
11//!
12//! - Before main initialization of the `.bss` and `.data` sections.
13//!
14//! - `#[entry]` to declare the entry point of the program
15//! - `#[pre_init]` to run code *before* `static` variables are initialized
16//!
17//! - A linker script that encodes the memory layout of a PicoRV32 RISC-V
18//! microcontroller. This linker script is missing some information that must
19//! be supplied through a `memory.x` file (see example below).
20//!
21//! - A `_sheap` symbol at whose address you can locate a heap.
22//!
23//! ``` text
24//! $ cargo new --bin app && cd $_
25//!
26//! $ # add this crate as a dependency
27//! $ edit Cargo.toml && cat $_
28//! [dependencies]
29//! picorv32-rt = "0.4.0"
30//! panic-halt = "0.2.0"
31//!
32//! $ # memory layout of the device
33//! $ edit memory.x && cat $_
34//! MEMORY
35//! {
36//! /* NOTE K = KiBi = 1024 bytes */
37//! FLASH : ORIGIN = 0x00100000, LENGTH = 0x400000
38//! RAM : ORIGIN = 0x00000000, LENGTH = 0x3800
39//! }
40//!
41//! $ edit src/main.rs && cat $_
42//! ```
43//!
44//! ``` ignore,no_run
45//! #![no_std]
46//! #![no_main]
47//!
48//! extern crate panic_halt;
49//!
50//! use picorv32::entry;
51//!
52//! // use `main` as the entry point of this application
53//! // `main` is not allowed to return
54//! #[entry]
55//! fn main() -> ! {
56//! // do something here
57//! loop { }
58//! }
59//! ```
60//!
61//! ``` text
62//! $ mkdir .cargo && edit .cargo/config && cat $_
63//! [target.riscv32imc-unknown-none-elf]
64//! rustflags = [
65//! "-C", "link-arg=-Tlink.x"
66//! ]
67//!
68//! [build]
69//! target = "riscv32imc-unknown-none-elf"
70//! $ edit build.rs && cat $_
71//! ```
72//!
73//! ``` ignore,no_run
74//! use std::env;
75//! use std::fs::File;
76//! use std::io::Write;
77//! use std::path::Path;
78//!
79//! /// Put the linker script somewhere the linker can find it.
80//! fn main() {
81//! let out_dir = env::var("OUT_DIR").expect("No out dir");
82//! let dest_path = Path::new(&out_dir);
83//! let mut f = File::create(&dest_path.join("memory.x"))
84//! .expect("Could not create file");
85//!
86//! f.write_all(include_bytes!("memory.x"))
87//! .expect("Could not write file");
88//!
89//! println!("cargo:rustc-link-search={}", dest_path.display());
90//!
91//! println!("cargo:rerun-if-changed=memory.x");
92//! println!("cargo:rerun-if-changed=build.rs");
93//! }
94//! ```
95//!
96//! ``` text
97//! $ cargo build
98//!
99//! $ riscv32-unknown-elf-objdump -Cd $(find target -name app) | head
100//!
101//! Disassembly of section .text:
102//!
103//! 20000000 <_start>:
104//! 20000000: 800011b7 lui gp,0x80001
105//! 20000004: 80018193 addi gp,gp,-2048 # 80000800 <_stack_start+0xffffc800>
106//! 20000008: 80004137 lui sp,0x80004
107//! ```
108//!
109//! # Symbol interfaces
110//!
111//! This crate makes heavy use of symbols, linker sections and linker scripts to
112//! provide most of its functionality. Below are described the main symbol
113//! interfaces.
114//!
115//! ## `memory.x`
116//!
117//! This file supplies the information about the device to the linker.
118//!
119//! ### `MEMORY`
120//!
121//! The main information that this file must provide is the memory layout of
122//! the device in the form of the `MEMORY` command. The command is documented
123//! [here][2], but at a minimum you'll want to create two memory regions: one
124//! for Flash memory and another for RAM.
125//!
126//! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html
127//!
128//! The program instructions (the `.text` section) will be stored in the memory
129//! region named FLASH, and the program `static` variables (the sections `.bss`
130//! and `.data`) will be allocated in the memory region named RAM.
131//!
132//! ### `_stack_start`
133//!
134//! This symbol provides the address at which the call stack will be allocated.
135//! The call stack grows downwards so this address is usually set to the highest
136//! valid RAM address plus one (this *is* an invalid address but the processor
137//! will decrement the stack pointer *before* using its value as an address).
138//!
139//! If omitted this symbol value will default to `ORIGIN(RAM) + LENGTH(RAM)`.
140//!
141//! #### Example
142//!
143//! Allocating the call stack on a different RAM region.
144//!
145//! ```
146//! MEMORY
147//! {
148//! /* call stack will go here */
149//! CCRAM : ORIGIN = 0x10000000, LENGTH = 8K
150//! FLASH : ORIGIN = 0x08000000, LENGTH = 256K
151//! /* static variables will go here */
152//! RAM : ORIGIN = 0x20000000, LENGTH = 40K
153//! }
154//!
155//! _stack_start = ORIGIN(CCRAM) + LENGTH(CCRAM);
156//! ```
157//!
158//! ### `_heap_size`
159//!
160//! This symbol provides the size of a heap region. The default value is 0. You can set `_heap_size`
161//! to a non-zero value if you are planning to use heap allocations.
162//!
163//! ### `_sheap`
164//!
165//! This symbol is located in RAM right after the `.bss` and `.data` sections.
166//! You can use the address of this symbol as the start address of a heap
167//! region. This symbol is 4 byte aligned so that address will be a multiple of 4.
168//!
169//! #### Example
170//!
171//! ```
172//! extern crate some_allocator;
173//!
174//! extern "C" {
175//! static _sheap: u8;
176//! static _heap_size: u8;
177//! }
178//!
179//! fn main() {
180//! unsafe {
181//! let heap_bottom = &_sheap as *const u8 as usize;
182//! let heap_size = &_heap_size as *const u8 as usize;
183//! some_allocator::initialize(heap_bottom, heap_size);
184//! }
185//! }
186//! ```
187//!
188//! ## `pre_init!`
189//!
190//! A user-defined function can be run at the start of the reset handler, before RAM is
191//! initialized. The macro `pre_init!` can be called to set the function to be run. The function is
192//! intended to perform actions that cannot wait the time it takes for RAM to be initialized, such
193//! as disabling a watchdog. As the function is called before RAM is initialized, any access of
194//! static variables will result in undefined behavior.
195
196// NOTE: Adapted from cortex-m/src/lib.rs
197#![no_std]
198#![deny(missing_docs)]
199
200extern crate picorv32_rt_macros as macros;
201extern crate r0;
202extern crate riscv;
203
204use core::fmt;
205use core::ptr::NonNull;
206pub use macros::{entry, pre_init};
207use picorv32::asm;
208
209extern "C" {
210 // Boundaries of the .bss section
211 static mut _ebss: u32;
212 static mut _sbss: u32;
213
214 // Boundaries of the .data section
215 static mut _edata: u32;
216 static mut _sdata: u32;
217
218 // Initial values of the .data section (stored in Flash)
219 static _sidata: u32;
220
221 // Address of _start_trap
222 #[cfg(feature = "interrupts")]
223 static _start_trap: u32;
224}
225
226/// Rust entry point (_start_rust)
227///
228/// Zeros bss section, initializes data section and calls main. This function
229/// never returns.
230#[link_section = ".init.rust"]
231#[export_name = "_start_rust"]
232pub unsafe extern "C" fn start_rust() -> ! {
233 extern "Rust" {
234 // This symbol will be provided by the user via `#[entry]`
235 fn main() -> !;
236
237 // This symbol will be provided by the user via `#[pre_init]`
238 fn __pre_init();
239 }
240
241 __pre_init();
242
243 r0::zero_bss(&mut _sbss, &mut _ebss);
244 r0::init_data(&mut _sdata, &mut _edata, &_sidata);
245
246 #[cfg(feature = "interrupts")]
247 picorv32::interrupt::enable();
248
249 main();
250}
251
252/// A block of registers saved for the duration of handling an interrupt
253#[repr(C)]
254#[derive(Copy, Clone)]
255pub struct PicoRV32StoredRegisters {
256 x3: u32,
257 #[cfg(not(feature = "interrupts-qregs"))]
258 x1: u32,
259 #[cfg(not(feature = "interrupts-qregs"))]
260 x2: u32,
261 x5: u32,
262 x6: u32,
263 x7: u32,
264 x10: u32,
265 x11: u32,
266 x12: u32,
267 x13: u32,
268 x14: u32,
269 x15: u32,
270 x16: u32,
271 x17: u32,
272 x28: u32,
273 x29: u32,
274 x30: u32,
275 x31: u32,
276}
277
278impl PicoRV32StoredRegisters {
279 /// `x1`/`ra` (return address, saved by caller)
280 #[inline]
281 #[cfg(feature = "interrupts-qregs")]
282 pub fn x1(&self) -> u32 {
283 unsafe { picorv32::asm::getq2() }
284 }
285
286 /// `x1`/`ra` (return address, saved by caller)
287 #[inline]
288 #[cfg(not(feature = "interrupts-qregs"))]
289 pub fn x1(&self) -> u32 {
290 self.x1
291 }
292
293 /// `x2`/`sp` (stack pointer, saved by callee)
294 #[inline]
295 #[cfg(feature = "interrupts-qregs")]
296 pub fn x2(&self) -> u32 {
297 unsafe { picorv32::asm::getq3() }
298 }
299
300 /// `x2`/`sp` (stack pointer, saved by callee)
301 #[inline]
302 #[cfg(not(feature = "interrupts-qregs"))]
303 pub fn x2(&self) -> u32 {
304 self.x2
305 }
306
307 /// `x3`/`gp` (global pointer)
308 #[inline]
309 pub fn x3(&self) -> u32 {
310 self.x3
311 }
312
313 /// `x5`/`t0` (t0, saved by caller)
314 #[inline]
315 pub fn x5(&self) -> u32 {
316 self.x5
317 }
318
319 /// `x6`/`t1` (t1, saved by caller)
320 #[inline]
321 pub fn x6(&self) -> u32 {
322 self.x6
323 }
324
325 /// `x7`/`t2` (t2, saved by caller)
326 #[inline]
327 pub fn x7(&self) -> u32 {
328 self.x7
329 }
330
331 /// `x10`/`a0` (a0, saved by caller)
332 #[inline]
333 #[cfg(not(feature = "interrupts-qregs"))]
334 pub fn x10(&self) -> u32 {
335 self.x10
336 }
337
338 /// `x11`/`a1` (a1, saved by caller)
339 #[inline]
340 #[cfg(not(feature = "interrupts-qregs"))]
341 pub fn x11(&self) -> u32 {
342 self.x11
343 }
344
345 /// `x12`/`a2` (a2, saved by caller)
346 #[inline]
347 #[cfg(not(feature = "interrupts-qregs"))]
348 pub fn x12(&self) -> u32 {
349 self.x12
350 }
351
352 /// `x13`/`a3` (a3, saved by caller)
353 #[inline]
354 pub fn x13(&self) -> u32 {
355 self.x13
356 }
357
358 /// `x14`/`a4` (a4, saved by caller)
359 #[inline]
360 pub fn x14(&self) -> u32 {
361 self.x14
362 }
363
364 /// `x15`/`a5` (a5, saved by caller)
365 #[inline]
366 pub fn x15(&self) -> u32 {
367 self.x15
368 }
369
370 /// `x16`/`a6` (a6, saved by caller)
371 #[inline]
372 pub fn x16(&self) -> u32 {
373 self.x16
374 }
375
376 /// `x17`/`a7` (a7, saved by caller)
377 #[inline]
378 pub fn x17(&self) -> u32 {
379 self.x17
380 }
381
382 /// `x28`/`t3` (t3, saved by caller)
383 #[inline]
384 pub fn x28(&self) -> u32 {
385 self.x28
386 }
387
388 /// `x29`/`t4` (t4, saved by caller)
389 #[inline]
390 pub fn x29(&self) -> u32 {
391 self.x29
392 }
393
394 /// `x30`/`t5` (t5, saved by caller)
395 #[inline]
396 pub fn x30(&self) -> u32 {
397 self.x30
398 }
399
400 /// `x31`/`t6` (t6, saved by caller)
401 #[inline]
402 pub fn x31(&self) -> u32 {
403 self.x31
404 }
405}
406
407impl fmt::Debug for PicoRV32StoredRegisters {
408 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
409 let pc = if self.x1() & 1 == 1 {
410 self.x1() - 3
411 } else {
412 self.x1() - 4
413 };
414
415 let (instr, long_instr) = {
416 let mut instr: u32 =
417 *(unsafe { NonNull::new_unchecked(pc as *mut u16).as_ref() }) as u32;
418 let long_instr = (instr & 3) == 3;
419 if long_instr {
420 let instr2 =
421 *(unsafe { NonNull::new_unchecked((pc + 2) as *mut u16).as_ref() }) as u32;
422 instr = instr | instr2 << 16;
423 }
424 (instr, long_instr)
425 };
426
427 write!(f, "RA: {:08x}\tINSTR: ", self.x1())?;
428 if long_instr {
429 writeln!(f, "{:08x}", instr)?;
430 } else {
431 writeln!(f, "{:04x}", instr)?;
432 }
433
434 writeln!(f, "SP: {:08x}\tGP: {:08x}", self.x2(), self.x3())?;
435 writeln!(
436 f,
437 "T0: {:08x}\tT1: {:08x}\tT2: {:08x}",
438 self.x5(),
439 self.x6(),
440 self.x7()
441 )?;
442 writeln!(
443 f,
444 "A0: {:08x}\tA1: {:08x}\tA2: {:08x}\tA3: {:08x}",
445 self.x10(),
446 self.x11(),
447 self.x12(),
448 self.x13()
449 )?;
450 writeln!(
451 f,
452 "A4: {:08x}\tA5: {:08x}\tA6: {:08x}\tA7: {:08x}",
453 self.x14(),
454 self.x15(),
455 self.x16(),
456 self.x17()
457 )?;
458 writeln!(
459 f,
460 "T3: {:08x}\tT4: {:08x}\tT5: {:08x}\tT6: {:08x}",
461 self.x28(),
462 self.x29(),
463 self.x30(),
464 self.x31()
465 )?;
466 Ok(())
467 }
468}
469
470/// All stored registers
471#[repr(C)]
472#[derive(Copy, Clone)]
473pub struct PicoRV32AllStoredRegisters {
474 x3: u32,
475 x1: u32,
476 x2: u32,
477 x5: u32,
478 x6: u32,
479 x7: u32,
480 x10: u32,
481 x11: u32,
482 x12: u32,
483 x13: u32,
484 x14: u32,
485 x15: u32,
486 x16: u32,
487 x17: u32,
488 x28: u32,
489 x29: u32,
490 x30: u32,
491 x31: u32,
492}
493
494impl From<PicoRV32StoredRegisters> for PicoRV32AllStoredRegisters {
495 fn from(r: PicoRV32StoredRegisters) -> Self {
496 if cfg!(feature = "interrupts-qregs") {
497 PicoRV32AllStoredRegisters {
498 x3: r.x3(),
499 x1: r.x1(),
500 x2: r.x2(),
501 x5: r.x5(),
502 x6: r.x6(),
503 x7: r.x7(),
504 x10: r.x10(),
505 x11: r.x11(),
506 x12: r.x12(),
507 x13: r.x13(),
508 x14: r.x14(),
509 x15: r.x15(),
510 x16: r.x16(),
511 x17: r.x17(),
512 x28: r.x28(),
513 x29: r.x29(),
514 x30: r.x30(),
515 x31: r.x31(),
516 }
517 } else {
518 unsafe { core::mem::transmute_copy(&r) }
519 }
520 }
521}
522
523/// Trap entry point rust (_start_trap_rust)
524///
525/// `irqs` is a bitmask off IRQs to handle
526#[link_section = ".trap.rust"]
527#[export_name = "_start_trap_rust"]
528pub extern "C" fn start_trap_rust(regs: *const u32, irqs: u32) {
529 extern "C" {
530 fn trap_handler(regs: &PicoRV32StoredRegisters, irqs: u32);
531 }
532
533 unsafe {
534 // dispatch trap to handler
535 trap_handler(
536 NonNull::new_unchecked(regs as *mut PicoRV32StoredRegisters).as_ref(),
537 irqs,
538 );
539 }
540}
541
542/// Default Trap Handler
543#[no_mangle]
544pub fn default_trap_handler(_irqs: u32) {}
545
546#[doc(hidden)]
547#[no_mangle]
548pub unsafe fn default_pre_init() {}
549
550/// Usage:
551///
552/// ```
553/// use core::sync::atomic;
554/// use core::sync::atomic::Ordering;
555///
556/// pub fn timer(_regs: &picorv32_rt::PicoRV32StoredRegisters) {
557/// // ...
558/// }
559///
560/// pub fn illegal_instruction(_regs: &picorv32_rt::PicoRV32StoredRegisters) {
561/// loop {
562/// atomic::compiler_fence(Ordering::SeqCst);
563/// }
564/// }
565///
566/// pub fn bus_error(_regs: &picorv32_rt::PicoRV32StoredRegisters) {
567/// loop {
568/// atomic::compiler_fence(Ordering::SeqCst);
569/// }
570/// }
571///
572/// pub fn irq5(_regs: &picorv32_rt::PicoRV32StoredRegisters) {
573/// // ...
574/// }
575///
576/// pub fn irq6(_regs: &picorv32_rt::PicoRV32StoredRegisters) {
577/// // ...
578/// }
579///
580/// picorv32_interrupts!(
581/// 0: timer,
582/// 1: illegal_instruction,
583/// 2: bus_error,
584/// 5: irq5,
585/// 6: irq6
586/// );
587/// ```
588#[cfg(feature = "interrupts")]
589#[macro_export]
590macro_rules! picorv32_interrupts {
591 (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => {
592 if $pending_irqs & (1 << $n) != 0 {
593 $handler($regs);
594 }
595 };
596 ( $( $irq:literal : $handler:ident ),* ) => {
597 #[no_mangle]
598 pub extern "C" fn trap_handler(regs: *const picorv32_rt::PicoRV32StoredRegisters, pending_irqs: u32) {
599 let regs = unsafe { regs.as_ref().unwrap() };
600 $(
601 picorv32_interrupts!(@interrupt($irq, pending_irqs, regs, $handler));
602 )*
603 }
604 };
605}
606
607/// sleep until an interrupt is received
608pub fn wfi() {
609 let _irqs = unsafe { asm::waitirq() };
610}