1 /*
2  * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * This file provides an abstract OS API for entering and exiting critical sections.
9  * It furthermore provides macros to define and initialize an optional spinlock
10  * if the used chip is a multi-core chip. If a single-core chip is used, just disabling interrupts
11  * is sufficient to guarantee consecutive, non-interrupted execution of a critical section.
12  * Hence, the spinlock is unneccessary and will be automatically ommitted by the macros.
13  */
14 #pragma once
15 
16 #include "freertos/FreeRTOS.h"
17 #include "spinlock.h"
18 
19 #ifdef __cplusplus
20 extern "C" {
21 #endif
22 
23 #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2
24 /**
25  * This macro also helps users switching between spinlock declarations/definitions for multi-/single core environments
26  * if the macros below aren't sufficient.
27  */
28 #define OS_SPINLOCK 1
29 #else
30 #define OS_SPINLOCK 0
31 #endif
32 
33 #if OS_SPINLOCK == 1
34 typedef spinlock_t esp_os_spinlock_t;
35 #endif
36 
37 /**
38  * Define and initialize a static (internal linking) lock for entering critical sections.
39  *
40  * Use this when all the critical sections are local inside a file.
41  * The lock will only be defined if built for a multi-core system, otherwise it is unnecessary.
42  *
43  * @note When using this macro, the critical section macros esp_os_enter_critical* and esp_os_exit_critical*
44  * MUST be used, otherwise normal functions would be passed an undefined variable when build for single-core systems.
45  *
46  * @param lock_name Variable name of the lock. This will later be used to reference the declared lock.
47  * @param optional_qualifiers Qualifiers such as DRAM_ATTR and other attributes. Can be omitted if no qualifiers are
48  *        required.
49  *
50  * Example usage:
51  * @code{c}
52  * ...
53  * #include "os/critical_section.h"
54  * ...
55  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
56  * ...
57  * esp_os_enter_critical(&my_lock);
58  * ...
59  * esp_os_exit_critical(&my_lock);
60  * @endcode
61  */
62 #if OS_SPINLOCK == 1
63 #define DEFINE_CRIT_SECTION_LOCK_STATIC(lock_name, optional_qualifiers...) static optional_qualifiers esp_os_spinlock_t lock_name = SPINLOCK_INITIALIZER
64 #else
65 #define DEFINE_CRIT_SECTION_LOCK_STATIC(lock_name, optional_qualifiers...)
66 #endif
67 
68 /**
69  * Define and initialize a non-static (external linking) lock for entering critical sections.
70  *
71  * Locks defined by this macro can be linked among object files but this rather exceptional.
72  * Prefer the static lock definition whenever possible.
73  * The lock will only be defined if built for a multi-core system, otherwise it is unnecessary.
74  *
75  * @note When using this macro, the critical section macros esp_os_enter_critical* and esp_os_exit_critical*
76  * MUST be used, otherwise normal functions would be passed an undefined variable when build for single-core systems.
77  *
78  * @param lock_name Variable name of the lock. This will later be used to reference the declared lock.
79  * @param optional_qualifiers Qualifiers such as DRAM_ATTR and other attributes. Can be omitted if no qualifiers are
80  *        required.
81  *
82  * Example usage:
83  * @code{c}
84  * ...
85  * #include "os/critical_section.h"
86  * ...
87  * DEFINE_CRIT_SECTION_LOCK(my_lock); // will have external linking (non-static)
88  * ...
89  * esp_os_enter_critical(&my_lock);
90  * ...
91  * esp_os_exit_critical(&my_lock);
92  * @endcode
93  */
94 #if OS_SPINLOCK == 1
95 #define DEFINE_CRIT_SECTION_LOCK(lock_name, optional_qualifiers...) optional_qualifiers esp_os_spinlock_t lock_name = SPINLOCK_INITIALIZER
96 #else
97 #define DEFINE_CRIT_SECTION_LOCK(lock_name, optional_qualifiers...)
98 #endif
99 
100 /**
101  * @brief This macro initializes a critical section lock at runtime.
102  *
103  * This macro basically creates a member of the initialization list, including the trailing comma.
104  * If the lock is unnecessary because the architecture is single-core, this macro will not do anything.
105  * This is incompatible with a lock created by DEFINE_CRIT_SECTION_LOCK_STATIC from above.
106  *
107  * @param lock_name Pointer to the lock.
108  *
109  * @note When using this macro, the critical section macros esp_os_enter_critical* and esp_os_exit_critical*
110  *       MUST be used, otherwise normal functions would be passed an undefined variable when build for single-core
111  *       systems.
112  *
113  * Example usage:
114  * @code{c}
115  * ...
116  * #include "os/critical_section.h"
117  * ...
118  * typedef struct protected_struct_t {
119  *     int member1;
120  *     DECLARE_CRIT_SECTION_LOCK_IN_STRUCT(my_lock)
121  *     int another_member;
122  * };
123  * ...
124  * protected_struct_t my_protected;
125  * INIT_CRIT_SECTION_LOCK_IN_STRUCT(&(my_protected.my_lock));
126  * };
127  * @endcode
128  */
129 #if OS_SPINLOCK == 1
130 #define INIT_CRIT_SECTION_LOCK_RUNTIME(lock_name) spinlock_initialize(lock_name)
131 #else
132 #define INIT_CRIT_SECTION_LOCK_RUNTIME(lock_name)
133 #endif
134 
135 /**
136  * @brief This macro declares a critical section lock as a member of a struct.
137  *
138  * The critical section lock member is only declared if built for multi-core systems, otherwise it is omitted.
139  *
140  * @note When using this macro, the critical section macros esp_os_enter_critical* and esp_os_exit_critical*
141  *       MUST be used, otherwise normal functions would be passed an undefined variable when build for single-core
142  *       systems.
143  * @note Do NOT add any semicolon after declaring the member with this macro.
144  *       The trailing semicolon is included in the macro, otherwise -Wpedantic would complain about
145  *       superfluous ";" if OS_SPINLOCK == 0.
146  *
147  * Example usage:
148  * @code{c}
149  * ...
150  * #include "os/critical_section.h"
151  * ...
152  * typedef struct protected_struct_t {
153  *     int member1;
154  *     DECLARE_CRIT_SECTION_LOCK_IN_STRUCT(my_lock) // no semicolon!
155  *     int another_member;
156  * };
157  * @endcode
158  */
159 #if OS_SPINLOCK == 1
160 #define DECLARE_CRIT_SECTION_LOCK_IN_STRUCT(lock_name) esp_os_spinlock_t lock_name;
161 #else
162 #define DECLARE_CRIT_SECTION_LOCK_IN_STRUCT(lock_name)
163 #endif
164 
165 /**
166  * @brief This macro initializes a critical section lock as a member of a struct when using an list initialization.
167  *        It has to be used together with \c DECLARE_CRIT_SECTION_LOCK_IN_STRUCT() to work.
168  *
169  * This macro basically creates a member of the initialization list, including the trailing comma.
170  * If the lock is unnecessary because the architecture is single-core, this macro will not do anything.
171  * This means that if \c lock_name is still a member of the struct, \c lock_name will be uninitialized.
172  * Hence, this macro has to be used together with \c DECLARE_CRIT_SECTION_LOCK_IN_STRUCT() to correctly to declare
173  * or omit the struct member \c lock_name.
174  *
175  * @param lock_name The field name of the lock inside the struct.
176  *
177  * @note When using this macro, the critical section macros esp_os_enter_critical* and esp_os_exit_critical*
178  *       MUST be used, otherwise normal functions would be passed an undefined variable when build for single-core
179  *       systems.
180  * @note Do NOT add any comma in the initializer list after using this macro.
181  *
182  * Example usage:
183  * @code{c}
184  * ...
185  * #include "os/critical_section.h"
186  * ...
187  * typedef struct protected_struct_t {
188  *     int member1;
189  *     DECLARE_CRIT_SECTION_LOCK_IN_STRUCT(my_lock)
190  *     int another_member;
191  * };
192  * ...
193  * protected_struct_t my_protected = {
194  *     .member1 = 0,
195  *     INIT_CRIT_SECTION_LOCK_IN_STRUCT(my_lock) // no comma!
196  *     another_member = 47,
197  * };
198  * @endcode
199  */
200 #if OS_SPINLOCK == 1
201 #define INIT_CRIT_SECTION_LOCK_IN_STRUCT(lock_name) .lock_name = portMUX_INITIALIZER_UNLOCKED,
202 #else
203 #define INIT_CRIT_SECTION_LOCK_IN_STRUCT(lock_name)
204 #endif
205 
206 /**
207  * @brief Enter a critical section, i.e., a section that will not be interrupted by any other task or interrupt.
208  *
209  * On multi-core systems, this will disable interrupts and take the spinlock \c lock. On single core systems, a
210  * spinlock is unncessary, hence \c lock is ignored and interrupts are disabled only.
211  *
212  * @note This macro MUST be used together with any of the initialization macros, e.g.
213  *       DEFINE_CRIT_SECTION_LOCK_STATIC. If not, there may be unused variables.
214  *
215  * @param lock Pointer to the critical section lock. Ignored if build for single core system.
216  *
217  * Example usage with static locks:
218  * @code{c}
219  * ...
220  * #include "os/critical_section.h"
221  * ...
222  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
223  * ...
224  * esp_os_enter_critical(&my_lock);
225  * // code inside critical section
226  * esp_os_exit_critical(&my_lock);
227  * @endcode
228  */
229 #if OS_SPINLOCK == 1
230 #define esp_os_enter_critical(lock)			portENTER_CRITICAL(lock)
231 #else
232 #define esp_os_enter_critical(lock)			vPortEnterCritical()
233 #endif
234 
235 /**
236  * @brief Exit a critical section.
237  *
238  * On multi-core systems, this will enable interrupts and release the spinlock \c lock. On single core systems, a
239  * spinlock is unncessary, hence \c lock is ignored and interrupts are enabled only.
240  *
241  * @note This macro MUST be used together with any of the initialization macros, e.g.
242  *       DEFINE_CRIT_SECTION_LOCK_STATIC. If not, there may be unused variables.
243  *
244  * @param lock Pointer to the critical section lock. Ignored if build for single core system.
245  *
246  * Example usage with static locks:
247  * @code{c}
248  * ...
249  * #include "os/critical_section.h"
250  * ...
251  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
252  * ...
253  * esp_os_enter_critical(&my_lock);
254  * // code inside critical section
255  * esp_os_exit_critical(&my_lock);
256  * @endcode
257  */
258 #if OS_SPINLOCK == 1
259 #define esp_os_exit_critical(lock)			portEXIT_CRITICAL(lock)
260 #else
261 #define esp_os_exit_critical(lock)			vPortExitCritical()
262 #endif
263 
264 /**
265  * @brief Enter a critical section while from ISR.
266  *
267  * On multi-core systems, this will disable interrupts and take the spinlock \c lock. On single core systems, a
268  * spinlock is unncessary, hence \c lock is ignored and interrupts are disabled only.
269  *
270  * @note This macro MUST be used together with any of the initialization macros, e.g.
271  *       DEFINE_CRIT_SECTION_LOCK_STATIC. If not, there may be unused variables.
272  *
273  * @param lock Pointer to the critical section lock. Ignored if build for single core system.
274  *
275  * Example usage with static locks:
276  * @code{c}
277  * ...
278  * #include "os/critical_section.h"
279  * ...
280  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
281  * ...
282  * esp_os_enter_critical(&my_lock);
283  * // code inside critical section
284  * esp_os_exit_critical(&my_lock);
285  * @endcode
286  */
287 #if OS_SPINLOCK == 1
288 #define esp_os_enter_critical_isr(lock)		portENTER_CRITICAL_ISR(lock)
289 #else
290 #define esp_os_enter_critical_isr(lock)		vPortEnterCritical()
291 #endif
292 
293 /**
294  * @brief Exit a critical section after entering from ISR.
295  *
296  * On multi-core systems, this will enable interrupts and release the spinlock \c lock. On single core systems, a
297  * spinlock is unncessary, hence \c lock is ignored and interrupts are enabled only.
298  *
299  * @note This macro MUST be used together with any of the initialization macros, e.g.
300  *       DEFINE_CRIT_SECTION_LOCK_STATIC. If not, there may be unused variables.
301  *
302  * @param lock Pointer to the critical section lock. Ignored if build for single core system.
303  *
304  * Example usage with static locks:
305  * @code{c}
306  * ...
307  * #include "os/critical_section.h"
308  * ...
309  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
310  * ...
311  * esp_os_enter_critical(&my_lock);
312  * // code inside critical section
313  * esp_os_exit_critical(&my_lock);
314  * @endcode
315  */
316 #if OS_SPINLOCK == 1
317 #define esp_os_exit_critical_isr(lock)		portEXIT_CRITICAL_ISR(lock)
318 #else
319 #define esp_os_exit_critical_isr(lock)		vPortExitCritical()
320 #endif
321 
322 /**
323  * @brief Enter a critical section from normal task or ISR. This macro will check if the current CPU is processing
324  *        an ISR or not and enter the critical section accordingly.
325  *
326  * On multi-core systems, this will disable interrupts and take the spinlock \c lock. On single core systems, a
327  * spinlock is unncessary, hence \c lock is ignored and interrupts are disabled only.
328  *
329  * @note This macro MUST be used together with any of the initialization macros, e.g.
330  *       DEFINE_CRIT_SECTION_LOCK_STATIC. If not, there may be unused variables.
331  *
332  * @param lock Pointer to the critical section lock. Ignored if build for single core system.
333  *
334  * Example usage with static locks:
335  * @code{c}
336  * ...
337  * #include "os/critical_section.h"
338  * ...
339  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
340  * ...
341  * esp_os_enter_critical(&my_lock);
342  * // code inside critical section
343  * esp_os_exit_critical(&my_lock);
344  * @endcode
345  */
346 #if OS_SPINLOCK == 1
347 #define esp_os_enter_critical_safe(lock)	portENTER_CRITICAL_SAFE(lock)
348 #else
349 #define esp_os_enter_critical_safe(lock)	vPortEnterCritical()
350 #endif
351 
352 /**
353  * @brief Exit a critical section after entering via esp_os_enter_critical_safe.
354  *
355  * On multi-core systems, this will enable interrupts and release the spinlock \c lock. On single core systems, a
356  * spinlock is unncessary, hence \c lock is ignored and interrupts are enabled only.
357  *
358  * @note This macro MUST be used together with any of the initialization macros, e.g.
359  *       DEFINE_CRIT_SECTION_LOCK_STATIC. If not, there may be unused variables.
360  *
361  * @param lock Pointer to the critical section lock. Ignored if build for single core system.
362  *
363  * Example usage with static locks:
364  * @code{c}
365  * ...
366  * #include "os/critical_section.h"
367  * ...
368  * DEFINE_CRIT_SECTION_LOCK_STATIC(my_lock); // will have internal linking (static)
369  * ...
370  * esp_os_enter_critical(&my_lock);
371  * // code inside critical section
372  * esp_os_exit_critical(&my_lock);
373  * @endcode
374  */
375 #if OS_SPINLOCK == 1
376 #define esp_os_exit_critical_safe(lock)		portEXIT_CRITICAL_SAFE(lock)
377 #else
378 #define esp_os_exit_critical_safe(lock)		vPortExitCritical()
379 #endif
380 
381 #ifdef __cplusplus
382 }
383 #endif
384