betrusted_rt/lib.rs
1//! Minimal startup / runtime for RISC-V CPU's
2//!
3//! # Minimum Supported Rust Version (MSRV)
4//!
5//! This crate is guaranteed to compile on stable Rust 1.31 and up. It *might*
6//! compile with older versions but that may change in any new patch release.
7//! Note that `riscv64imac-unknown-none-elf` and `riscv64gc-unknown-none-elf` targets
8//! are not supported on stable yet.
9//!
10//! # Features
11//!
12//! This crate provides
13//!
14//! - Before main initialization of the `.bss` and `.data` sections.
15//!
16//! - `#[entry]` to declare the entry point of the program
17//! - `#[pre_init]` to run code *before* `static` variables are initialized
18//!
19//! - A linker script that encodes the memory layout of a generic RISC-V
20//! microcontroller. This linker script is missing some information that must
21//! be supplied through a `memory.x` file (see example below). This file
22//! must be supplied using rustflags and listed *before* `link.x`. Arbitrary
23//! filename can be use instead of `memory.x`.
24//!
25//! - A `_sheap` symbol at whose address you can locate a heap.
26//!
27//! ``` text
28//! $ cargo new --bin app && cd $_
29//!
30//! $ # add this crate as a dependency
31//! $ edit Cargo.toml && cat $_
32//! [dependencies]
33//! riscv-rt = "0.6.1"
34//! panic-halt = "0.2.0"
35//!
36//! $ # memory layout of the device
37//! $ edit memory.x && cat $_
38//! MEMORY
39//! {
40//! RAM : ORIGIN = 0x80000000, LENGTH = 16K
41//! FLASH : ORIGIN = 0x20000000, LENGTH = 16M
42//! }
43//!
44//! REGION_ALIAS("REGION_TEXT", FLASH);
45//! REGION_ALIAS("REGION_RODATA", FLASH);
46//! REGION_ALIAS("REGION_DATA", RAM);
47//! REGION_ALIAS("REGION_BSS", RAM);
48//! REGION_ALIAS("REGION_HEAP", RAM);
49//! REGION_ALIAS("REGION_STACK", RAM);
50//!
51//! $ edit src/main.rs && cat $_
52//! ```
53//!
54//! ``` ignore,no_run
55//! #![no_std]
56//! #![no_main]
57//!
58//! extern crate panic_halt;
59//!
60//! use riscv_rt::entry;
61//!
62//! // use `main` as the entry point of this application
63//! // `main` is not allowed to return
64//! #[entry]
65//! fn main() -> ! {
66//! // do something here
67//! loop { }
68//! }
69//! ```
70//!
71//! ``` text
72//! $ mkdir .cargo && edit .cargo/config && cat $_
73//! [target.riscv32imac-unknown-none-elf]
74//! rustflags = [
75//! "-C", "link-arg=-Tmemory.x",
76//! "-C", "link-arg=-Tlink.x",
77//! ]
78//!
79//! [build]
80//! target = "riscv32imac-unknown-none-elf"
81//! $ edit build.rs && cat $_
82//! ```
83//!
84//! ``` ignore,no_run
85//! use std::env;
86//! use std::fs::File;
87//! use std::io::Write;
88//! use std::path::Path;
89//!
90//! /// Put the linker script somewhere the linker can find it.
91//! fn main() {
92//! let out_dir = env::var("OUT_DIR").expect("No out dir");
93//! let dest_path = Path::new(&out_dir);
94//! let mut f = File::create(&dest_path.join("memory.x"))
95//! .expect("Could not create file");
96//!
97//! f.write_all(include_bytes!("memory.x"))
98//! .expect("Could not write file");
99//!
100//! println!("cargo:rustc-link-search={}", dest_path.display());
101//!
102//! println!("cargo:rerun-if-changed=memory.x");
103//! println!("cargo:rerun-if-changed=build.rs");
104//! }
105//! ```
106//!
107//! ``` text
108//! $ cargo build
109//!
110//! $ riscv32-unknown-elf-objdump -Cd $(find target -name app) | head
111//!
112//! Disassembly of section .text:
113//!
114//! 20000000 <_start>:
115//! 20000000: 800011b7 lui gp,0x80001
116//! 20000004: 80018193 addi gp,gp,-2048 # 80000800 <_stack_start+0xffffc800>
117//! 20000008: 80004137 lui sp,0x80004
118//! ```
119//!
120//! # Symbol interfaces
121//!
122//! This crate makes heavy use of symbols, linker sections and linker scripts to
123//! provide most of its functionality. Below are described the main symbol
124//! interfaces.
125//!
126//! ## `memory.x`
127//!
128//! This file supplies the information about the device to the linker.
129//!
130//! ### `MEMORY`
131//!
132//! The main information that this file must provide is the memory layout of
133//! the device in the form of the `MEMORY` command. The command is documented
134//! [here][2], but at a minimum you'll want to create at least one memory region.
135//!
136//! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html
137//!
138//! To support different relocation models (RAM-only, FLASH+RAM) multiple regions are used:
139//!
140//! - `REGION_TEXT` - for `.init`, `.trap` and `.text` sections
141//! - `REGION_RODATA` - for `.rodata` section and storing initial values for `.data` section
142//! - `REGION_DATA` - for `.data` section
143//! - `REGION_BSS` - for `.bss` section
144//! - `REGION_HEAP` - for the heap area
145//! - `REGION_STACK` - for hart stacks
146//!
147//! Specific aliases for these regions must be defined in `memory.x` file (see example below).
148//!
149//! ### `_stext`
150//!
151//! This symbol provides the loading address of `.text` section. This value can be changed
152//! to override the loading address of the firmware (for example, in case of bootloader present).
153//!
154//! If omitted this symbol value will default to `ORIGIN(REGION_TEXT)`.
155//!
156//! ### `_stack_start`
157//!
158//! This symbol provides the address at which the call stack will be allocated.
159//! The call stack grows downwards so this address is usually set to the highest
160//! valid RAM address plus one (this *is* an invalid address but the processor
161//! will decrement the stack pointer *before* using its value as an address).
162//!
163//! In case of multiple harts present, this address defines the initial stack pointer for hart 0.
164//! Stack pointer for hart `N` is calculated as `_stack_start - N * _hart_stack_size`.
165//!
166//! If omitted this symbol value will default to `ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)`.
167//!
168//! #### Example
169//!
170//! Allocating the call stack on a different RAM region.
171//!
172//! ``` text
173//! MEMORY
174//! {
175//! L2_LIM : ORIGIN = 0x08000000, LENGTH = 1M
176//! RAM : ORIGIN = 0x80000000, LENGTH = 16K
177//! FLASH : ORIGIN = 0x20000000, LENGTH = 16M
178//! }
179//!
180//! REGION_ALIAS("REGION_TEXT", FLASH);
181//! REGION_ALIAS("REGION_RODATA", FLASH);
182//! REGION_ALIAS("REGION_DATA", RAM);
183//! REGION_ALIAS("REGION_BSS", RAM);
184//! REGION_ALIAS("REGION_HEAP", RAM);
185//! REGION_ALIAS("REGION_STACK", L2_LIM);
186//!
187//! _stack_start = ORIGIN(L2_LIM) + LENGTH(L2_LIM);
188//! ```
189//!
190//! ### `_max_hart_id`
191//!
192//! This symbol defines the maximum hart id suppoted. All harts with id
193//! greater than `_max_hart_id` will be redirected to `abort()`.
194//!
195//! This symbol is supposed to be redefined in platform support crates for
196//! multi-core targets.
197//!
198//! If omitted this symbol value will default to 0 (single core).
199//!
200//! ### `_hart_stack_size`
201//!
202//! This symbol defines stack area size for *one* hart.
203//!
204//! If omitted this symbol value will default to 2K.
205//!
206//! ### `_heap_size`
207//!
208//! This symbol provides the size of a heap region. The default value is 0. You can set `_heap_size`
209//! to a non-zero value if you are planning to use heap allocations.
210//!
211//! ### `_sheap`
212//!
213//! This symbol is located in RAM right after the `.bss` and `.data` sections.
214//! You can use the address of this symbol as the start address of a heap
215//! region. This symbol is 4 byte aligned so that address will be a multiple of 4.
216//!
217//! #### Example
218//!
219//! ``` no_run
220//! extern crate some_allocator;
221//!
222//! extern "C" {
223//! static _sheap: u8;
224//! static _heap_size: u8;
225//! }
226//!
227//! fn main() {
228//! unsafe {
229//! let heap_bottom = &_sheap as *const u8 as usize;
230//! let heap_size = &_heap_size as *const u8 as usize;
231//! some_allocator::initialize(heap_bottom, heap_size);
232//! }
233//! }
234//! ```
235//!
236//! ### `_mp_hook`
237//!
238//! This function is called from all the harts and must return true only for one hart,
239//! which will perform memory initialization. For other harts it must return false
240//! and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
241//!
242//! This function can be redefined in the following way:
243//!
244//! ``` no_run
245//! #[export_name = "_mp_hook"]
246//! pub extern "Rust" fn mp_hook() -> bool {
247//! // ...
248//! }
249//! ```
250//!
251//! Default implementation of this function wakes hart 0 and busy-loops all the other harts.
252
253// NOTE: Adapted from cortex-m/src/lib.rs
254#![no_std]
255#![deny(missing_docs)]
256#![deny(warnings)]
257
258extern crate vexriscv as riscv;
259extern crate riscv_rt_macros as macros;
260extern crate r0;
261
262pub use macros::{entry, pre_init};
263
264use riscv::register::mstatus;
265
266#[export_name = "error: riscv-rt appears more than once in the dependency graph"]
267#[doc(hidden)]
268pub static __ONCE__: () = ();
269
270extern "C" {
271 // Boundaries of the .bss section
272 static mut _ebss: u32;
273 static mut _sbss: u32;
274
275 // Boundaries of the .data section
276 static mut _edata: u32;
277 static mut _sdata: u32;
278
279 // Initial values of the .data section (stored in Flash)
280 static _sidata: u32;
281}
282
283
284/// Rust entry point (_start_rust)
285///
286/// Zeros bss section, initializes data section and calls main. This function
287/// never returns.
288#[link_section = ".init.rust"]
289#[export_name = "_start_rust"]
290pub unsafe extern "C" fn start_rust() -> ! {
291 extern "Rust" {
292 // This symbol will be provided by the user via `#[entry]`
293 fn main() -> !;
294
295 // This symbol will be provided by the user via `#[pre_init]`
296 fn __pre_init();
297
298 fn _mp_hook() -> bool;
299 }
300
301 if _mp_hook() {
302 __pre_init();
303
304 r0::zero_bss(&mut _sbss, &mut _ebss);
305 r0::init_data(&mut _sdata, &mut _edata, &_sidata);
306 }
307
308 // TODO: Enable FPU when available
309
310 main();
311}
312
313
314/// Trap entry point rust (_start_trap_rust)
315///
316/// mcause is read to determine the cause of the trap. XLEN-1 bit indicates
317/// if it's an interrupt or an exception. The result is converted to an element
318/// of the Interrupt or Exception enum and passed to handle_interrupt or
319/// handle_exception.
320#[link_section = ".trap.rust"]
321#[export_name = "_start_trap_rust"]
322pub extern "C" fn start_trap_rust() {
323 extern "C" {
324 fn trap_handler();
325 }
326
327 unsafe {
328 // dispatch trap to handler
329 trap_handler();
330
331 // mstatus, remain in M-mode after mret
332 mstatus::set_mpp(mstatus::MPP::Machine);
333 }
334}
335
336
337#[doc(hidden)]
338#[no_mangle]
339pub fn default_trap_handler() {}
340
341#[doc(hidden)]
342#[no_mangle]
343pub unsafe extern "Rust" fn default_pre_init() {}
344
345#[doc(hidden)]
346#[no_mangle]
347pub extern "Rust" fn default_mp_hook() -> bool {
348 use riscv::register::mhartid;
349 match mhartid::read() {
350 0 => true,
351 _ => loop {
352 unsafe { riscv::asm::wfi() }
353 },
354 }
355}