esp32c_rt/
lib.rs

1//! Minimal runtime / startup for ESP32-C series SoCs
2//!
3//! This crate is a fork of the `riscv-rt` crate.
4//!
5//! # Minimum Supported Rust Version (MSRV)
6//!
7//! This crate is guaranteed to compile on stable Rust 1.42 and up. It *might*
8//! compile with older versions but that may change in any new patch release.
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 the ESP32-C3 RISC-V
20//!   SoC. The memory layout is only included when the feature `esp32c3` is
21//!   enabled. Alternatively, a custom memory layout can be included. This file
22//!   must be supplied using rustflags and listed *before* `link.x`. An arbitrary
23//!   filename can be used.
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//! esp32c-rt = "0.1.0"
34//! panic-halt = "0.2.0"
35//!
36//! $ edit src/main.rs && cat $_
37//! ```
38//!
39//! ``` ignore,no_run
40//! #![no_std]
41//! #![no_main]
42//!
43//! use esp32c_rt::entry;
44//!
45//! // Provides a necessary panic handler
46//! #[allow(unused_imports)]
47//! use panic_halt;
48//!
49//! // use `main` as the entry point of this application
50//! // `main` is not allowed to return
51//! #[entry]
52//! fn main() -> ! {
53//!     // do something here
54//!     loop { }
55//! }
56//! ```
57//!
58//! ``` text
59//! $ mkdir .cargo && edit .cargo/config && cat $_
60//! [target.riscv32imc-unknown-none-elf]
61//! rustflags = [
62//!   "-C", "link-arg=-Tmemory.x",
63//!   "-C", "link-arg=-Tlink.x",
64//! ]
65//!
66//! [build]
67//! target = "riscv32imc-unknown-none-elf"
68//!
69//! ``` text
70//! $ cargo build
71//!
72//! $ riscv32-unknown-elf-objdump -Cd $(find target -name app) | head
73//!
74//! Disassembly of section .text:
75//!
76//! 42000000 <_start-0x8>:
77//! 42000000:       041d                    addi    s0,s0,7
78//! 42000002:       041daedb                0x41daedb
79//! 42000006:                       0xaedb
80//! ```
81//!
82//! # Symbol interfaces
83//!
84//! This crate makes heavy use of symbols, linker sections and linker scripts to
85//! provide most of its functionality. Below are described the main symbol
86//! interfaces.
87//!
88//! ## `memory.x`
89//!
90//! This file supplies the information about the device to the linker.
91//!
92//! _If the feature `esp32c3` is enabled (enabled by default), this file does not have
93//! to be provided. The memory layout for the ESP32-C3 in that case is already
94//! provided by this crate.
95//! By disabling the feature, a custom `memory.x` file can be provided and used._
96//!
97//! ### `MEMORY`
98//!
99//! The main information that this file must provide is the memory layout of
100//! the device in the form of the `MEMORY` command. The command is documented
101//! [here][2], but at a minimum you'll want to create at least one memory region.
102//!
103//! [2]: https://sourceware.org/binutils/docs/ld/MEMORY.html
104//!
105//! To support different relocation models (RAM-only, FLASH+RAM) multiple regions are used:
106//!
107//! - `REGION_TEXT` - for `.init`, `.trap` and `.text` sections
108//! - `REGION_RODATA` - for `.rodata` section and storing initial values for `.data` section
109//! - `REGION_DATA` - for `.data` section
110//! - `REGION_BSS` - for `.bss` section
111//! - `REGION_HEAP` - for the heap area
112//! - `REGION_STACK` - for hart stacks
113//!
114//! Specific aliases for these regions must be defined in `memory.x` file (see example below).
115//!
116//! ### `_stext`
117//!
118//! This symbol provides the loading address of `.text` section. This value can be changed
119//! to override the loading address of the firmware (for example, in case of bootloader present).
120//!
121//! If omitted this symbol value will default to `ORIGIN(REGION_TEXT)`.
122//!
123//! ### `_stack_start`
124//!
125//! This symbol provides the address at which the call stack will be allocated.
126//! The call stack grows downwards so this address is usually set to the highest
127//! valid RAM address plus one (this *is* an invalid address but the processor
128//! will decrement the stack pointer *before* using its value as an address).
129//!
130//! In case of multiple harts present, this address defines the initial stack pointer for hart 0.
131//! Stack pointer for hart `N` is calculated as  `_stack_start - N * _hart_stack_size`.
132//!
133//! If omitted this symbol value will default to `ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)`.
134//!
135//! #### Example
136//!
137//! Allocating the call stack on a different RAM region.
138//!
139//! ``` text
140//! MEMORY
141//! {
142//!   L2_LIM : ORIGIN = 0x08000000, LENGTH = 1M
143//!   RAM : ORIGIN = 0x80000000, LENGTH = 16K
144//!   FLASH : ORIGIN = 0x20000000, LENGTH = 16M
145//! }
146//!
147//! REGION_ALIAS("REGION_TEXT", FLASH);
148//! REGION_ALIAS("REGION_RODATA", FLASH);
149//! REGION_ALIAS("REGION_DATA", RAM);
150//! REGION_ALIAS("REGION_BSS", RAM);
151//! REGION_ALIAS("REGION_HEAP", RAM);
152//! REGION_ALIAS("REGION_STACK", L2_LIM);
153//!
154//! _stack_start = ORIGIN(L2_LIM) + LENGTH(L2_LIM);
155//! ```
156//!
157//! ### `_max_hart_id`
158//!
159//! This symbol defines the maximum hart id supported. All harts with id
160//! greater than `_max_hart_id` will be redirected to `abort()`.
161//!
162//! This symbol is supposed to be redefined in platform support crates for
163//! multi-core targets.
164//!
165//! If omitted this symbol value will default to 0 (single core).
166//!
167//! ### `_hart_stack_size`
168//!
169//! This symbol defines stack area size for *one* hart.
170//!
171//! If omitted this symbol value will default to 2K.
172//!
173//! ### `_heap_size`
174//!
175//! This symbol provides the size of a heap region. The default value is 0. You can set `_heap_size`
176//! to a non-zero value if you are planning to use heap allocations.
177//!
178//! ### `_sheap`
179//!
180//! This symbol is located in RAM right after the `.bss` and `.data` sections.
181//! You can use the address of this symbol as the start address of a heap
182//! region. This symbol is 4 byte aligned so that address will be a multiple of 4.
183//!
184//! #### Example
185//!
186//! ``` no_run
187//! extern crate some_allocator;
188//!
189//! extern "C" {
190//!     static _sheap: u8;
191//!     static _heap_size: u8;
192//! }
193//!
194//! fn main() {
195//!     unsafe {
196//!         let heap_bottom = &_sheap as *const u8 as usize;
197//!         let heap_size = &_heap_size as *const u8 as usize;
198//!         some_allocator::initialize(heap_bottom, heap_size);
199//!     }
200//! }
201//! ```
202//!
203//! ### `_mp_hook`
204//!
205//! This function is called from all the harts and must return true only for one hart,
206//! which will perform memory initialization. For other harts it must return false
207//! and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
208//!
209//! This function can be redefined in the following way:
210//!
211//! ``` no_run
212//! #[export_name = "_mp_hook"]
213//! pub extern "Rust" fn mp_hook() -> bool {
214//!    // ...
215//! }
216//! ```
217//!
218//! Default implementation of this function wakes hart 0 and busy-loops all the other harts.
219//!
220//! ### `ExceptionHandler`
221//!
222//! This function is called when exception is occured. The exception reason can be decoded from the
223//! `mcause` register.
224//!
225//! This function can be redefined in the following way:
226//!
227//! ``` no_run
228//! #[export_name = "ExceptionHandler"]
229//! fn custom_exception_handler(trap_frame: &riscv_rt::TrapFrame) -> ! {
230//!     // ...
231//! }
232//! ```
233//! or
234//! ``` no_run
235//! #[no_mangle]
236//! fn ExceptionHandler(trap_frame: &riscv_rt::TrapFrame) -> ! {
237//!     // ...
238//! }
239//! ```
240//!
241//! Default implementation of this function stucks in a busy-loop.
242//!
243//!
244//! ### Core interrupt handlers
245//!
246//! This functions are called when corresponding interrupt is occured.
247//! You can define an interrupt handler with one of the following names:
248//! * `UserSoft`
249//! * `SupervisorSoft`
250//! * `MachineSoft`
251//! * `UserTimer`
252//! * `SupervisorTimer`
253//! * `MachineTimer`
254//! * `UserExternal`
255//! * `SupervisorExternal`
256//! * `MachineExternal`
257//!
258//! For example:
259//! ``` no_run
260//! #[export_name = "MachineTimer"]
261//! fn custom_timer_handler() {
262//!     // ...
263//! }
264//! ```
265//! or
266//! ``` no_run
267//! #[no_mangle]
268//! fn MachineTimer() {
269//!     // ...
270//! }
271//! ```
272//!
273//! If interrupt handler is not explicitly defined, `DefaultHandler` is called.
274//!
275//! ### `DefaultHandler`
276//!
277//! This function is called when interrupt without defined interrupt handler is occured.
278//! The interrupt reason can be decoded from the `mcause` register.
279//!
280//! This function can be redefined in the following way:
281//!
282//! ``` no_run
283//! #[export_name = "DefaultHandler"]
284//! fn custom_interrupt_handler() {
285//!     // ...
286//! }
287//! ```
288//! or
289//! ``` no_run
290//! #[no_mangle]
291//! fn DefaultHandler() {
292//!     // ...
293//! }
294//! ```
295//!
296//! Default implementation of this function stucks in a busy-loop.
297
298// NOTE: Adapted from cortex-m/src/lib.rs
299#![no_std]
300#![deny(missing_docs)]
301
302extern crate r0;
303extern crate riscv;
304extern crate riscv_rt_macros as macros;
305
306pub use macros::{entry, pre_init};
307
308use riscv::register::mcause;
309
310#[export_name = "error: riscv-rt appears more than once in the dependency graph"]
311#[doc(hidden)]
312pub static __ONCE__: () = ();
313
314extern "C" {
315    // Boundaries of the .bss section
316    static mut _ebss: u32;
317    static mut _sbss: u32;
318
319    // Boundaries of the .data section
320    static mut _edata: u32;
321    static mut _sdata: u32;
322
323    // Initial values of the .data section (stored in Flash)
324    static _sidata: u32;
325}
326
327/// Rust entry point (_start_rust)
328///
329/// Zeros bss section, initializes data section and calls main. This function
330/// never returns.
331#[link_section = ".init.rust"]
332#[export_name = "_start_rust"]
333pub unsafe extern "C" fn start_rust() -> ! {
334    #[rustfmt::skip]
335    extern "Rust" {
336        // This symbol will be provided by the user via `#[entry]`
337        fn main() -> !;
338
339        // This symbol will be provided by the user via `#[pre_init]`
340        fn __pre_init();
341
342        fn _setup_interrupts();
343
344        fn _mp_hook() -> bool;
345    }
346
347    if _mp_hook() {
348        __pre_init();
349
350        r0::zero_bss(&mut _sbss, &mut _ebss);
351        r0::init_data(&mut _sdata, &mut _edata, &_sidata);
352    }
353
354    _setup_interrupts();
355
356    main();
357}
358
359/// Registers saved in trap handler
360#[allow(missing_docs)]
361#[repr(C)]
362pub struct TrapFrame {
363    pub ra: usize,
364    pub t0: usize,
365    pub t1: usize,
366    pub t2: usize,
367    pub t3: usize,
368    pub t4: usize,
369    pub t5: usize,
370    pub t6: usize,
371    pub a0: usize,
372    pub a1: usize,
373    pub a2: usize,
374    pub a3: usize,
375    pub a4: usize,
376    pub a5: usize,
377    pub a6: usize,
378    pub a7: usize,
379}
380
381/// Trap entry point rust (_start_trap_rust)
382///
383/// `mcause` is read to determine the cause of the trap. XLEN-1 bit indicates
384/// if it's an interrupt or an exception. The result is examined and ExceptionHandler
385/// or one of the core interrupt handlers is called.
386#[link_section = ".trap.rust"]
387#[export_name = "_start_trap_rust"]
388pub extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) {
389    extern "C" {
390        fn ExceptionHandler(trap_frame: &TrapFrame);
391        fn DefaultHandler();
392    }
393
394    unsafe {
395        let cause = mcause::read();
396        if cause.is_exception() {
397            ExceptionHandler(&*trap_frame)
398        } else {
399            let code = cause.code();
400            if code < __INTERRUPTS.len() {
401                let h = &__INTERRUPTS[code];
402                if h.reserved == 0 {
403                    DefaultHandler();
404                } else {
405                    (h.handler)();
406                }
407            } else {
408                DefaultHandler();
409            }
410        }
411    }
412}
413
414#[doc(hidden)]
415#[no_mangle]
416#[allow(unused_variables, non_snake_case)]
417pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! {
418    loop {
419        // Prevent this from turning into a UDF instruction
420        // see rust-lang/rust#28728 for details
421        continue;
422    }
423}
424
425#[doc(hidden)]
426#[no_mangle]
427#[allow(unused_variables, non_snake_case)]
428pub fn DefaultInterruptHandler() {
429    loop {
430        // Prevent this from turning into a UDF instruction
431        // see rust-lang/rust#28728 for details
432        continue;
433    }
434}
435
436/* Interrupts */
437#[doc(hidden)]
438pub enum Interrupt {
439    UserSoft,
440    SupervisorSoft,
441    MachineSoft,
442    UserTimer,
443    SupervisorTimer,
444    MachineTimer,
445    UserExternal,
446    SupervisorExternal,
447    MachineExternal,
448}
449
450pub use self::Interrupt as interrupt;
451
452extern "C" {
453    fn UserSoft();
454    fn SupervisorSoft();
455    fn MachineSoft();
456    fn UserTimer();
457    fn SupervisorTimer();
458    fn MachineTimer();
459    fn UserExternal();
460    fn SupervisorExternal();
461    fn MachineExternal();
462}
463
464#[doc(hidden)]
465pub union Vector {
466    handler: unsafe extern "C" fn(),
467    reserved: usize,
468}
469
470#[doc(hidden)]
471#[no_mangle]
472pub static __INTERRUPTS: [Vector; 12] = [
473    Vector { handler: UserSoft },
474    Vector {
475        handler: SupervisorSoft,
476    },
477    Vector { reserved: 0 },
478    Vector {
479        handler: MachineSoft,
480    },
481    Vector { handler: UserTimer },
482    Vector {
483        handler: SupervisorTimer,
484    },
485    Vector { reserved: 0 },
486    Vector {
487        handler: MachineTimer,
488    },
489    Vector {
490        handler: UserExternal,
491    },
492    Vector {
493        handler: SupervisorExternal,
494    },
495    Vector { reserved: 0 },
496    Vector {
497        handler: MachineExternal,
498    },
499];
500
501#[doc(hidden)]
502#[no_mangle]
503#[rustfmt::skip]
504pub unsafe extern "Rust" fn default_pre_init() {}
505
506#[doc(hidden)]
507#[no_mangle]
508#[rustfmt::skip]
509pub extern "Rust" fn default_mp_hook() -> bool {
510    use riscv::register::mhartid;
511    match mhartid::read() {
512        0 => true,
513        _ => loop {
514            unsafe { riscv::asm::wfi() }
515        },
516    }
517}