cortexm_threads/lib.rs
1//!
2//! A simple library for context-switching on ARM Cortex-M ( 0, 0+, 3, 4, 4F ) micro-processors
3//!
4//! Supports pre-emptive, priority based switching
5//!
6//! This project is meant for learning and should be used only at the user's risk. For practical and mature
7//! rust alternatives, see [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust)
8//!
9//! Example:
10//!
11//! See [example crate on github](https://github.com/n-k/cortexm-threads/tree/master/example_crates/qemu-m4)
12//! ```
13//! #![no_std]
14//! #![no_main]
15//!
16//! extern crate panic_semihosting;
17//! use cortex_m::peripheral::syst::SystClkSource;
18//! use cortex_m_rt::{entry, exception};
19//! use cortex_m_semihosting::{hprintln};
20//! use cortexm_threads::{init, create_thread, create_thread_with_config, sleep};
21//!
22//! #[entry]
23//! fn main() -> ! {
24//! let cp = cortex_m::Peripherals::take().unwrap();
25//! let mut syst = cp.SYST;
26//! syst.set_clock_source(SystClkSource::Core);
27//! syst.set_reload(80_000);
28//! syst.enable_counter();
29//! syst.enable_interrupt();
30//!
31//! let mut stack1 = [0xDEADBEEF; 512];
32//! let mut stack2 = [0xDEADBEEF; 512];
33//! let _ = create_thread(
34//! &mut stack1,
35//! || {
36//! loop {
37//! let _ = hprintln!("in task 1 !!");
38//! sleep(50); // sleep for 50 ticks
39//! }
40//! });
41//! let _ = create_thread_with_config(
42//! &mut stack2,
43//! || {
44//! loop {
45//! let _ = hprintln!("in task 2 !!");
46//! sleep(30); // sleep for 30 ticks
47//! }
48//! },
49//! 0x01, // priority, higher numeric value means higher priority
50//! true // privileged thread
51//! );
52//! init();
53//! }
54//! ```
55#![no_std]
56
57use core::ptr;
58
59/// Returned by create_thread or create_thread_with_config as Err(ERR_TOO_MANY_THREADS)
60/// if creating a thread will cause more than 32 threads to exist (inclusing the idle thread)
61/// created by this library
62pub static ERR_TOO_MANY_THREADS: u8 = 0x01;
63/// Returned by create_thread or create_thread_with_config as Err(ERR_STACK_TOO_SMALL)
64/// if array to be used as stack area is too small. Smallest size is 32 u32's
65pub static ERR_STACK_TOO_SMALL: u8 = 0x02;
66/// Returned by create_thread or create_thread_with_config as Err(ERR_NO_CREATE_PRIV)
67/// if called from an unprivileged thread
68pub static ERR_NO_CREATE_PRIV: u8 = 0x03;
69
70/// Context switching and threads' state
71#[repr(C)]
72struct ThreadsState {
73 // start fields used in assembly, do not change their order
74 curr: usize,
75 next: usize,
76 // end fields used in assembly
77 inited: bool,
78 idx: usize,
79 add_idx: usize,
80 threads: [ThreadControlBlock; 32],
81}
82
83/// Thread status
84#[repr(C)]
85#[derive(Clone, Copy, PartialEq, Eq)]
86enum ThreadStatus {
87 Idle,
88 Sleeping,
89}
90
91/// A single thread's state
92#[repr(C)]
93#[derive(Clone, Copy)]
94struct ThreadControlBlock {
95 // start fields used in assembly, do not reorder them
96 /// current stack pointer of this thread
97 sp: u32,
98 privileged: u32, // make it a word, assembly is easier. FIXME
99 // end fields used in assembly
100 priority: u8,
101 status: ThreadStatus,
102 sleep_ticks: u32,
103}
104
105// GLOBALS:
106#[no_mangle]
107static mut __CORTEXM_THREADS_GLOBAL_PTR: u32 = 0;
108static mut __CORTEXM_THREADS_GLOBAL: ThreadsState = ThreadsState {
109 curr: 0,
110 next: 0,
111 inited: false,
112 idx: 0,
113 add_idx: 1,
114 threads: [ThreadControlBlock {
115 sp: 0,
116 status: ThreadStatus::Idle,
117 priority: 0,
118 privileged: 0,
119 sleep_ticks: 0,
120 }; 32],
121};
122// end GLOBALS
123
124// functions defined in assembly
125extern "C" {
126 fn __CORTEXM_THREADS_cpsid();
127 fn __CORTEXM_THREADS_cpsie();
128 fn __CORTEXM_THREADS_wfe();
129}
130
131#[inline(always)]
132pub fn enable_threads() {
133 unsafe {
134 __CORTEXM_THREADS_cpsie();
135 }
136}
137
138#[inline(always)]
139pub fn disable_threads() {
140 unsafe {
141 __CORTEXM_THREADS_cpsid();
142 }
143}
144
145/// Initialize the switcher system
146pub fn init() -> ! {
147 unsafe {
148 disable_threads();
149 let ptr: usize = core::intrinsics::transmute(&__CORTEXM_THREADS_GLOBAL);
150 __CORTEXM_THREADS_GLOBAL_PTR = ptr as u32;
151 enable_threads();
152 let mut idle_stack = [0xDEADBEEF; 64];
153 match create_tcb(
154 &mut idle_stack,
155 || loop {
156 __CORTEXM_THREADS_wfe();
157 },
158 0xff,
159 false,
160 ) {
161 Ok(tcb) => {
162 insert_tcb(0, tcb);
163 }
164 _ => panic!("Could not create idle thread"),
165 }
166 __CORTEXM_THREADS_GLOBAL.inited = true;
167 SysTick();
168 loop {
169 __CORTEXM_THREADS_wfe();
170 }
171 }
172}
173
174/// Create a thread with default configuration (lowest priority, unprivileged).
175///
176/// # Arguments
177/// * stack: mut array of u32's to be used as stack area
178/// * handler_fn: function to execute in created thread
179///
180/// # Example
181/// ```
182/// let mut stack1 = [0xDEADBEEF; 512];
183/// let _ = create_thread(
184/// &mut stack1,
185/// || {
186/// loop {
187/// let _ = hprintln!("in task 1 !!");
188/// sleep(50);
189/// }
190/// });
191///```
192pub fn create_thread(stack: &mut [u32], handler_fn: fn() -> !) -> Result<(), u8> {
193 create_thread_with_config(stack, handler_fn, 0x00, false)
194}
195
196/// Create a thread with explicit configuration
197/// # Arguments
198/// * stack: mut array of u32's to be used as stack area
199/// * handler_fn: function to execute in created thread
200/// * priority: higher numeric value means higher priority
201/// * privileged: run thread in privileged mode
202///
203/// # Example
204/// ```
205/// let mut stack1 = [0xDEADBEEF; 512];
206/// let _ = create_thread_with_config(
207/// &mut stack1,
208/// || {
209/// loop {
210/// let _ = hprintln!("in task 1 !!");
211/// sleep(50);
212/// }
213/// },
214/// 0xff, // priority, this is the maximum, higher number means higher priority
215/// true // this thread will be run in privileged mode
216/// );
217///```
218/// FIXME: take stack memory as a vec (arrayvec?, smallvec?) instead of &[]
219pub fn create_thread_with_config(
220 stack: &mut [u32],
221 handler_fn: fn() -> !,
222 priority: u8,
223 priviliged: bool,
224) -> Result<(), u8> {
225 unsafe {
226 disable_threads();
227 let handler = &mut __CORTEXM_THREADS_GLOBAL;
228 if handler.add_idx >= handler.threads.len() {
229 return Err(ERR_TOO_MANY_THREADS);
230 }
231 if handler.inited && handler.threads[handler.idx].privileged == 0 {
232 return Err(ERR_NO_CREATE_PRIV);
233 }
234 match create_tcb(stack, handler_fn, priority, priviliged) {
235 Ok(tcb) => {
236 insert_tcb(handler.add_idx, tcb);
237 handler.add_idx = handler.add_idx + 1;
238 }
239 Err(e) => {
240 enable_threads();
241 return Err(e);
242 }
243 }
244 enable_threads();
245 Ok(())
246 }
247}
248
249/// Handle a tick event. Typically, this would be called as SysTick handler, but can be
250/// called anytime. Call from thread handler code to yield and switch context.
251///
252/// * updates sleep_ticks field in sleeping threads, decreses by 1
253/// * if a sleeping thread has sleep_ticks == 0, wake it, i.e., change status to idle
254/// * find next thread to schedule
255/// * if context switch is required, will pend the PendSV exception, which will do the actual thread switching
256#[no_mangle]
257pub extern "C" fn SysTick() {
258 disable_threads();
259 let handler = unsafe { &mut __CORTEXM_THREADS_GLOBAL };
260 if handler.inited {
261 if handler.curr == handler.next {
262 // schedule a thread to be run
263 handler.idx = get_next_thread_idx();
264 unsafe {
265 handler.next = core::intrinsics::transmute(&handler.threads[handler.idx]);
266 }
267 }
268 if handler.curr != handler.next {
269 unsafe {
270 let pend = ptr::read_volatile(0xE000ED04 as *const u32);
271 ptr::write_volatile(0xE000ED04 as *mut u32, pend | 1 << 28);
272 }
273 }
274 }
275 enable_threads();
276}
277
278/// Get id of current thread
279pub fn get_thread_id() -> usize {
280 let handler = unsafe { &mut __CORTEXM_THREADS_GLOBAL };
281 handler.idx
282}
283
284/// Make current thread sleep for `ticks` ticks. Current thread will be put in `Sleeping`
285/// state and another thread will be scheduled immediately. Current thread will not be considered
286/// for scheduling until `tick()` is called at least `tick` times.
287///
288/// # Example
289/// ```
290/// let mut stack1 = [0xDEADBEEF; 512];
291/// let _ = create_thread(
292/// &mut stack1,
293/// || {
294/// loop {
295/// let _ = hprintln!("in task 1 !!");
296/// sleep(50);
297/// }
298/// });
299/// ```
300pub fn sleep(ticks: u32) {
301 let handler = unsafe { &mut __CORTEXM_THREADS_GLOBAL };
302 if handler.idx > 0 {
303 handler.threads[handler.idx].status = ThreadStatus::Sleeping;
304 handler.threads[handler.idx].sleep_ticks = ticks;
305 // schedule another thread
306 SysTick();
307 }
308}
309
310fn get_next_thread_idx() -> usize {
311 let handler = unsafe { &mut __CORTEXM_THREADS_GLOBAL };
312 if handler.add_idx <= 1 {
313 // no user threads, schedule idle thread
314 return 0;
315 }
316 // user threads exist
317 // update sleeping threads
318 for i in 1..handler.add_idx {
319 if handler.threads[i].status == ThreadStatus::Sleeping {
320 if handler.threads[i].sleep_ticks > 0 {
321 handler.threads[i].sleep_ticks = handler.threads[i].sleep_ticks - 1;
322 } else {
323 handler.threads[i].status = ThreadStatus::Idle;
324 }
325 }
326 }
327 match handler
328 .threads
329 .iter()
330 .enumerate()
331 .filter(|&(idx, x)| idx > 0 && idx < handler.add_idx && x.status != ThreadStatus::Sleeping)
332 .max_by(|&(_, a), &(_, b)| a.priority.cmp(&b.priority))
333 {
334 Some((idx, _)) => idx,
335 _ => 0,
336 }
337}
338
339fn create_tcb(
340 stack: &mut [u32],
341 handler: fn() -> !,
342 priority: u8,
343 priviliged: bool,
344) -> Result<ThreadControlBlock, u8> {
345 if stack.len() < 32 {
346 return Err(ERR_STACK_TOO_SMALL);
347 }
348 let idx = stack.len() - 1;
349 stack[idx] = 1 << 24; // xPSR
350 let pc: usize = unsafe { core::intrinsics::transmute(handler as *const fn()) };
351 stack[idx - 1] = pc as u32; // PC
352 stack[idx - 2] = 0xFFFFFFFD; // LR
353 stack[idx - 3] = 0xCCCCCCCC; // R12
354 stack[idx - 4] = 0x33333333; // R3
355 stack[idx - 5] = 0x22222222; // R2
356 stack[idx - 6] = 0x11111111; // R1
357 stack[idx - 7] = 0x00000000; // R0
358 // aditional regs
359 stack[idx - 08] = 0x77777777; // R7
360 stack[idx - 09] = 0x66666666; // R6
361 stack[idx - 10] = 0x55555555; // R5
362 stack[idx - 11] = 0x44444444; // R4
363 stack[idx - 12] = 0xBBBBBBBB; // R11
364 stack[idx - 13] = 0xAAAAAAAA; // R10
365 stack[idx - 14] = 0x99999999; // R9
366 stack[idx - 15] = 0x88888888; // R8
367 unsafe {
368 let sp: usize = core::intrinsics::transmute(&stack[stack.len() - 16]);
369 let tcb = ThreadControlBlock {
370 sp: sp as u32,
371 priority: priority,
372 privileged: if priviliged { 0x1 } else { 0x0 },
373 status: ThreadStatus::Idle,
374 sleep_ticks: 0,
375 };
376 Ok(tcb)
377 }
378}
379
380fn insert_tcb(idx: usize, tcb: ThreadControlBlock) {
381 unsafe {
382 let handler = &mut __CORTEXM_THREADS_GLOBAL;
383 handler.threads[idx] = tcb;
384 }
385}