blob: 865b2a311bb544b70c21d203a7b18e3035577364 [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.
Jack Arendtfb1492d2021-09-28 13:33:27 -070052 vEventGroupDelete(
53 reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080054#endif // PW_THREAD_JOINING_ENABLED
55
56#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
57 // The thread was detached before the task finished, free any allocations
58 // it ran on.
59 if (context.dynamically_allocated()) {
60 delete &context;
61 }
62#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
63
64 // Re-enable the scheduler before we delete this execution.
65 xTaskResumeAll();
66 vTaskDelete(nullptr);
67 PW_UNREACHABLE;
68 }
69
70 // Otherwise the task finished before the thread was detached or joined, defer
71 // cleanup to Thread's join() or detach().
72 context.set_thread_done();
73 xTaskResumeAll();
74
75#if PW_THREAD_JOINING_ENABLED
Jack Arendtfb1492d2021-09-28 13:33:27 -070076 xEventGroupSetBits(
77 reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()),
78 kThreadDoneBit);
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080079#endif // PW_THREAD_JOINING_ENABLED
80
81 while (true) {
82#if INCLUDE_vTaskSuspend == 1
83 // Use indefinite suspension when available.
84 vTaskSuspend(nullptr);
85#else
86 vTaskDelay(portMAX_DELAY);
87#endif // INCLUDE_vTaskSuspend == 1
88 }
89 PW_UNREACHABLE;
90}
91
92void Context::TerminateThread(Context& context) {
93 // Stop the other task first.
94 PW_DCHECK_NOTNULL(context.task_handle(), "We shall not delete ourselves!");
95 vTaskDelete(context.task_handle());
96
97 // Mark the context as unused for potential later re-use.
98 context.set_task_handle(nullptr);
99
100#if PW_THREAD_JOINING_ENABLED
101 // Just in case someone abused our API, ensure their use of the event group is
102 // properly handled by the kernel regardless.
Jack Arendtfb1492d2021-09-28 13:33:27 -0700103 vEventGroupDelete(
104 reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800105#endif // PW_THREAD_JOINING_ENABLED
106
107#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
108 // Then free any allocations it ran on.
109 if (context.dynamically_allocated()) {
110 delete &context;
111 }
112#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
113}
114
115Thread::Thread(const thread::Options& facade_options,
116 ThreadRoutine entry,
117 void* arg)
118 : native_type_(nullptr) {
119 // Cast the generic facade options to the backend specific option of which
120 // only one type can exist at compile time.
121 auto options = static_cast<const freertos::Options&>(facade_options);
122 if (options.static_context() != nullptr) {
123 // Use the statically allocated context.
124 native_type_ = options.static_context();
125 // Can't use a context more than once.
126 PW_DCHECK_PTR_EQ(native_type_->task_handle(), nullptr);
127 // Reset the state of the static context in case it was re-used.
128 native_type_->set_detached(false);
129 native_type_->set_thread_done(false);
130#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800131 const EventGroupHandle_t event_group_handle =
132 xEventGroupCreateStatic(&native_type_->join_event_group());
133 PW_DCHECK_PTR_EQ(event_group_handle,
134 &native_type_->join_event_group(),
135 "Failed to create the joining event group");
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800136#endif // PW_THREAD_JOINING_ENABLED
137
138 // In order to support functions which return and joining, a delegate is
139 // deep copied into the context with a small wrapping function to actually
140 // invoke the task with its arg.
141 native_type_->set_thread_routine(entry, arg);
142 const TaskHandle_t task_handle =
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700143 xTaskCreateStatic(Context::ThreadEntryPoint,
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800144 options.name(),
145 options.static_context()->stack().size(),
146 native_type_,
147 options.priority(),
148 options.static_context()->stack().data(),
149 &options.static_context()->tcb());
150 PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
151 native_type_->set_task_handle(task_handle);
152 } else {
153#if !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
154 PW_CRASH(
155 "dynamic thread allocations are not enabled and no static_context "
156 "was provided");
157#else // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
158 // Dynamically allocate the context and the task.
159 native_type_ = new pw::thread::freertos::Context();
160 native_type_->set_dynamically_allocated();
161#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800162 const EventGroupHandle_t event_group_handle =
163 xEventGroupCreateStatic(&native_type_->join_event_group());
164 PW_DCHECK_PTR_EQ(event_group_handle,
165 &native_type_->join_event_group(),
166 "Failed to create the joining event group");
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800167#endif // PW_THREAD_JOINING_ENABLED
168
169 // In order to support functions which return and joining, a delegate is
170 // deep copied into the context with a small wrapping function to actually
171 // invoke the task with its arg.
172 native_type_->set_thread_routine(entry, arg);
173 TaskHandle_t task_handle;
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700174 const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800175 options.name(),
176 options.stack_size_words(),
177 native_type_,
178 options.priority(),
179 &task_handle);
180
181 // Ensure it succeeded.
182 PW_CHECK_UINT_EQ(result, pdPASS);
183 native_type_->set_task_handle(task_handle);
184#endif // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
185 }
186}
187
188void Thread::detach() {
Ewout van Bekkum47941cc2021-04-12 15:39:19 -0700189 PW_CHECK(joinable());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800190
Scott James Remnantbf34d282021-08-09 14:25:46 -0700191#if (INCLUDE_vTaskSuspend == 1) && (INCLUDE_xTaskGetSchedulerState == 1)
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800192 // No need to suspend extra tasks.
Scott James Remnantbf34d282021-08-09 14:25:46 -0700193 if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
194 vTaskSuspend(native_type_->task_handle());
195 }
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800196#else
Scott James Remnantbf34d282021-08-09 14:25:46 -0700197 // Safe to suspend all tasks while scheduler is not running.
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800198 vTaskSuspendAll();
199#endif // INCLUDE_vTaskSuspend == 1
200 native_type_->set_detached();
201 const bool thread_done = native_type_->thread_done();
Scott James Remnantbf34d282021-08-09 14:25:46 -0700202#if (INCLUDE_vTaskSuspend == 1) && (INCLUDE_xTaskGetSchedulerState == 1)
203 // No need to suspend extra tasks, but only safe to call once scheduler is
204 // running.
205 if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
206 vTaskResume(native_type_->task_handle());
207 }
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800208#else
Scott James Remnantbf34d282021-08-09 14:25:46 -0700209 // Safe to resume all tasks while scheduler is not running.
Edwin Vane59c3ac72021-12-02 11:20:33 -0500210 xTaskResumeAll();
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800211#endif // INCLUDE_vTaskSuspend == 1
212
213 if (thread_done) {
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700214 // The task finished (hit end of Context::ThreadEntryPoint) before we
215 // invoked detach, clean up the thread.
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800216 Context::TerminateThread(*native_type_);
217 } else {
218 // We're detaching before the task finished, defer cleanup to the task at
Ewout van Bekkum3f00a3e2021-05-14 10:28:49 -0700219 // the end of Context::ThreadEntryPoint.
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800220 }
221
222 // Update to no longer represent a thread of execution.
223 native_type_ = nullptr;
224}
225
226#if PW_THREAD_JOINING_ENABLED
227void Thread::join() {
228 PW_CHECK(joinable());
229 PW_CHECK(this_thread::get_id() != get_id());
230
231 // Wait indefinitely until kThreadDoneBit is set.
Jack Arendtfb1492d2021-09-28 13:33:27 -0700232 while (xEventGroupWaitBits(reinterpret_cast<EventGroupHandle_t>(
233 &native_type_->join_event_group()),
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800234 kThreadDoneBit,
235 pdTRUE, // Clear the bits.
236 pdFALSE, // Any bits is fine, N/A.
237 portMAX_DELAY) != kThreadDoneBit) {
238 }
239
240 // No need for a critical section here as the thread at this point is
241 // waiting to be terminated.
242 Context::TerminateThread(*native_type_);
243
244 // Update to no longer represent a thread of execution.
245 native_type_ = nullptr;
246}
247#endif // PW_THREAD_JOINING_ENABLED
248
249} // namespace pw::thread