1 /*
2 * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdlib.h>
8 #include <assert.h>
9 #include <cxxabi.h>
10 #include <stdint.h>
11 #include <limits.h>
12 #include <algorithm>
13 #include <sys/lock.h>
14 #include "freertos/FreeRTOS.h"
15 #include "freertos/semphr.h"
16 #include "freertos/task.h"
17
18 using __cxxabiv1::__guard;
19
20 static SemaphoreHandle_t s_static_init_mutex = NULL; //!< lock used for the critical section
21 static SemaphoreHandle_t s_static_init_wait_sem = NULL; //!< counting semaphore used by the waiting tasks
22 static portMUX_TYPE s_init_spinlock = portMUX_INITIALIZER_UNLOCKED; //!< spinlock used to guard initialization of the above two primitives
23 static size_t s_static_init_waiting_count = 0; //!< number of tasks which are waiting for static init guards
24 #ifndef _NDEBUG
25 static size_t s_static_init_max_waiting_count = 0; //!< maximum ever value of the above; can be inspected using GDB for debugging purposes
26 #endif
27
28 extern "C" int __cxa_guard_acquire(__guard* pg);
29 extern "C" void __cxa_guard_release(__guard* pg);
30 extern "C" void __cxa_guard_abort(__guard* pg);
31 extern "C" void __cxa_guard_dummy(void);
32
33 /**
34 * Layout of the guard object (defined by the ABI).
35 *
36 * Compiler will check lower byte before calling guard functions.
37 */
38 typedef struct {
39 uint8_t ready; //!< nonzero if initialization is done
40 uint8_t pending; //!< nonzero if initialization is in progress
41 } guard_t;
42
static_init_prepare()43 static void static_init_prepare()
44 {
45 portENTER_CRITICAL(&s_init_spinlock);
46 if (s_static_init_mutex == NULL) {
47 s_static_init_mutex = xSemaphoreCreateMutex();
48 s_static_init_wait_sem = xSemaphoreCreateCounting(INT_MAX, 0);
49 if (s_static_init_mutex == NULL || s_static_init_wait_sem == NULL) {
50 // no way to bail out of static initialization without these
51 abort();
52 }
53 }
54 portEXIT_CRITICAL(&s_init_spinlock);
55 }
56
57 /**
58 * Use s_static_init_wait_sem to wait until guard->pending == 0.
59 * Preconditions:
60 * - s_static_init_mutex taken
61 * - guard.pending == 1
62 * Postconditions:
63 * - s_static_init_mutex taken
64 * - guard.pending == 0
65 */
wait_for_guard_obj(guard_t * g)66 static void wait_for_guard_obj(guard_t* g)
67 {
68 s_static_init_waiting_count++;
69 #ifndef _NDEBUG
70 s_static_init_max_waiting_count = std::max(s_static_init_waiting_count,
71 s_static_init_max_waiting_count);
72 #endif
73
74 do {
75 auto result = xSemaphoreGive(s_static_init_mutex);
76 assert(result);
77 static_cast<void>(result);
78 /* Task may be preempted here, but this isn't a problem,
79 * as the semaphore will be given exactly the s_static_init_waiting_count
80 * number of times; eventually the current task will execute next statement,
81 * which will immediately succeed.
82 */
83 result = xSemaphoreTake(s_static_init_wait_sem, portMAX_DELAY);
84 assert(result);
85 /* At this point the semaphore was given, so all waiting tasks have woken up.
86 * We take s_static_init_mutex before accessing the state of the guard
87 * object again.
88 */
89 result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY);
90 assert(result);
91 /* Semaphore may have been given because some other guard object became ready.
92 * Check the guard object we need and wait again if it is still pending.
93 */
94 } while(g->pending);
95 s_static_init_waiting_count--;
96 }
97
98 /**
99 * Unblock tasks waiting for static initialization to complete.
100 * Preconditions:
101 * - s_static_init_mutex taken
102 * Postconditions:
103 * - s_static_init_mutex taken
104 */
signal_waiting_tasks()105 static void signal_waiting_tasks()
106 {
107 auto count = s_static_init_waiting_count;
108 while (count--) {
109 xSemaphoreGive(s_static_init_wait_sem);
110 }
111 }
112
__cxa_guard_acquire(__guard * pg)113 extern "C" int __cxa_guard_acquire(__guard* pg)
114 {
115 guard_t* g = reinterpret_cast<guard_t*>(pg);
116 const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED;
117 if (!scheduler_started) {
118 if (g->pending) {
119 /* Before the scheduler has started, there we don't support simultaneous
120 * static initialization. This may be implemented using a spinlock and a
121 * s32c1i instruction, though.
122 */
123 abort();
124 }
125 } else {
126 if (s_static_init_mutex == NULL) {
127 static_init_prepare();
128 }
129
130 /* We don't need to use double-checked locking pattern here, as the compiler
131 * must generate code to check if the first byte of *pg is non-zero, before
132 * calling __cxa_guard_acquire.
133 */
134 auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY);
135 assert(result);
136 static_cast<void>(result);
137 if (g->pending) {
138 /* Another task is doing initialization at the moment; wait until it calls
139 * __cxa_guard_release or __cxa_guard_abort
140 */
141 wait_for_guard_obj(g);
142 /* At this point there are two scenarios:
143 * - the task which was doing static initialization has called __cxa_guard_release,
144 * which means that g->ready is set. We need to return 0.
145 * - the task which was doing static initialization has called __cxa_guard_abort,
146 * which means that g->ready is not set; we should acquire the guard and return 1,
147 * same as for the case if we didn't have to wait.
148 * Note: actually the second scenario is unlikely to occur in the current
149 * configuration because exception support is disabled.
150 */
151 }
152 }
153 int ret;
154 if (g->ready) {
155 /* Static initialization has been done by another task; nothing to do here */
156 ret = 0;
157 } else {
158 /* Current task can start doing static initialization */
159 g->pending = 1;
160 ret = 1;
161 }
162 if (scheduler_started) {
163 auto result = xSemaphoreGive(s_static_init_mutex);
164 assert(result);
165 static_cast<void>(result);
166 }
167 return ret;
168 }
169
__cxa_guard_release(__guard * pg)170 extern "C" void __cxa_guard_release(__guard* pg)
171 {
172 guard_t* g = reinterpret_cast<guard_t*>(pg);
173 const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED;
174 if (scheduler_started) {
175 auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY);
176 assert(result);
177 static_cast<void>(result);
178 }
179 assert(g->pending && "tried to release a guard which wasn't acquired");
180 g->pending = 0;
181 /* Initialization was successful */
182 g->ready = 1;
183 if (scheduler_started) {
184 /* Unblock the tasks waiting for static initialization to complete */
185 signal_waiting_tasks();
186 auto result = xSemaphoreGive(s_static_init_mutex);
187 assert(result);
188 static_cast<void>(result);
189 }
190 }
191
__cxa_guard_abort(__guard * pg)192 extern "C" void __cxa_guard_abort(__guard* pg)
193 {
194 guard_t* g = reinterpret_cast<guard_t*>(pg);
195 const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED;
196 if (scheduler_started) {
197 auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY);
198 assert(result);
199 static_cast<void>(result);
200 }
201 assert(!g->ready && "tried to abort a guard which is ready");
202 assert(g->pending && "tried to release a guard which is not acquired");
203 g->pending = 0;
204 if (scheduler_started) {
205 /* Unblock the tasks waiting for static initialization to complete */
206 signal_waiting_tasks();
207 auto result = xSemaphoreGive(s_static_init_mutex);
208 assert(result);
209 static_cast<void>(result);
210 }
211 }
212
213 /**
214 * Dummy function used to force linking this file instead of the same one in libstdc++.
215 * This works via -u __cxa_guard_dummy flag in component.mk
216 */
__cxa_guard_dummy(void)217 extern "C" void __cxa_guard_dummy(void)
218 {
219 }
220