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_freertos/config.h"
20 #include "pw_thread_freertos/context.h"
21 #include "pw_thread_freertos/options.h"
22 #include "task.h"
23
24 using pw::thread::freertos::Context;
25
26 namespace pw::thread {
27 namespace {
28
29 #if (INCLUDE_xTaskGetSchedulerState != 1) && (configUSE_TIMERS != 1)
30 #error "xTaskGetSchedulerState is required for pw::Thread"
31 #endif
32
33 #if PW_THREAD_JOINING_ENABLED
34 constexpr EventBits_t kThreadDoneBit = 1 << 0;
35 #endif // PW_THREAD_JOINING_ENABLED
36 } // namespace
37
ThreadEntryPoint(void * void_context_ptr)38 void Context::ThreadEntryPoint(void* void_context_ptr) {
39 Context& context = *static_cast<Context*>(void_context_ptr);
40
41 // Invoke the user's thread function. This may never return.
42 context.fn_();
43 context.fn_ = nullptr;
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
AddToEventGroup()120 void Context::AddToEventGroup() {
121 #if PW_THREAD_JOINING_ENABLED
122 const EventGroupHandle_t event_group_handle =
123 xEventGroupCreateStatic(&join_event_group());
124 PW_DCHECK_PTR_EQ(event_group_handle,
125 &join_event_group(),
126 "Failed to create the joining event group");
127 #endif // PW_THREAD_JOINING_ENABLED
128 }
129
CreateThread(const freertos::Options & options,Function<void ()> && thread_fn,Context * & native_type_out)130 void Context::CreateThread(const freertos::Options& options,
131 Function<void()>&& thread_fn,
132 Context*& native_type_out) {
133 TaskHandle_t task_handle;
134 if (options.static_context() != nullptr) {
135 // Use the statically allocated context.
136 native_type_out = options.static_context();
137 // Can't use a context more than once.
138 PW_DCHECK_PTR_EQ(native_type_out->task_handle(), nullptr);
139 // Reset the state of the static context in case it was re-used.
140 native_type_out->set_detached(false);
141 native_type_out->set_thread_done(false);
142 native_type_out->AddToEventGroup();
143
144 // In order to support functions which return and joining, a delegate is
145 // deep copied into the context with a small wrapping function to actually
146 // invoke the task with its arg.
147 native_type_out->set_thread_routine(std::move(thread_fn));
148 task_handle = xTaskCreateStatic(Context::ThreadEntryPoint,
149 options.name(),
150 options.static_context()->stack().size(),
151 native_type_out,
152 options.priority(),
153 options.static_context()->stack().data(),
154 &options.static_context()->tcb());
155 } else {
156 #if !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
157 PW_CRASH(
158 "dynamic thread allocations are not enabled and no static_context "
159 "was provided");
160 #else // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
161 // Dynamically allocate the context and the task.
162 native_type_out = new pw::thread::freertos::Context();
163 native_type_out->set_dynamically_allocated();
164 native_type_out->AddToEventGroup();
165
166 // In order to support functions which return and joining, a delegate is
167 // deep copied into the context with a small wrapping function to actually
168 // invoke the task with its arg.
169 native_type_out->set_thread_routine(std::move(thread_fn));
170 const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
171 options.name(),
172 options.stack_size_words(),
173 native_type_out,
174 options.priority(),
175 &task_handle);
176
177 // Ensure it succeeded.
178 PW_CHECK_UINT_EQ(result, pdPASS);
179 #endif // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
180 }
181 PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
182 native_type_out->set_task_handle(task_handle);
183 }
184
Thread(const thread::Options & facade_options,Function<void ()> && entry)185 Thread::Thread(const thread::Options& facade_options, Function<void()>&& entry)
186 : native_type_(nullptr) {
187 // Cast the generic facade options to the backend specific option of which
188 // only one type can exist at compile time.
189 auto options = static_cast<const freertos::Options&>(facade_options);
190 Context::CreateThread(options, std::move(entry), native_type_);
191 }
192
detach()193 void Thread::detach() {
194 PW_CHECK(joinable());
195
196 // xTaskResumeAll() can only be used after the scheduler has been started.
197 const bool scheduler_initialized =
198 xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED;
199
200 if (scheduler_initialized) {
201 // We don't want to individually suspend and resume this task using
202 // vTaskResume() as that can cause tasks to prematurely wake up and return
203 // from blocking APIs (b/303885539).
204 vTaskSuspendAll();
205 }
206 native_type_->set_detached();
207 const bool thread_done = native_type_->thread_done();
208 if (scheduler_initialized) {
209 xTaskResumeAll();
210 }
211
212 if (thread_done) {
213 // The task finished (hit end of Context::ThreadEntryPoint) before we
214 // invoked detach, clean up the thread.
215 Context::TerminateThread(*native_type_);
216 } else {
217 // We're detaching before the task finished, defer cleanup to the task at
218 // the end of Context::ThreadEntryPoint.
219 }
220
221 // Update to no longer represent a thread of execution.
222 native_type_ = nullptr;
223 }
224
225 #if PW_THREAD_JOINING_ENABLED
join()226 void Thread::join() {
227 PW_CHECK(joinable());
228 PW_CHECK(this_thread::get_id() != get_id());
229
230 // Wait indefinitely until kThreadDoneBit is set.
231 while (xEventGroupWaitBits(reinterpret_cast<EventGroupHandle_t>(
232 &native_type_->join_event_group()),
233 kThreadDoneBit,
234 pdTRUE, // Clear the bits.
235 pdFALSE, // Any bits is fine, N/A.
236 portMAX_DELAY) != kThreadDoneBit) {
237 }
238
239 // No need for a critical section here as the thread at this point is
240 // waiting to be terminated.
241 Context::TerminateThread(*native_type_);
242
243 // Update to no longer represent a thread of execution.
244 native_type_ = nullptr;
245 }
246 #endif // PW_THREAD_JOINING_ENABLED
247
248 } // namespace pw::thread
249