├── .cargo-ok ├── src ├── config.rs ├── memory.rs ├── timer.rs ├── main.rs └── thread │ ├── sync.rs │ └── mod.rs ├── rust-toolchain.toml ├── .gitignore ├── openocd.cfg ├── Cargo.toml ├── openocd-release.gdb ├── openocd.gdb ├── .cargo └── config ├── memory.x └── README.md /.cargo-ok: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub(crate) const HEAP_SIZE: usize = 0x1000; 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-01-31" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | .gdb_history 4 | Cargo.lock 5 | target/ 6 | stm32f100.svd 7 | 8 | # editor files 9 | .vscode 10 | .idea 11 | asm.s 12 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::config::HEAP_SIZE; 2 | use core::mem::MaybeUninit; 3 | use embedded_alloc::Heap; 4 | 5 | #[global_allocator] 6 | pub static HEAP: Heap = Heap::empty(); 7 | pub fn init() { 8 | static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; 9 | unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } 10 | } 11 | -------------------------------------------------------------------------------- /openocd.cfg: -------------------------------------------------------------------------------- 1 | # Sample OpenOCD configuration for the STM32F3DISCOVERY development board 2 | 3 | # Depending on the hardware revision you got you'll have to pick ONE of these 4 | # interfaces. At any time only one interface should be commented out. 5 | 6 | # Revision C (newer revision) 7 | source [find interface/stlink-v2.cfg] 8 | 9 | # Revision A and B (older revisions) 10 | # source [find interface/stlink-v2.cfg] 11 | 12 | source [find target/stm32f1x.cfg] 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["longfangsong "] 3 | edition = "2021" 4 | readme = "README.md" 5 | name = "rs-rtt" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | cortex-m = { version = "0.7.7", features = [ 10 | "inline-asm", 11 | "critical-section-single-core", 12 | ] } 13 | cortex-m-rt = "0.7.3" 14 | cortex-m-semihosting = "0.5.0" 15 | embedded-alloc = "0.5.1" 16 | panic-semihosting = "0.6.0" 17 | spin = "0.9.8" 18 | 19 | # this lets you use `cargo fix`! 20 | [[bin]] 21 | name = "rs-rtt" 22 | test = false 23 | bench = false 24 | 25 | [profile.release] 26 | codegen-units = 1 # better optimizations 27 | debug = true # symbols are nice and they don't increase the size on Flash 28 | lto = true # better optimizations 29 | 30 | [profile.dev] 31 | opt-level = "z" 32 | -------------------------------------------------------------------------------- /src/timer.rs: -------------------------------------------------------------------------------- 1 | use cortex_m::Peripherals; 2 | 3 | pub fn init() { 4 | let mut peripherals = Peripherals::take().unwrap(); 5 | let ticks_per_10ms_by_hal = cortex_m::peripheral::SYST::get_ticks_per_10ms(); 6 | let ticks_per_10ms = if ticks_per_10ms_by_hal == 0 { 7 | match peripherals.SYST.get_clock_source() { 8 | cortex_m::peripheral::syst::SystClkSource::Core => { 9 | // todo: generic this to more mcus 10 | // lm3s6965evb internal clock: 12 MHz 11 | // seconds per tick: 1 / 12M 12 | // ms per tick: 1000 / 12M 13 | // tick_per_ms: 12M / 1000 14 | // tick_per_10ms: 12M / 100 15 | 120_000 16 | } 17 | cortex_m::peripheral::syst::SystClkSource::External => unimplemented!(), 18 | } 19 | } else { 20 | ticks_per_10ms_by_hal 21 | }; 22 | peripherals.SYST.set_reload(ticks_per_10ms); 23 | peripherals.SYST.enable_interrupt(); 24 | peripherals.SYST.enable_counter(); 25 | } 26 | -------------------------------------------------------------------------------- /openocd-release.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | file ./target/thumbv7m-none-eabi/release/rs-rtt 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | # # run the next few lines so the panic message is printed immediately 14 | # # the number needs to be adjusted for your panic handler 15 | # commands $bpnum 16 | # next 4 17 | # end 18 | 19 | # *try* to stop at the user entry point (it might be gone due to inlining) 20 | break main 21 | 22 | monitor arm semihosting enable 23 | 24 | # # send captured ITM to the file itm.fifo 25 | # # (the microcontroller SWO pin must be connected to the programmer SWO pin) 26 | # # 8000000 must match the core clock frequency 27 | # monitor tpiu config internal itm.txt uart off 8000000 28 | 29 | # # OR: make the microcontroller SWO pin output compatible with UART (8N1) 30 | # # 8000000 must match the core clock frequency 31 | # # 2000000 is the frequency of the SWO pin 32 | # monitor tpiu config external uart off 8000000 2000000 33 | 34 | # # enable ITM port 0 35 | # monitor itm port 0 on 36 | 37 | tui enable 38 | 39 | load 40 | 41 | c 42 | # start the process but immediately halt the processor 43 | # stepi 44 | -------------------------------------------------------------------------------- /openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | file ./target/thumbv7m-none-eabi/debug/rs-rtt 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # set backtrace limit to not have infinite backtrace loops 7 | set backtrace limit 32 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | # # run the next few lines so the panic message is printed immediately 14 | # # the number needs to be adjusted for your panic handler 15 | # commands $bpnum 16 | # next 4 17 | # end 18 | 19 | # *try* to stop at the user entry point (it might be gone due to inlining) 20 | break main 21 | 22 | monitor arm semihosting enable 23 | 24 | # # send captured ITM to the file itm.fifo 25 | # # (the microcontroller SWO pin must be connected to the programmer SWO pin) 26 | # # 8000000 must match the core clock frequency 27 | # monitor tpiu config internal itm.txt uart off 8000000 28 | 29 | # # OR: make the microcontroller SWO pin output compatible with UART (8N1) 30 | # # 8000000 must match the core clock frequency 31 | # # 2000000 is the frequency of the SWO pin 32 | # monitor tpiu config external uart off 8000000 2000000 33 | 34 | # # enable ITM port 0 35 | # monitor itm port 0 on 36 | 37 | tui enable 38 | 39 | # load 40 | 41 | # c 42 | # start the process but immediately halt the processor 43 | # stepi 44 | 45 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | # uncomment this to make `cargo run` execute programs on QEMU 3 | runner = "qemu-system-arm -S -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -gdb tcp::3333 -kernel" 4 | 5 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 6 | # uncomment ONE of these three option to make `cargo run` start a GDB session 7 | # which option to pick depends on your system 8 | # runner = "arm-none-eabi-gdb -q -x openocd.gdb" 9 | # runner = "gdb-multiarch -q -x openocd.gdb" 10 | # runner = "gdb -q -x openocd.gdb" 11 | 12 | rustflags = [ 13 | # LLD (shipped with the Rust toolchain) is used as the default linker 14 | "-C", "link-arg=-Tlink.x", 15 | 16 | # if you run into problems with LLD switch to the GNU linker by commenting out 17 | # this line 18 | # "-C", "linker=arm-none-eabi-ld", 19 | 20 | # if you need to link to pre-compiled C libraries provided by a C toolchain 21 | # use GCC as the linker by commenting out both lines above and then 22 | # uncommenting the three lines below 23 | # "-C", "linker=arm-none-eabi-gcc", 24 | # "-C", "link-arg=-Wl,-Tlink.x", 25 | # "-C", "link-arg=-nostartfiles", 26 | ] 27 | 28 | [build] 29 | # Pick ONE of these compilation targets 30 | # target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ 31 | target = "thumbv7m-none-eabi" # Cortex-M3 32 | # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) 33 | # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) 34 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 6 | /**/ 7 | /* These are for stm32f103 */ 8 | /* 9 | FLASH : ORIGIN = 0x8000000, LENGTH = 512K 10 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 11 | */ 12 | /* These are for stm32f100RBT6 */ 13 | /* 14 | FLASH : ORIGIN = 0x8000000, LENGTH = 128K 15 | RAM : ORIGIN = 0x20000000, LENGTH = 8K 16 | */ 17 | } 18 | 19 | /* This is where the call stack will be allocated. */ 20 | /* The stack is of the full descending type. */ 21 | /* You may want to use this variable to locate the call stack and static 22 | variables in different memory regions. Below is shown the default value */ 23 | /* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ 24 | 25 | /* You can use this symbol to customize the location of the .text section */ 26 | /* If omitted the .text section will be placed right after the .vector_table 27 | section */ 28 | /* This is required only on microcontrollers that store some configuration right 29 | after the vector table */ 30 | /* _stext = ORIGIN(FLASH) + 0x400; */ 31 | 32 | /* Example of putting non-initialized variables into custom RAM locations. */ 33 | /* This assumes you have defined a region RAM2 above, and in the Rust 34 | sources added the attribute `#[link_section = ".ram2bss"]` to the data 35 | you want to place there. */ 36 | /* Note that the section will not be zero-initialized by the runtime! */ 37 | /* SECTIONS { 38 | .ram2bss (NOLOAD) : ALIGN(4) { 39 | *(.ram2bss); 40 | . = ALIGN(4); 41 | } > RAM2 42 | } INSERT AFTER .bss; 43 | */ -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(naked_functions)] 4 | 5 | extern crate panic_semihosting; 6 | 7 | use crate::thread::spawn; 8 | use cortex_m::asm::wfi; 9 | use cortex_m_rt::entry; 10 | use cortex_m_semihosting::hprintln; 11 | use thread::{sync::Mutex, Address}; 12 | 13 | mod config; 14 | mod memory; 15 | mod thread; 16 | mod timer; 17 | 18 | #[no_mangle] 19 | static mut I: u32 = 0; 20 | 21 | static mut MI: Mutex = Mutex::new(0); 22 | 23 | fn thread1(_: Address) { 24 | hprintln!("thread1 started"); 25 | for _ in 0..100000u64 { 26 | cortex_m::asm::delay(101); 27 | let mut tmp = unsafe { I }; 28 | tmp += 1; 29 | unsafe { 30 | I = tmp; 31 | } 32 | cortex_m::asm::delay(103); 33 | let mut mi = unsafe { MI.lock() }; 34 | cortex_m::asm::delay(107); 35 | // hprintln!("thread1 get lock"); 36 | let mut tmp = *mi; 37 | tmp += 1; 38 | *mi = tmp; 39 | cortex_m::asm::delay(109); 40 | // hprintln!("thread1 released lock"); 41 | } 42 | // hprintln!("thread1 get lock"); 43 | let mi = unsafe { MI.lock() }; 44 | hprintln!("thread1 done {}, {}", unsafe { I }, *mi); 45 | drop(mi); 46 | // hprintln!("thread1 released lock"); 47 | loop { 48 | wfi(); 49 | } 50 | } 51 | 52 | fn thread2(_: Address) { 53 | hprintln!("thread2 started"); 54 | for _ in 0..100000u64 { 55 | cortex_m::asm::delay(113); 56 | let mut tmp = unsafe { I }; 57 | tmp += 1; 58 | unsafe { 59 | I = tmp; 60 | } 61 | cortex_m::asm::delay(127); 62 | let mut mi = unsafe { MI.lock() }; 63 | cortex_m::asm::delay(131); 64 | // hprintln!("thread2 get lock"); 65 | let mut tmp = *mi; 66 | tmp += 1; 67 | *mi = tmp; 68 | cortex_m::asm::delay(137); 69 | // hprintln!("thread2 released lock"); 70 | } 71 | // hprintln!("thread2 get lock"); 72 | let mi = unsafe { MI.lock() }; 73 | hprintln!("thread2 done {}, {}", unsafe { I }, *mi); 74 | drop(mi); 75 | // hprintln!("thread2 released lock"); 76 | loop { 77 | wfi(); 78 | } 79 | } 80 | 81 | fn main_thread(_: Address) { 82 | spawn(thread1, 0, 0x200); 83 | spawn(thread2, 0, 0x200); 84 | // cortex_m::asm::delay(100_000_000); 85 | // let mi = unsafe { MI.lock() }; 86 | // hprintln!("All done {}, {}", unsafe { I }, *mi); 87 | // drop(mi); 88 | loop { 89 | wfi(); 90 | } 91 | } 92 | 93 | #[entry] 94 | fn main() -> ! { 95 | memory::init(); 96 | timer::init(); 97 | thread::init(main_thread, 0, 0x200); 98 | loop { 99 | cortex_m::asm::wfi(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/thread/sync.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use super::{request_switch, SimpleThread, PENDING_LIST}; 4 | use alloc::collections::VecDeque; 5 | use core::cell::{RefCell, UnsafeCell}; 6 | use core::ops::{Deref, DerefMut}; 7 | use cortex_m::interrupt::{self, CriticalSection}; 8 | 9 | pub trait Blocker { 10 | fn blocking_list(&self, cs: &CriticalSection) 11 | -> impl DerefMut>; 12 | 13 | fn block(&self, cs: &CriticalSection) { 14 | let mut pending_list = cortex_m::interrupt::Mutex::borrow(&PENDING_LIST, cs).borrow_mut(); 15 | let current_thread = pending_list.pop_front().unwrap(); 16 | let mut blocking_list = self.blocking_list(cs); 17 | blocking_list.push_back(current_thread); 18 | let from_thread_sp = &mut blocking_list.back_mut().unwrap().stack_pointer; 19 | let next_thread_sp = &mut pending_list.front_mut().unwrap().stack_pointer; 20 | unsafe { 21 | request_switch(cs, from_thread_sp, next_thread_sp); 22 | } 23 | } 24 | 25 | fn unblock_first(&self, cs: &CriticalSection) { 26 | let mut pending_list = cortex_m::interrupt::Mutex::borrow(&PENDING_LIST, cs).borrow_mut(); 27 | let mut blocking_list = self.blocking_list(cs); 28 | if let Some(blocked) = blocking_list.pop_front() { 29 | pending_list.push_back(blocked); 30 | } 31 | } 32 | } 33 | 34 | pub struct Mutex { 35 | resource: UnsafeCell, 36 | has_owner: RefCell, 37 | blocking_list: RefCell>, 38 | } 39 | 40 | impl Blocker for Mutex { 41 | fn blocking_list( 42 | &self, 43 | _cs: &CriticalSection, 44 | ) -> impl DerefMut> { 45 | self.blocking_list.borrow_mut() 46 | } 47 | } 48 | 49 | pub struct MutexGuard<'a, T> { 50 | lock: &'a Mutex, 51 | } 52 | 53 | unsafe impl Sync for MutexGuard<'_, T> {} 54 | 55 | impl Deref for MutexGuard<'_, T> { 56 | type Target = T; 57 | 58 | fn deref(&self) -> &T { 59 | unsafe { &*self.lock.resource.get() } 60 | } 61 | } 62 | 63 | impl DerefMut for MutexGuard<'_, T> { 64 | fn deref_mut(&mut self) -> &mut T { 65 | unsafe { &mut *self.lock.resource.get() } 66 | } 67 | } 68 | 69 | impl Mutex { 70 | pub const fn new(resource: T) -> Self { 71 | Self { 72 | resource: UnsafeCell::new(resource), 73 | has_owner: RefCell::new(false), 74 | blocking_list: RefCell::new(VecDeque::new()), 75 | } 76 | } 77 | pub fn lock(&mut self) -> MutexGuard<'_, T> { 78 | interrupt::free(|cs: &CriticalSection| { 79 | let has_owner = self.has_owner.get_mut(); 80 | if !*has_owner { 81 | *has_owner = true; 82 | } else { 83 | self.block(cs) 84 | } 85 | MutexGuard { lock: self } 86 | }) 87 | } 88 | } 89 | 90 | impl<'a, T> Drop for MutexGuard<'a, T> { 91 | fn drop(&mut self) { 92 | interrupt::free(|cs: &CriticalSection| { 93 | let mut has_owner = self.lock.has_owner.borrow_mut(); 94 | let has_owner = has_owner.deref_mut(); 95 | // debug_assert!(*has_owner); 96 | *has_owner = false; 97 | self.lock.unblock_first(cs) 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `cortex-m-quickstart` 2 | 3 | > A template for building applications for ARM Cortex-M microcontrollers 4 | 5 | This project is developed and maintained by the [Cortex-M team][team]. 6 | 7 | ## Dependencies 8 | 9 | To build embedded programs using this template you'll need: 10 | 11 | - Rust 1.31, 1.30-beta, nightly-2018-09-13 or a newer toolchain. e.g. `rustup 12 | default beta` 13 | 14 | - The `cargo generate` subcommand. [Installation 15 | instructions](https://github.com/ashleygwilliams/cargo-generate#installation). 16 | 17 | - `rust-std` components (pre-compiled `core` crate) for the ARM Cortex-M 18 | targets. Run: 19 | 20 | ``` console 21 | $ rustup target add thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf 22 | ``` 23 | 24 | ## Using this template 25 | 26 | **NOTE**: This is the very short version that only covers building programs. For 27 | the long version, which additionally covers flashing, running and debugging 28 | programs, check [the embedded Rust book][book]. 29 | 30 | [book]: https://rust-embedded.github.io/book 31 | 32 | 0. Before we begin you need to identify some characteristics of the target 33 | device as these will be used to configure the project: 34 | 35 | - The ARM core. e.g. Cortex-M3. 36 | 37 | - Does the ARM core include an FPU? Cortex-M4**F** and Cortex-M7**F** cores do. 38 | 39 | - How much Flash memory and RAM does the target device has? e.g. 256 KiB of 40 | Flash and 32 KiB of RAM. 41 | 42 | - Where are Flash memory and RAM mapped in the address space? e.g. RAM is 43 | commonly located at address `0x2000_0000`. 44 | 45 | You can find this information in the data sheet or the reference manual of your 46 | device. 47 | 48 | In this example we'll be using the STM32F3DISCOVERY. This board contains an 49 | STM32F303VCT6 microcontroller. This microcontroller has: 50 | 51 | - A Cortex-M4F core that includes a single precision FPU 52 | 53 | - 256 KiB of Flash located at address 0x0800_0000. 54 | 55 | - 40 KiB of RAM located at address 0x2000_0000. (There's another RAM region but 56 | for simplicity we'll ignore it). 57 | 58 | 1. Instantiate the template. 59 | 60 | ``` console 61 | $ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart 62 | Project Name: app 63 | Creating project called `app`... 64 | Done! New project created /tmp/app 65 | 66 | $ cd app 67 | ``` 68 | 69 | 2. Set a default compilation target. There are four options as mentioned at the 70 | bottom of `.cargo/config`. For the STM32F303VCT6, which has a Cortex-M4F 71 | core, we'll pick the `thumbv7em-none-eabihf` target. 72 | 73 | ``` console 74 | $ tail -n6 .cargo/config 75 | ``` 76 | 77 | ``` toml 78 | [build] 79 | # Pick ONE of these compilation targets 80 | # target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ 81 | # target = "thumbv7m-none-eabi" # Cortex-M3 82 | # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) 83 | target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) 84 | ``` 85 | 86 | 3. Enter the memory region information into the `memory.x` file. 87 | 88 | ``` console 89 | $ cat memory.x 90 | /* Linker script for the STM32F303VCT6 */ 91 | MEMORY 92 | { 93 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 94 | FLASH : ORIGIN = 0x08000000, LENGTH = 256K 95 | RAM : ORIGIN = 0x20000000, LENGTH = 40K 96 | } 97 | ``` 98 | 99 | 4. Build the template application or one of the examples. 100 | 101 | ``` console 102 | $ cargo build 103 | ``` 104 | 105 | ## VS Code 106 | 107 | This template includes launch configurations for debugging CortexM programs with Visual Studio Code located in the `.vscode/` directory. 108 | See [.vscode/README.md](./.vscode/README.md) for more information. 109 | If you're not using VS Code, you can safely delete the directory from the generated project. 110 | 111 | # License 112 | 113 | This template is licensed under either of 114 | 115 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 116 | http://www.apache.org/licenses/LICENSE-2.0) 117 | 118 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 119 | 120 | at your option. 121 | 122 | ## Contribution 123 | 124 | Unless you explicitly state otherwise, any contribution intentionally submitted 125 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 126 | dual licensed as above, without any additional terms or conditions. 127 | 128 | ## Code of Conduct 129 | 130 | Contribution to this crate is organized under the terms of the [Rust Code of 131 | Conduct][CoC], the maintainer of this crate, the [Cortex-M team][team], promises 132 | to intervene to uphold that code of conduct. 133 | 134 | [CoC]: https://www.rust-lang.org/policies/code-of-conduct 135 | [team]: https://github.com/rust-embedded/wg#the-cortex-m-team 136 | -------------------------------------------------------------------------------- /src/thread/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use crate::memory::HEAP; 4 | use alloc::collections::VecDeque; 5 | use core::alloc::{GlobalAlloc, Layout}; 6 | use core::arch::asm; 7 | use core::cell::RefCell; 8 | use core::fmt::{Debug, Error, Formatter}; 9 | use core::mem; 10 | use core::mem::MaybeUninit; 11 | use core::ptr::null_mut; 12 | use cortex_m::asm::wfi; 13 | use cortex_m::interrupt::CriticalSection; 14 | use cortex_m::peripheral::scb::SystemHandler; 15 | use cortex_m::Peripherals; 16 | use cortex_m_rt::{exception, ExceptionFrame}; 17 | 18 | pub type Address = u32; 19 | type ThreadEntryFunction = fn(Address) -> (); 20 | 21 | #[derive(Debug, Clone, Copy)] 22 | #[repr(C)] 23 | struct StackFrame { 24 | r4: u32, 25 | r5: u32, 26 | r6: u32, 27 | r7: u32, 28 | r8: u32, 29 | r9: u32, 30 | r10: u32, 31 | r11: u32, 32 | exception_stack_frame: ExceptionFrame, 33 | } 34 | 35 | impl StackFrame { 36 | fn new(entry: ThreadEntryFunction, param: Address) -> Self { 37 | let exception_stack_frame = unsafe { 38 | let mut exception_stack_frame: ExceptionFrame = MaybeUninit::zeroed().assume_init(); 39 | exception_stack_frame.set_r0(param); 40 | exception_stack_frame.set_pc(entry as usize as _); 41 | // bit24 (enable thumb) must be 1, we don't care about the rest bits 42 | exception_stack_frame.set_xpsr(0x01000000); 43 | 44 | exception_stack_frame.set_r1(0xdeadbeef); 45 | exception_stack_frame.set_r2(0xdeadbeef); 46 | exception_stack_frame.set_r3(0xdeadbeef); 47 | exception_stack_frame.set_r12(0xdeadbeef); 48 | exception_stack_frame.set_lr(0xdeadbeef); 49 | exception_stack_frame 50 | }; 51 | StackFrame { 52 | r4: 0xdeadbeef, 53 | r5: 0xdeadbeef, 54 | r6: 0xdeadbeef, 55 | r7: 0xdeadbeef, 56 | r8: 0xdeadbeef, 57 | r9: 0xdeadbeef, 58 | r10: 0xdeadbeef, 59 | r11: 0xdeadbeef, 60 | exception_stack_frame, 61 | } 62 | } 63 | } 64 | 65 | pub struct SimpleThread { 66 | stack_pointer: Address, 67 | stack: Address, 68 | stack_size: usize, 69 | // TODO: deadline: u32, 70 | } 71 | 72 | impl Debug for SimpleThread { 73 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 74 | write!( 75 | f, 76 | "Thread which sp:0x{:x}, stack:0x{:x}", 77 | self.stack_pointer, self.stack 78 | ) 79 | } 80 | } 81 | 82 | impl SimpleThread { 83 | fn new(entry: ThreadEntryFunction, param: Address, stack_size: usize) -> Self { 84 | unsafe { 85 | // alloc space for the stack 86 | let stack = HEAP.alloc(Layout::from_size_align(stack_size, 8).unwrap()); 87 | // construct the first stackframe 88 | let frame: *mut StackFrame = stack.add(stack_size - mem::size_of::()) as _; 89 | *frame = StackFrame::new(entry, param); 90 | SimpleThread { 91 | stack_pointer: frame as _, 92 | stack: stack as _, 93 | stack_size, 94 | } 95 | } 96 | } 97 | } 98 | 99 | impl Drop for SimpleThread { 100 | fn drop(&mut self) { 101 | unsafe { 102 | HEAP.dealloc( 103 | self.stack as _, 104 | Layout::from_size_align(self.stack_size, 8).unwrap(), 105 | ) 106 | } 107 | } 108 | } 109 | 110 | pub(in crate::thread) static PENDING_LIST: cortex_m::interrupt::Mutex< 111 | RefCell>, 112 | > = cortex_m::interrupt::Mutex::new(RefCell::new(VecDeque::new())); 113 | 114 | fn idle(_: Address) { 115 | loop { 116 | wfi(); 117 | } 118 | } 119 | 120 | #[no_mangle] 121 | static mut SWITCH_FROM_SP: *mut u32 = null_mut(); 122 | #[no_mangle] 123 | static mut SWITCH_TO_SP: *mut u32 = null_mut(); 124 | 125 | #[naked] 126 | #[no_mangle] 127 | unsafe extern "C" fn PendSV() { 128 | asm!( 129 | "mrs r3, PRIMASK", 130 | "cpsid I", 131 | "ldr r1, ={0}", 132 | "ldr r1, [r1]", // r1: reference to the stack pointer of the thread we want to switch from 133 | "ldr r2, ={1}", 134 | "ldr r2, [r2]", // r2: reference to the stack pointer of the thread we want to switch to 135 | "cbz r1, 3f", 136 | "2:", 137 | " mrs r0, psp", 138 | " stmfd r0!, {{r4-r11}}", 139 | " str r0, [r1]", // update the stack pointer of the thread we want to switch from (update SimpleThread.stack_pointer) 140 | "3:", 141 | " ldr r0, [r2]", 142 | " ldmfd r0!, {{r4-r11}}", 143 | " msr psp, r0", 144 | "msr PRIMASK, r3", 145 | "orr lr, lr, #0x04", 146 | "bx lr", 147 | sym SWITCH_FROM_SP, sym SWITCH_TO_SP, options(noreturn)); 148 | } 149 | 150 | // safety: 151 | // precondition: 152 | // `from_sp` should point to existing SimpleThread's stack or null 153 | // `to_sp` should point to existing SimpleThread's stack 154 | // No other functions except request_switch and PendSV should access `SWITCH_FROM_SP` and `SWITCH_TO_SP` 155 | unsafe fn request_switch(_cs: &CriticalSection, from_sp: *mut u32, to_sp: *mut u32) { 156 | debug_assert!(!to_sp.is_null() && to_sp != from_sp); 157 | SWITCH_FROM_SP = from_sp; 158 | SWITCH_TO_SP = to_sp; 159 | cortex_m::peripheral::SCB::set_pendsv(); 160 | } 161 | 162 | pub fn init(main_entry: ThreadEntryFunction, param: Address, stack_size: usize) { 163 | unsafe { 164 | let mut peripherals = Peripherals::steal(); 165 | peripherals.SCB.set_priority(SystemHandler::PendSV, 0xff); 166 | } 167 | let idle_thread = SimpleThread::new(idle, 0, 0x200); 168 | let thread = SimpleThread::new(main_entry, param, stack_size); 169 | cortex_m::interrupt::free(move |cs: &CriticalSection| { 170 | let mut pending_list = cortex_m::interrupt::Mutex::borrow(&PENDING_LIST, cs).borrow_mut(); 171 | pending_list.push_back(thread); 172 | pending_list.push_back(idle_thread); 173 | let switch_to_sp = &mut pending_list.front_mut().unwrap().stack_pointer as *mut u32; 174 | unsafe { request_switch(cs, null_mut(), switch_to_sp) }; 175 | }); 176 | } 177 | 178 | pub fn schedule() { 179 | // TODO: currently it is a simple time slice based scheduling, should upgrade to a more advanced algorithm 180 | cortex_m::interrupt::free(|cs: &CriticalSection| { 181 | let mut pending_list = cortex_m::interrupt::Mutex::borrow(&PENDING_LIST, cs).borrow_mut(); 182 | if pending_list.len() >= 2 && !cortex_m::peripheral::SCB::is_pendsv_pending() { 183 | let switch_from_thread = pending_list.pop_front().unwrap(); 184 | pending_list.push_back(switch_from_thread); 185 | let switch_from_sp = &mut pending_list.back_mut().unwrap().stack_pointer as *mut u32; 186 | let switch_to_sp = &mut pending_list.front_mut().unwrap().stack_pointer as *mut u32; 187 | unsafe { request_switch(cs, switch_from_sp, switch_to_sp) }; 188 | } 189 | }); 190 | } 191 | 192 | pub fn spawn(entry: ThreadEntryFunction, param: Address, stack_size: usize) { 193 | let thread = SimpleThread::new(entry, param, stack_size); 194 | cortex_m::interrupt::free(|cs: &CriticalSection| { 195 | let mut pending_list = cortex_m::interrupt::Mutex::borrow(&PENDING_LIST, cs).borrow_mut(); 196 | pending_list.push_back(thread); 197 | }); 198 | } 199 | 200 | #[exception] 201 | unsafe fn SysTick() { 202 | schedule() 203 | } 204 | 205 | pub mod sync; 206 | #[exception] 207 | unsafe fn SVCall() { 208 | let mut stack_frame: *const ExceptionFrame; 209 | // first we need to get the svc parameter 210 | // we need to get the exception_stack_frame out of the process stack frame 211 | asm!("mrs {}, psp", out(reg) stack_frame); 212 | let caller_thread_pc = stack_frame.as_ref().unwrap().pc(); 213 | // `caller_thread_pc` is the address of next instruction to be executed after 214 | // retuning from exception handling, in thumb, svc is a 16 bit instruction (https://developer.arm.com/documentation/ddi0597/2023-12/Base-Instructions/SVC--Supervisor-Call-?lang=en#sa_c), 215 | // so the svc instruction's address is `caller_thread_pc - 2` 216 | // todo: what if the assembler decides to use A1 format for svc instruction? 217 | let svc_instruction: *const u16 = (caller_thread_pc - 2) as _; 218 | let _svc_parameter: u8 = (svc_instruction.as_ref().unwrap() & 0b1111_1111) as _; 219 | } 220 | --------------------------------------------------------------------------------