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}