esp_radio_rtos_driver/semaphore.rs
1//! Semaphores
2//!
3//! Semaphores are synchronization primitives that allow threads to coordinate their execution.
4//! They are used to control access to a shared resource by limiting the number of threads that can
5//! access it simultaneously.
6//!
7//! esp-radio sometimes mixes up semaphores and mutexes (FreeRTOS allows this), so this crate
8//! exposes a single interface to work with both.
9//!
10//! ## Implementation
11//!
12//! Implement the `SemaphoreImplementation` trait for an object, and use the
13//! `register_semaphore_implementation` to register that implementation for esp-radio.
14//!
15//! See the [`SemaphoreImplementation`] documentation for more information.
16//!
17//! ## Usage
18//!
19//! Users should use [`SemaphoreHandle`] to interact with semaphores created by the driver
20//! implementation. Use [`SemaphoreKind`] to specify the type of semaphore or mutex to create.
21//!
22//! > Note that the only expected user of this crate is esp-radio.
23
24use core::ptr::NonNull;
25
26/// Pointer to an opaque semaphore created by the driver implementation.
27pub type SemaphorePtr = NonNull<()>;
28
29/// The type of semaphore or mutex to create.
30pub enum SemaphoreKind {
31 /// Counting semaphore.
32 Counting { max: u32, initial: u32 },
33
34 /// Non-recursive mutex.
35 Mutex,
36
37 /// Recursive mutex.
38 RecursiveMutex,
39}
40
41unsafe extern "Rust" {
42 fn esp_rtos_semaphore_create(kind: SemaphoreKind) -> SemaphorePtr;
43 fn esp_rtos_semaphore_delete(semaphore: SemaphorePtr);
44
45 fn esp_rtos_semaphore_take(semaphore: SemaphorePtr, timeout_us: Option<u32>) -> bool;
46 fn esp_rtos_semaphore_give(semaphore: SemaphorePtr) -> bool;
47 fn esp_rtos_semaphore_try_give_from_isr(
48 semaphore: SemaphorePtr,
49 higher_prio_task_waken: Option<&mut bool>,
50 ) -> bool;
51 fn esp_rtos_semaphore_current_count(semaphore: SemaphorePtr) -> u32;
52
53 fn esp_rtos_semaphore_try_take(semaphore: SemaphorePtr) -> bool;
54 fn esp_rtos_semaphore_try_take_from_isr(
55 semaphore: SemaphorePtr,
56 higher_prio_task_waken: Option<&mut bool>,
57 ) -> bool;
58}
59
60/// A semaphore primitive.
61///
62/// The following snippet demonstrates the boilerplate necessary to implement a semaphore using the
63/// `SemaphoreImplementation` trait:
64///
65/// ```rust,no_run
66/// use esp_radio_rtos_driver::{
67/// register_semaphore_implementation,
68/// semaphore::{SemaphoreImplementation, SemaphoreKind, SemaphorePtr},
69/// };
70///
71/// struct MySemaphore {
72/// // Semaphore implementation details
73/// }
74///
75/// impl SemaphoreImplementation for MySemaphore {
76/// fn create(kind: SemaphoreKind) -> SemaphorePtr {
77/// unimplemented!()
78/// }
79///
80/// unsafe fn delete(semaphore: SemaphorePtr) {
81/// unimplemented!()
82/// }
83///
84/// unsafe fn take(semaphore: SemaphorePtr, timeout_us: Option<u32>) -> bool {
85/// unimplemented!()
86/// }
87///
88/// unsafe fn give(semaphore: SemaphorePtr) -> bool {
89/// unimplemented!()
90/// }
91///
92/// unsafe fn try_give_from_isr(
93/// semaphore: SemaphorePtr,
94/// higher_prio_task_waken: Option<&mut bool>,
95/// ) -> bool {
96/// unimplemented!()
97/// }
98///
99/// unsafe fn current_count(semaphore: SemaphorePtr) -> u32 {
100/// unimplemented!()
101/// }
102///
103/// unsafe fn try_take(semaphore: SemaphorePtr) -> bool {
104/// unimplemented!()
105/// }
106///
107/// unsafe fn try_take_from_isr(
108/// semaphore: SemaphorePtr,
109/// higher_prio_task_waken: Option<&mut bool>,
110/// ) -> bool {
111/// unimplemented!()
112/// }
113/// }
114///
115/// register_semaphore_implementation!(MySemaphore);
116/// ```
117pub trait SemaphoreImplementation {
118 /// Creates a new semaphore instance.
119 ///
120 /// `kind` specifies the type of semaphore to create.
121 ///
122 /// - `SemaphoreKind::Counting` should create counting, non-recursive semaphores/mutexes.
123 /// - `SemaphoreKind::RecursiveMutex` should create recursive mutexes.
124 fn create(kind: SemaphoreKind) -> SemaphorePtr;
125
126 /// Deletes a semaphore instance.
127 ///
128 /// # Safety
129 ///
130 /// `semaphore` must be a pointer returned from [`Self::create`].
131 unsafe fn delete(semaphore: SemaphorePtr);
132
133 /// Increments the semaphore's counter.
134 ///
135 /// If a timeout is given, this function should block until either a semaphore could be taken,
136 /// or the timeout has been reached. If no timeout is specified, the function should block
137 /// indefinitely.
138 ///
139 /// Recursive mutexes can be repeatedly taken by the same task.
140 ///
141 /// The timeout is specified in microseconds.
142 ///
143 /// This function returns `true` if the semaphore was taken, `false` if the timeout was reached.
144 ///
145 /// # Safety
146 ///
147 /// `semaphore` must be a pointer returned from [`Self::create`].
148 unsafe fn take(semaphore: SemaphorePtr, timeout_us: Option<u32>) -> bool;
149
150 /// Increments the semaphore's counter.
151 ///
152 /// This function returns `true` if the semaphore was given, `false` if the counter is at
153 /// its maximum.
154 ///
155 /// Recursive mutexes can not be given by a task other than the one that first locked it.
156 ///
157 /// # Safety
158 ///
159 /// `semaphore` must be a pointer returned from [`Self::create`].
160 unsafe fn give(semaphore: SemaphorePtr) -> bool;
161
162 /// Attempts to increment the semaphore's counter from an ISR.
163 ///
164 /// This function returns `true` if the semaphore was given, `false` if the counter is at
165 /// its maximum.
166 ///
167 /// The `higher_prio_task_waken` parameter is an optional mutable reference to a boolean flag.
168 /// If the flag is `Some`, the implementation may set it to `true` to request a context switch.
169 ///
170 /// # Safety
171 ///
172 /// `semaphore` must be a pointer returned from [`Self::create`].
173 unsafe fn try_give_from_isr(
174 semaphore: SemaphorePtr,
175 higher_prio_task_waken: Option<&mut bool>,
176 ) -> bool;
177
178 /// Returns the semaphore's current counter value.
179 ///
180 /// # Safety
181 ///
182 /// `semaphore` must be a pointer returned from [`Self::create`].
183 unsafe fn current_count(semaphore: SemaphorePtr) -> u32;
184
185 /// Attempts to decrement the semaphore's counter.
186 ///
187 /// If the counter is zero, this function must immediately return `false`.
188 ///
189 /// # Safety
190 ///
191 /// `semaphore` must be a pointer returned from [`Self::create`].
192 unsafe fn try_take(semaphore: SemaphorePtr) -> bool;
193
194 /// Attempts to decrement the semaphore's counter from an ISR.
195 ///
196 /// If the counter is zero, this function must immediately return `false`.
197 ///
198 /// The `higher_prio_task_waken` parameter is an optional mutable reference to a boolean flag.
199 /// If the flag is `Some`, the implementation may set it to `true` to request a context switch.
200 ///
201 /// # Safety
202 ///
203 /// `semaphore` must be a pointer returned from [`Self::create`].
204 unsafe fn try_take_from_isr(
205 semaphore: SemaphorePtr,
206 higher_prio_task_waken: Option<&mut bool>,
207 ) -> bool;
208}
209
210#[macro_export]
211macro_rules! register_semaphore_implementation {
212 ($t: ty) => {
213 #[unsafe(no_mangle)]
214 #[inline]
215 fn esp_rtos_semaphore_create(
216 kind: $crate::semaphore::SemaphoreKind,
217 ) -> $crate::semaphore::SemaphorePtr {
218 <$t as $crate::semaphore::SemaphoreImplementation>::create(kind)
219 }
220
221 #[unsafe(no_mangle)]
222 #[inline]
223 fn esp_rtos_semaphore_delete(semaphore: $crate::semaphore::SemaphorePtr) {
224 unsafe { <$t as $crate::semaphore::SemaphoreImplementation>::delete(semaphore) }
225 }
226
227 #[unsafe(no_mangle)]
228 #[inline]
229 fn esp_rtos_semaphore_take(
230 semaphore: $crate::semaphore::SemaphorePtr,
231 timeout_us: Option<u32>,
232 ) -> bool {
233 unsafe {
234 <$t as $crate::semaphore::SemaphoreImplementation>::take(semaphore, timeout_us)
235 }
236 }
237
238 #[unsafe(no_mangle)]
239 #[inline]
240 fn esp_rtos_semaphore_give(semaphore: $crate::semaphore::SemaphorePtr) -> bool {
241 unsafe { <$t as $crate::semaphore::SemaphoreImplementation>::give(semaphore) }
242 }
243
244 #[unsafe(no_mangle)]
245 #[inline]
246 fn esp_rtos_semaphore_try_give_from_isr(
247 semaphore: $crate::semaphore::SemaphorePtr,
248 higher_prio_task_waken: Option<&mut bool>,
249 ) -> bool {
250 unsafe {
251 <$t as $crate::semaphore::SemaphoreImplementation>::try_give_from_isr(
252 semaphore,
253 higher_prio_task_waken,
254 )
255 }
256 }
257
258 #[unsafe(no_mangle)]
259 #[inline]
260 fn esp_rtos_semaphore_current_count(semaphore: $crate::semaphore::SemaphorePtr) -> u32 {
261 unsafe { <$t as $crate::semaphore::SemaphoreImplementation>::current_count(semaphore) }
262 }
263
264 #[unsafe(no_mangle)]
265 #[inline]
266 fn esp_rtos_semaphore_try_take(semaphore: $crate::semaphore::SemaphorePtr) -> bool {
267 unsafe { <$t as $crate::semaphore::SemaphoreImplementation>::try_take(semaphore) }
268 }
269
270 #[unsafe(no_mangle)]
271 #[inline]
272 fn esp_rtos_semaphore_try_take_from_isr(
273 semaphore: $crate::semaphore::SemaphorePtr,
274 higher_prio_task_waken: Option<&mut bool>,
275 ) -> bool {
276 unsafe {
277 <$t as $crate::semaphore::SemaphoreImplementation>::try_take_from_isr(
278 semaphore,
279 higher_prio_task_waken,
280 )
281 }
282 }
283 };
284}
285
286/// Semaphore handle.
287///
288/// This handle is used to interact with semaphores created by the driver implementation.
289#[repr(transparent)]
290pub struct SemaphoreHandle(SemaphorePtr);
291impl SemaphoreHandle {
292 /// Creates a new semaphore instance.
293 ///
294 /// `kind` specifies the type of semaphore to create.
295 ///
296 /// - Use `SemaphoreKind::Counting` to create counting semaphores and non-recursive mutexes.
297 /// - Use `SemaphoreKind::RecursiveMutex` to create recursive mutexes.
298 #[inline]
299 pub fn new(kind: SemaphoreKind) -> Self {
300 let ptr = unsafe { esp_rtos_semaphore_create(kind) };
301 Self(ptr)
302 }
303
304 /// Converts this object into a pointer without dropping it.
305 #[inline]
306 pub fn leak(self) -> SemaphorePtr {
307 let ptr = self.0;
308 core::mem::forget(self);
309 ptr
310 }
311
312 /// Recovers the object from a leaked pointer.
313 ///
314 /// # Safety
315 ///
316 /// - The caller must only use pointers created using [`Self::leak`].
317 /// - The caller must ensure the pointer is not shared.
318 #[inline]
319 pub unsafe fn from_ptr(ptr: SemaphorePtr) -> Self {
320 Self(ptr)
321 }
322
323 /// Creates a reference to this object from a leaked pointer.
324 ///
325 /// This function is used in the esp-radio code to interact with the semaphore.
326 ///
327 /// # Safety
328 ///
329 /// - The caller must only use pointers created using [`Self::leak`].
330 #[inline]
331 pub unsafe fn ref_from_ptr(ptr: &SemaphorePtr) -> &Self {
332 unsafe { core::mem::transmute(ptr) }
333 }
334
335 /// Increments the semaphore's counter.
336 ///
337 /// If a timeout is given, this function blocks until either a semaphore could be taken, or the
338 /// timeout has been reached. If no timeout is given, this function blocks until the operation
339 /// succeeds.
340 ///
341 /// This function returns `true` if the semaphore was taken, `false` if the timeout was reached.
342 #[inline]
343 pub fn take(&self, timeout_us: Option<u32>) -> bool {
344 unsafe { esp_rtos_semaphore_take(self.0, timeout_us) }
345 }
346
347 /// Increments the semaphore's counter.
348 ///
349 /// This function returns `true` if the semaphore was given, `false` if the counter is at its
350 /// maximum.
351 #[inline]
352 pub fn give(&self) -> bool {
353 unsafe { esp_rtos_semaphore_give(self.0) }
354 }
355
356 /// Attempts to increment the semaphore's counter from an ISR.
357 ///
358 /// If the counter is at its maximum, this function returns `false`.
359 ///
360 /// If the flag is `Some`, the implementation may set it to `true` to request a context switch.
361 #[inline]
362 pub fn try_give_from_isr(&self, higher_prio_task_waken: Option<&mut bool>) -> bool {
363 unsafe { esp_rtos_semaphore_try_give_from_isr(self.0, higher_prio_task_waken) }
364 }
365
366 /// Returns the current counter value.
367 #[inline]
368 pub fn current_count(&self) -> u32 {
369 unsafe { esp_rtos_semaphore_current_count(self.0) }
370 }
371
372 /// Attempts to decrement the semaphore's counter.
373 ///
374 /// If the counter is zero, this function returns `false`.
375 #[inline]
376 pub fn try_take(&self) -> bool {
377 unsafe { esp_rtos_semaphore_try_take(self.0) }
378 }
379
380 /// Attempts to decrement the semaphore's counter from an ISR.
381 ///
382 /// If the counter is zero, this function returns `false`.
383 ///
384 /// If a higher priority task is woken up by this operation, the `higher_prio_task_waken` flag
385 /// is set to `true`.
386 #[inline]
387 pub fn try_take_from_isr(&self, higher_prio_task_waken: Option<&mut bool>) -> bool {
388 unsafe { esp_rtos_semaphore_try_take_from_isr(self.0, higher_prio_task_waken) }
389 }
390}
391
392impl Drop for SemaphoreHandle {
393 #[inline]
394 fn drop(&mut self) {
395 unsafe { esp_rtos_semaphore_delete(self.0) };
396 }
397}