1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #include "pw_thread/thread.h"
15
16 #include "FreeRTOS.h"
17 #include "pw_assert/check.h"
18 #include "pw_preprocessor/compiler.h"
19 #include "pw_thread/id.h"
20 #include "pw_thread_freertos/config.h"
21 #include "pw_thread_freertos/context.h"
22 #include "pw_thread_freertos/options.h"
23 #include "task.h"
24
25 using pw::thread::freertos::Context;
26
27 namespace pw::thread {
28 namespace {
29
30 #if (INCLUDE_xTaskGetSchedulerState != 1) && (configUSE_TIMERS != 1)
31 #error "xTaskGetSchedulerState is required for pw::thread::Thread"
32 #endif
33
34 #if PW_THREAD_JOINING_ENABLED
35 constexpr EventBits_t kThreadDoneBit = 1 << 0;
36 #endif // PW_THREAD_JOINING_ENABLED
37 } // namespace
38
ThreadEntryPoint(void * void_context_ptr)39 void Context::ThreadEntryPoint(void* void_context_ptr) {
40 Context& context = *static_cast<Context*>(void_context_ptr);
41
42 // Invoke the user's thread function. This may never return.
43 context.user_thread_entry_function_(context.user_thread_entry_arg_);
44
45 // Use a task only critical section to guard against join() and detach().
46 vTaskSuspendAll();
47 if (context.detached()) {
48 // There is no threadsafe way to re-use detached threads, as there's no way
49 // to signal the vTaskDelete success. Joining MUST be used for this.
50 // However to enable unit test coverage we go ahead and clear this.
51 context.set_task_handle(nullptr);
52
53 #if PW_THREAD_JOINING_ENABLED
54 // If the thread handle was detached before the thread finished execution,
55 // i.e. got here, then we are responsible for cleaning up the join event
56 // group.
57 vEventGroupDelete(
58 reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
59 #endif // PW_THREAD_JOINING_ENABLED
60
61 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
62 // The thread was detached before the task finished, free any allocations
63 // it ran on.
64 if (context.dynamically_allocated()) {
65 delete &context;
66 }
67 #endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
68
69 // Re-enable the scheduler before we delete this execution.
70 xTaskResumeAll();
71 vTaskDelete(nullptr);
72 PW_UNREACHABLE;
73 }
74
75 // Otherwise the task finished before the thread was detached or joined, defer
76 // cleanup to Thread's join() or detach().
77 context.set_thread_done();
78 xTaskResumeAll();
79
80 #if PW_THREAD_JOINING_ENABLED
81 xEventGroupSetBits(
82 reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()),
83 kThreadDoneBit);
84 #endif // PW_THREAD_JOINING_ENABLED
85
86 while (true) {
87 #if INCLUDE_vTaskSuspend == 1
88 // Use indefinite suspension when available.
89 vTaskSuspend(nullptr);
90 #else
91 vTaskDelay(portMAX_DELAY);
92 #endif // INCLUDE_vTaskSuspend == 1
93 }
94 PW_UNREACHABLE;
95 }
96
TerminateThread(Context & context)97 void Context::TerminateThread(Context& context) {
98 // Stop the other task first.
99 PW_DCHECK_NOTNULL(context.task_handle(), "We shall not delete ourselves!");
100 vTaskDelete(context.task_handle());
101
102 // Mark the context as unused for potential later re-use.
103 context.set_task_handle(nullptr);
104
105 #if PW_THREAD_JOINING_ENABLED
106 // Just in case someone abused our API, ensure their use of the event group is
107 // properly handled by the kernel regardless.
108 vEventGroupDelete(
109 reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
110 #endif // PW_THREAD_JOINING_ENABLED
111
112 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
113 // Then free any allocations it ran on.
114 if (context.dynamically_allocated()) {
115 delete &context;
116 }
117 #endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
118 }
119
Thread(const thread::Options & facade_options,ThreadRoutine entry,void * arg)120 Thread::Thread(const thread::Options& facade_options,
121 ThreadRoutine entry,
122 void* arg)
123 : native_type_(nullptr) {
124 // Cast the generic facade options to the backend specific option of which
125 // only one type can exist at compile time.
126 auto options = static_cast<const freertos::Options&>(facade_options);
127 if (options.static_context() != nullptr) {
128 // Use the statically allocated context.
129 native_type_ = options.static_context();
130 // Can't use a context more than once.
131 PW_DCHECK_PTR_EQ(native_type_->task_handle(), nullptr);
132 // Reset the state of the static context in case it was re-used.
133 native_type_->set_detached(false);
134 native_type_->set_thread_done(false);
135 #if PW_THREAD_JOINING_ENABLED
136 const EventGroupHandle_t event_group_handle =
137 xEventGroupCreateStatic(&native_type_->join_event_group());
138 PW_DCHECK_PTR_EQ(event_group_handle,
139 &native_type_->join_event_group(),
140 "Failed to create the joining event group");
141 #endif // PW_THREAD_JOINING_ENABLED
142
143 // In order to support functions which return and joining, a delegate is
144 // deep copied into the context with a small wrapping function to actually
145 // invoke the task with its arg.
146 native_type_->set_thread_routine(entry, arg);
147 const TaskHandle_t task_handle =
148 xTaskCreateStatic(Context::ThreadEntryPoint,
149 options.name(),
150 options.static_context()->stack().size(),
151 native_type_,
152 options.priority(),
153 options.static_context()->stack().data(),
154 &options.static_context()->tcb());
155 PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
156 native_type_->set_task_handle(task_handle);
157 } else {
158 #if !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
159 PW_CRASH(
160 "dynamic thread allocations are not enabled and no static_context "
161 "was provided");
162 #else // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
163 // Dynamically allocate the context and the task.
164 native_type_ = new pw::thread::freertos::Context();
165 native_type_->set_dynamically_allocated();
166 #if PW_THREAD_JOINING_ENABLED
167 const EventGroupHandle_t event_group_handle =
168 xEventGroupCreateStatic(&native_type_->join_event_group());
169 PW_DCHECK_PTR_EQ(event_group_handle,
170 &native_type_->join_event_group(),
171 "Failed to create the joining event group");
172 #endif // PW_THREAD_JOINING_ENABLED
173
174 // In order to support functions which return and joining, a delegate is
175 // deep copied into the context with a small wrapping function to actually
176 // invoke the task with its arg.
177 native_type_->set_thread_routine(entry, arg);
178 TaskHandle_t task_handle;
179 const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
180 options.name(),
181 options.stack_size_words(),
182 native_type_,
183 options.priority(),
184 &task_handle);
185
186 // Ensure it succeeded.
187 PW_CHECK_UINT_EQ(result, pdPASS);
188 native_type_->set_task_handle(task_handle);
189 #endif // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
190 }
191 }
192
detach()193 void Thread::detach() {
194 PW_CHECK(joinable());
195
196 if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
197 #if (INCLUDE_vTaskSuspend == 1)
198 // No need to suspend extra tasks.
199 vTaskSuspend(native_type_->task_handle());
200 #else
201 vTaskSuspendAll();
202 #endif // INCLUDE_vTaskSuspend == 1
203 }
204 native_type_->set_detached();
205 const bool thread_done = native_type_->thread_done();
206 if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
207 #if (INCLUDE_vTaskSuspend == 1)
208 vTaskResume(native_type_->task_handle());
209 #else
210 xTaskResumeAll();
211 #endif // INCLUDE_vTaskSuspend == 1
212 }
213
214 if (thread_done) {
215 // The task finished (hit end of Context::ThreadEntryPoint) before we
216 // invoked detach, clean up the thread.
217 Context::TerminateThread(*native_type_);
218 } else {
219 // We're detaching before the task finished, defer cleanup to the task at
220 // the end of Context::ThreadEntryPoint.
221 }
222
223 // Update to no longer represent a thread of execution.
224 native_type_ = nullptr;
225 }
226
227 #if PW_THREAD_JOINING_ENABLED
join()228 void Thread::join() {
229 PW_CHECK(joinable());
230 PW_CHECK(this_thread::get_id() != get_id());
231
232 // Wait indefinitely until kThreadDoneBit is set.
233 while (xEventGroupWaitBits(reinterpret_cast<EventGroupHandle_t>(
234 &native_type_->join_event_group()),
235 kThreadDoneBit,
236 pdTRUE, // Clear the bits.
237 pdFALSE, // Any bits is fine, N/A.
238 portMAX_DELAY) != kThreadDoneBit) {
239 }
240
241 // No need for a critical section here as the thread at this point is
242 // waiting to be terminated.
243 Context::TerminateThread(*native_type_);
244
245 // Update to no longer represent a thread of execution.
246 native_type_ = nullptr;
247 }
248 #endif // PW_THREAD_JOINING_ENABLED
249
250 } // namespace pw::thread
251