esp_rtos/
semaphore.rs

1//! Semaphores and mutexes.
2//!
3//! This module provides the [`Semaphore`] type, which implements counting semaphores and mutexes.
4
5use esp_hal::{system::Cpu, time::Instant};
6use esp_sync::NonReentrantMutex;
7
8use crate::{
9    SCHEDULER,
10    run_queue::Priority,
11    task::{TaskExt, TaskPtr},
12    wait_queue::WaitQueue,
13};
14
15enum SemaphoreInner {
16    Counting {
17        current: u32,
18        max: u32,
19        waiting: WaitQueue,
20    },
21    Mutex {
22        recursive: bool,
23        owner: Option<TaskPtr>,
24        original_priority: Priority,
25        lock_counter: u32,
26        waiting: WaitQueue,
27    },
28}
29
30impl SemaphoreInner {
31    fn try_take(&mut self) -> bool {
32        match self {
33            SemaphoreInner::Counting { current, .. } => {
34                if *current > 0 {
35                    *current -= 1;
36                    true
37                } else {
38                    false
39                }
40            }
41            SemaphoreInner::Mutex {
42                recursive,
43                owner,
44                lock_counter,
45                original_priority,
46                ..
47            } => {
48                SCHEDULER.with(|scheduler| {
49                    let current = scheduler.current_task(Cpu::current());
50                    if let Some(owner) = owner {
51                        if *owner == current && *recursive {
52                            *lock_counter += 1;
53                            true
54                        } else {
55                            // We can't lock the mutex. Make sure the mutex holder has a high enough
56                            // priority to avoid priority inversion.
57                            let current_priority = current.priority(&mut scheduler.run_queue);
58                            if owner.priority(&mut scheduler.run_queue) < current_priority {
59                                owner.set_priority(&mut scheduler.run_queue, current_priority);
60                                scheduler.resume_task(*owner);
61                            }
62                            false
63                        }
64                    } else {
65                        *owner = Some(current);
66                        *lock_counter += 1;
67                        *original_priority = current.priority(&mut scheduler.run_queue);
68                        true
69                    }
70                })
71            }
72        }
73    }
74
75    fn try_give(&mut self) -> bool {
76        match self {
77            SemaphoreInner::Counting { current, max, .. } => {
78                if *current < *max {
79                    *current += 1;
80                    true
81                } else {
82                    false
83                }
84            }
85            SemaphoreInner::Mutex {
86                owner,
87                lock_counter,
88                original_priority,
89                ..
90            } => SCHEDULER.with(|scheduler| {
91                let current_cpu = Cpu::current() as usize;
92                let current = unwrap!(scheduler.per_cpu[current_cpu].current_task);
93
94                if *owner == Some(current) && *lock_counter > 0 {
95                    *lock_counter -= 1;
96                    if *lock_counter == 0
97                        && let Some(owner) = owner.take()
98                    {
99                        owner.set_priority(&mut scheduler.run_queue, *original_priority);
100                    }
101                    true
102                } else {
103                    false
104                }
105            }),
106        }
107    }
108
109    fn current_count(&mut self) -> u32 {
110        match self {
111            SemaphoreInner::Counting { current, .. } => *current,
112            SemaphoreInner::Mutex { .. } => {
113                panic!("RecursiveMutex does not support current_count")
114            }
115        }
116    }
117
118    fn wait_with_deadline(&mut self, deadline: Instant) {
119        trace!("Semaphore wait_with_deadline - {:?}", deadline);
120        match self {
121            SemaphoreInner::Counting { waiting, .. } => waiting.wait_with_deadline(deadline),
122            SemaphoreInner::Mutex { waiting, .. } => waiting.wait_with_deadline(deadline),
123        }
124    }
125
126    fn notify(&mut self) {
127        trace!("Semaphore notify");
128        match self {
129            SemaphoreInner::Counting { waiting, .. } => waiting.notify(),
130            SemaphoreInner::Mutex { waiting, .. } => waiting.notify(),
131        }
132    }
133}
134
135/// Semaphore and mutex primitives.
136pub struct Semaphore {
137    inner: NonReentrantMutex<SemaphoreInner>,
138}
139
140impl Semaphore {
141    /// Create a new counting semaphore.
142    pub const fn new_counting(initial: u32, max: u32) -> Self {
143        Semaphore {
144            inner: NonReentrantMutex::new(SemaphoreInner::Counting {
145                current: initial,
146                max,
147                waiting: WaitQueue::new(),
148            }),
149        }
150    }
151
152    /// Create a new mutex.
153    ///
154    /// If `recursive` is true, the mutex can be locked multiple times by the same task.
155    pub const fn new_mutex(recursive: bool) -> Self {
156        Semaphore {
157            inner: NonReentrantMutex::new(SemaphoreInner::Mutex {
158                recursive,
159                owner: None,
160                lock_counter: 0,
161                original_priority: Priority::new(0),
162                waiting: WaitQueue::new(),
163            }),
164        }
165    }
166
167    /// Try to take the semaphore.
168    ///
169    /// This is a non-blocking operation.
170    pub fn try_take(&self) -> bool {
171        self.inner.with(|sem| sem.try_take())
172    }
173
174    /// Take the semaphore.
175    ///
176    /// This is a blocking operation.
177    ///
178    /// If the semaphore is already taken, the task will be blocked until the semaphore is released.
179    /// Recursive mutexes can be locked multiple times by the mutex owner task.
180    pub fn take(&self, timeout_us: Option<u32>) -> bool {
181        if crate::with_deadline(timeout_us, |deadline| {
182            self.inner.with(|sem| {
183                if sem.try_take() {
184                    true
185                } else {
186                    // The task will go to sleep when the above critical section is released.
187                    sem.wait_with_deadline(deadline);
188                    false
189                }
190            })
191        }) {
192            debug!("Semaphore - take - success");
193            true
194        } else {
195            debug!("Semaphore - take - timed out");
196            false
197        }
198    }
199
200    /// Return the current count of the semaphore.
201    pub fn current_count(&self) -> u32 {
202        self.inner.with(|sem| sem.current_count())
203    }
204
205    /// Unlock the semaphore.
206    pub fn give(&self) -> bool {
207        self.inner.with(|sem| {
208            if sem.try_give() {
209                sem.notify();
210                true
211            } else {
212                false
213            }
214        })
215    }
216}