blob: 7854e197308c19397e1c4a22c37d2b20709d2b46 [file] [log] [blame]
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -08001// 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"
Wyatt Heplerf298de42021-03-19 15:06:36 -070017#include "pw_assert/check.h"
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080018#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
25using pw::thread::freertos::Context;
26
27namespace pw::thread {
28namespace {
29#if PW_THREAD_JOINING_ENABLED
30constexpr EventBits_t kThreadDoneBit = 1 << 0;
31#endif // PW_THREAD_JOINING_ENABLED
32} // namespace
33
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -070034void Context::ThreadEntryPoint(void* void_context_ptr) {
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080035 Context& context = *static_cast<Context*>(void_context_ptr);
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -070036
37 // Invoke the user's thread function. This may never return.
38 context.user_thread_entry_function_(context.user_thread_entry_arg_);
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080039
40 // Use a task only critical section to guard against join() and detach().
41 vTaskSuspendAll();
42 if (context.detached()) {
43 // There is no threadsafe way to re-use detached threads, as there's no way
44 // to signal the vTaskDelete success. Joining MUST be used for this.
45 // However to enable unit test coverage we go ahead and clear this.
46 context.set_task_handle(nullptr);
47
48#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -070049 // If the thread handle was detached before the thread finished execution,
50 // i.e. got here, then we are responsible for cleaning up the join event
51 // group.
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -080052 vEventGroupDelete(&context.join_event_group());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080053#endif // PW_THREAD_JOINING_ENABLED
54
55#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
56 // The thread was detached before the task finished, free any allocations
57 // it ran on.
58 if (context.dynamically_allocated()) {
59 delete &context;
60 }
61#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
62
63 // Re-enable the scheduler before we delete this execution.
64 xTaskResumeAll();
65 vTaskDelete(nullptr);
66 PW_UNREACHABLE;
67 }
68
69 // Otherwise the task finished before the thread was detached or joined, defer
70 // cleanup to Thread's join() or detach().
71 context.set_thread_done();
72 xTaskResumeAll();
73
74#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -080075 xEventGroupSetBits(&context.join_event_group(), kThreadDoneBit);
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080076#endif // PW_THREAD_JOINING_ENABLED
77
78 while (true) {
79#if INCLUDE_vTaskSuspend == 1
80 // Use indefinite suspension when available.
81 vTaskSuspend(nullptr);
82#else
83 vTaskDelay(portMAX_DELAY);
84#endif // INCLUDE_vTaskSuspend == 1
85 }
86 PW_UNREACHABLE;
87}
88
89void Context::TerminateThread(Context& context) {
90 // Stop the other task first.
91 PW_DCHECK_NOTNULL(context.task_handle(), "We shall not delete ourselves!");
92 vTaskDelete(context.task_handle());
93
94 // Mark the context as unused for potential later re-use.
95 context.set_task_handle(nullptr);
96
97#if PW_THREAD_JOINING_ENABLED
98 // Just in case someone abused our API, ensure their use of the event group is
99 // properly handled by the kernel regardless.
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800100 vEventGroupDelete(&context.join_event_group());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800101#endif // PW_THREAD_JOINING_ENABLED
102
103#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
104 // Then free any allocations it ran on.
105 if (context.dynamically_allocated()) {
106 delete &context;
107 }
108#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
109}
110
111Thread::Thread(const thread::Options& facade_options,
112 ThreadRoutine entry,
113 void* arg)
114 : native_type_(nullptr) {
115 // Cast the generic facade options to the backend specific option of which
116 // only one type can exist at compile time.
117 auto options = static_cast<const freertos::Options&>(facade_options);
118 if (options.static_context() != nullptr) {
119 // Use the statically allocated context.
120 native_type_ = options.static_context();
121 // Can't use a context more than once.
122 PW_DCHECK_PTR_EQ(native_type_->task_handle(), nullptr);
123 // Reset the state of the static context in case it was re-used.
124 native_type_->set_detached(false);
125 native_type_->set_thread_done(false);
126#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800127 const EventGroupHandle_t event_group_handle =
128 xEventGroupCreateStatic(&native_type_->join_event_group());
129 PW_DCHECK_PTR_EQ(event_group_handle,
130 &native_type_->join_event_group(),
131 "Failed to create the joining event group");
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800132#endif // PW_THREAD_JOINING_ENABLED
133
134 // In order to support functions which return and joining, a delegate is
135 // deep copied into the context with a small wrapping function to actually
136 // invoke the task with its arg.
137 native_type_->set_thread_routine(entry, arg);
138 const TaskHandle_t task_handle =
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700139 xTaskCreateStatic(Context::ThreadEntryPoint,
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800140 options.name(),
141 options.static_context()->stack().size(),
142 native_type_,
143 options.priority(),
144 options.static_context()->stack().data(),
145 &options.static_context()->tcb());
146 PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
147 native_type_->set_task_handle(task_handle);
148 } else {
149#if !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
150 PW_CRASH(
151 "dynamic thread allocations are not enabled and no static_context "
152 "was provided");
153#else // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
154 // Dynamically allocate the context and the task.
155 native_type_ = new pw::thread::freertos::Context();
156 native_type_->set_dynamically_allocated();
157#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800158 const EventGroupHandle_t event_group_handle =
159 xEventGroupCreateStatic(&native_type_->join_event_group());
160 PW_DCHECK_PTR_EQ(event_group_handle,
161 &native_type_->join_event_group(),
162 "Failed to create the joining event group");
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800163#endif // PW_THREAD_JOINING_ENABLED
164
165 // In order to support functions which return and joining, a delegate is
166 // deep copied into the context with a small wrapping function to actually
167 // invoke the task with its arg.
168 native_type_->set_thread_routine(entry, arg);
169 TaskHandle_t task_handle;
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700170 const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800171 options.name(),
172 options.stack_size_words(),
173 native_type_,
174 options.priority(),
175 &task_handle);
176
177 // Ensure it succeeded.
178 PW_CHECK_UINT_EQ(result, pdPASS);
179 native_type_->set_task_handle(task_handle);
180#endif // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
181 }
182}
183
184void Thread::detach() {
Ewout van Bekkum47941cc2021-04-12 15:39:19 -0700185 PW_CHECK(joinable());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800186
187#if INCLUDE_vTaskSuspend == 1
188 // No need to suspend extra tasks.
189 vTaskSuspend(native_type_->task_handle());
190#else
191 vTaskSuspendAll();
192#endif // INCLUDE_vTaskSuspend == 1
193 native_type_->set_detached();
194 const bool thread_done = native_type_->thread_done();
195#if INCLUDE_vTaskSuspend == 1
196 // No need to suspend extra tasks.
197 vTaskResume(native_type_->task_handle());
198#else
199 vTaskResumeAll();
200#endif // INCLUDE_vTaskSuspend == 1
201
202 if (thread_done) {
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700203 // The task finished (hit end of Context::ThreadEntryPoint) before we
204 // invoked detach, clean up the thread.
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800205 Context::TerminateThread(*native_type_);
206 } else {
207 // We're detaching before the task finished, defer cleanup to the task at
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700208 // the end of Context::ThreadEntryPoint.
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800209 }
210
211 // Update to no longer represent a thread of execution.
212 native_type_ = nullptr;
213}
214
215#if PW_THREAD_JOINING_ENABLED
216void Thread::join() {
217 PW_CHECK(joinable());
218 PW_CHECK(this_thread::get_id() != get_id());
219
220 // Wait indefinitely until kThreadDoneBit is set.
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800221 while (xEventGroupWaitBits(&native_type_->join_event_group(),
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800222 kThreadDoneBit,
223 pdTRUE, // Clear the bits.
224 pdFALSE, // Any bits is fine, N/A.
225 portMAX_DELAY) != kThreadDoneBit) {
226 }
227
228 // No need for a critical section here as the thread at this point is
229 // waiting to be terminated.
230 Context::TerminateThread(*native_type_);
231
232 // Update to no longer represent a thread of execution.
233 native_type_ = nullptr;
234}
235#endif // PW_THREAD_JOINING_ENABLED
236
237} // namespace pw::thread