zephyr/thread.rs
1//! Thread support.
2//!
3//! Implement the friendly Thread types used by the `zephyr::thread` proc macro to declare new
4//! threads.
5//!
6//! This is intended to be completely usable without alloc, while still allow threads to be
7//! started with any arbitrary Send arguments. Threads can be joined, and reused after they have
8//! exited. The model intentionally tries to be similar to how async tasks work in something like
9//! Embassy, but some changes due to the different semantics of Zephyr threads.
10
11use core::{
12 cell::UnsafeCell,
13 ffi::{c_int, c_void, CStr},
14 mem,
15 ptr::null_mut,
16 sync::atomic::Ordering,
17};
18
19use portable_atomic::AtomicU8;
20use zephyr_sys::{
21 k_thread, k_thread_create, k_thread_entry_t, k_thread_join, k_thread_name_set,
22 k_thread_priority_set, k_wakeup, z_thread_stack_element, ZR_STACK_ALIGN, ZR_STACK_RESERVED,
23};
24
25use crate::{
26 align::AlignAs,
27 error::to_result_void,
28 sys::{K_FOREVER, K_NO_WAIT},
29 time::{Forever, Timeout},
30};
31
32/// Adjust a given requested stack size up for the alignment. This is just the stack, and the
33/// reservation is explicitly included in the stack declaration below.
34pub const fn stack_len(size: usize) -> usize {
35 size.next_multiple_of(ZR_STACK_ALIGN)
36}
37
38/// States a Zephyr thread can be in.
39#[repr(u8)]
40pub enum ThreadState {
41 /// A non running thread, that is free.
42 Init,
43 /// An allocated thread. There is a ThreadHandle for this thread, but it has not been started.
44 Allocated,
45 /// A thread that is running, as far as we know. Termination is not checked unless demanded.
46 Running,
47}
48
49/// The holder of data that is to be shared with the target thread.
50///
51/// # Safety
52///
53/// The Option is kept in an UnsafeCell, and it's use governed by an atomic in the `TaskData`
54/// below. When the task is not initialized/not running, this should be set to None. It will be
55/// set to Some in a critical section during startup, where the critical section provides the
56/// barrier. Once the atomic is set to true, the thread owns this data.
57///
58/// The Send constraint force arguments passed to threads to be Send.
59pub struct InitData<T: Send>(pub UnsafeCell<Option<T>>);
60
61impl<T: Send> InitData<T> {
62 /// Construct new Shared init state.
63 pub const fn new() -> Self {
64 Self(UnsafeCell::new(None))
65 }
66}
67
68unsafe impl<T: Send> Sync for InitData<T> {}
69
70/// The static data associated with each thread. The stack is kept separate, as it is intended to
71/// go into an uninitialized linker section.
72pub struct ThreadData<T: Send> {
73 init: InitData<T>,
74 state: AtomicU8,
75 thread: Thread,
76}
77
78impl<T: Send> ThreadData<T> {
79 /// Construct new ThreadData.
80 pub const fn new() -> Self {
81 Self {
82 init: InitData::new(),
83 state: AtomicU8::new(ThreadState::Init as u8),
84 thread: unsafe { Thread::new() },
85 }
86 }
87
88 /// Acquire the thread, in preparation to run it.
89 pub fn acquire_old<const SIZE: usize>(
90 &'static self,
91 args: T,
92 stack: &'static ThreadStack<SIZE>,
93 entry: k_thread_entry_t,
94 ) {
95 critical_section::with(|_| {
96 // Relaxed is sufficient, as the critical section provides both synchronization and
97 // a memory barrier.
98 let old = self.state.load(Ordering::Relaxed);
99 if old != ThreadState::Init as u8 {
100 // TODO: This is where we should check for termination.
101 panic!("Attempt to use a thread that is already in use");
102 }
103 self.state
104 .store(ThreadState::Allocated as u8, Ordering::Relaxed);
105
106 let init = self.init.0.get();
107 unsafe {
108 *init = Some(args);
109 }
110 });
111
112 // For now, just directly start the thread. We'll want to delay this so that parameters
113 // (priority and/or flags) can be passed, as well as to have a handle to be able to join and
114 // restart threads.
115 let _tid = unsafe {
116 k_thread_create(
117 self.thread.0.get(),
118 stack.data.get() as *mut z_thread_stack_element,
119 stack.size(),
120 entry,
121 self.init.0.get() as *mut c_void,
122 null_mut(),
123 null_mut(),
124 0,
125 0,
126 K_NO_WAIT,
127 )
128 };
129 }
130
131 /// Acquire a thread from the pool of threads, panicing if the pool is exhausted.
132 pub fn acquire<const SIZE: usize>(
133 pool: &'static [Self],
134 stacks: &'static [ThreadStack<SIZE>],
135 args: T,
136 entry: k_thread_entry_t,
137 priority: c_int,
138 name: &'static CStr,
139 ) -> ReadyThread {
140 let id = Self::find_thread(pool);
141
142 let init = pool[id].init.0.get();
143 unsafe {
144 *init = Some(args);
145 }
146
147 // Create the thread in Zephyr, in a non-running state.
148 let tid = unsafe {
149 k_thread_create(
150 pool[id].thread.0.get(),
151 stacks[id].data.get() as *mut z_thread_stack_element,
152 SIZE,
153 entry,
154 pool[id].init.0.get() as *mut c_void,
155 null_mut(),
156 null_mut(),
157 priority,
158 0,
159 K_FOREVER,
160 )
161 };
162
163 // Set the name.
164 unsafe {
165 k_thread_name_set(tid, name.as_ptr());
166 }
167
168 ReadyThread { id: tid }
169 }
170
171 /// Scan the pool of threads, looking for an available thread.
172 ///
173 /// Returns the index of a newly allocated thread. The thread will be marked 'Allocated' after
174 /// this.
175 fn find_thread(pool: &'static [Self]) -> usize {
176 let id = critical_section::with(|_| {
177 for (id, thread) in pool.iter().enumerate() {
178 // Relaxed is sufficient, due to the critical section.
179 let old = thread.state.load(Ordering::Relaxed);
180
181 match old {
182 v if v == ThreadState::Init as u8 => {
183 // This is available. Mark as allocated and return from the closure.
184 thread
185 .state
186 .store(ThreadState::Allocated as u8, Ordering::Relaxed);
187 return Some(id);
188 }
189 v if v == ThreadState::Allocated as u8 => {
190 // Allocate threads haven't started, so aren't available.
191 }
192 v if v == ThreadState::Running as u8 => {
193 // A running thread might be available if it has terminated. We could
194 // improve performance here by not checking these until after the pool has
195 // been checked for Init threads.
196 if unsafe { k_thread_join(thread.thread.0.get(), K_NO_WAIT) } == 0 {
197 thread
198 .state
199 .store(ThreadState::Allocated as u8, Ordering::Relaxed);
200 return Some(id);
201 }
202 }
203 _ => unreachable!(),
204 }
205 }
206
207 None
208 });
209
210 if let Some(id) = id {
211 id
212 } else {
213 panic!("Attempt to use more threads than declared pool size");
214 }
215 }
216}
217
218/// A thread that has been set up and is ready to start.
219///
220/// Represents a thread that has been created, but not yet started.
221pub struct ReadyThread {
222 id: *mut k_thread,
223}
224
225impl ReadyThread {
226 /// Change the priority of this thread before starting it. The initial default priority was
227 /// determined by the declaration of the thread.
228 pub fn set_priority(&self, priority: c_int) {
229 // SAFETY: ReadyThread should only exist for valid created threads.
230 unsafe {
231 k_thread_priority_set(self.id, priority);
232 }
233 }
234
235 /// Start this thread.
236 pub fn start(self) -> RunningThread {
237 // SAFETY: ReadyThread should only exist for valid created threads.
238 unsafe {
239 // As per the docs, this should no longer be `k_thread_start`, but `k_wakeup` is fine
240 // these days.
241 k_wakeup(self.id);
242 }
243
244 RunningThread { id: self.id }
245 }
246}
247
248/// A thread that has been started.
249pub struct RunningThread {
250 id: *mut k_thread,
251}
252
253impl RunningThread {
254 /// Wait, with timeout, for this thread to finish executing.
255 ///
256 /// Will block until either the thread terminates, or the timeout occurrs.
257 pub fn join_timeout<T>(&self, timeout: T) -> crate::Result<()>
258 where
259 T: Into<Timeout>,
260 {
261 let timeout: Timeout = timeout.into();
262 let ret = unsafe { k_thread_join(self.id, timeout.0) };
263 to_result_void(ret)
264 }
265
266 /// Wait for this thread to finish executing.
267 ///
268 /// Will block until the thread has terminated.
269 ///
270 /// TODO: Allow a timeout?
271 /// TODO: Should we try to return a value?
272 pub fn join(&self) -> crate::Result<()> {
273 self.join_timeout(Forever)
274 }
275}
276
277/// A Zephyr stack declaration.
278///
279/// This isn't intended to be used directly, as it needs additional decoration about linker sections
280/// and such. Unlike the C declaration, the reservation is a separate field. As long as the SIZE
281/// is properly aligned, this should work without padding between the fields.
282///
283/// Generally, this should be placed in a noinit linker section to avoid having to initialize the
284/// memory.
285#[repr(C)]
286pub struct ThreadStack<const SIZE: usize> {
287 /// Align the stack itself according to the Kconfig determined alignment.
288 #[allow(dead_code)]
289 align: AlignAs<ZR_STACK_ALIGN>,
290 /// The data of the stack itself.
291 #[allow(dead_code)]
292 pub data: UnsafeCell<[z_thread_stack_element; SIZE]>,
293 /// Extra data, used by Zephyr.
294 #[allow(dead_code)]
295 extra: [z_thread_stack_element; ZR_STACK_RESERVED],
296}
297
298unsafe impl<const SIZE: usize> Sync for ThreadStack<SIZE> {}
299
300impl<const SIZE: usize> ThreadStack<SIZE> {
301 /// Construct a new ThreadStack
302 ///
303 /// # Safety
304 ///
305 /// This is unsafe as the memory remains uninitialized, and it is the responsibility of the
306 /// caller to use the stack correctly. The stack should be associated with a single thread.
307 pub const fn new() -> Self {
308 // SAFETY: Although this is declared as zeroed, the linker section actually used on the
309 // stack can be used to place it in no-init memory.
310 unsafe { mem::zeroed() }
311 }
312
313 /// Retrieve the size of this stack.
314 pub const fn size(&self) -> usize {
315 SIZE
316 }
317}
318
319/// A zephyr thread.
320///
321/// This declares a single k_thread in Zephyr.
322pub struct Thread(pub UnsafeCell<k_thread>);
323
324// Threads are "sort of" thread safe. But, this declaration is needed to be able to declare these
325// statically, and all generated versions will protect the thread with a critical section.
326unsafe impl Sync for Thread {}
327
328impl Thread {
329 /// Static allocation of a thread
330 ///
331 /// This makes the zero-initialized memory that can later be used as a thread.
332 ///
333 /// # Safety
334 ///
335 /// The caller is responsible for using operations such as `create` to construct the thread,
336 /// according to the underlying semantics of the Zephyr operations.
337 pub const unsafe fn new() -> Self {
338 // SAFETY: Zero initialized to match thread declarations in the C macros.
339 unsafe { mem::zeroed() }
340 }
341}