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}