blob: b77df7841212ab8bc81ef4e61f5fa50bd41b22e0 [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"
17#include "pw_assert/assert.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
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
34void Context::RunThread(void* void_context_ptr) {
35 Context& context = *static_cast<Context*>(void_context_ptr);
36 context.entry_(context.arg_);
37
38 // Use a task only critical section to guard against join() and detach().
39 vTaskSuspendAll();
40 if (context.detached()) {
41 // There is no threadsafe way to re-use detached threads, as there's no way
42 // to signal the vTaskDelete success. Joining MUST be used for this.
43 // However to enable unit test coverage we go ahead and clear this.
44 context.set_task_handle(nullptr);
45
46#if PW_THREAD_JOINING_ENABLED
47 // Just in case someone abused our API, ensure their use of the event group
48 // is properly handled by the kernel regardless.
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -080049 vEventGroupDelete(&context.join_event_group());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080050#endif // PW_THREAD_JOINING_ENABLED
51
52#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
53 // The thread was detached before the task finished, free any allocations
54 // it ran on.
55 if (context.dynamically_allocated()) {
56 delete &context;
57 }
58#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
59
60 // Re-enable the scheduler before we delete this execution.
61 xTaskResumeAll();
62 vTaskDelete(nullptr);
63 PW_UNREACHABLE;
64 }
65
66 // Otherwise the task finished before the thread was detached or joined, defer
67 // cleanup to Thread's join() or detach().
68 context.set_thread_done();
69 xTaskResumeAll();
70
71#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -080072 xEventGroupSetBits(&context.join_event_group(), kThreadDoneBit);
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080073#endif // PW_THREAD_JOINING_ENABLED
74
75 while (true) {
76#if INCLUDE_vTaskSuspend == 1
77 // Use indefinite suspension when available.
78 vTaskSuspend(nullptr);
79#else
80 vTaskDelay(portMAX_DELAY);
81#endif // INCLUDE_vTaskSuspend == 1
82 }
83 PW_UNREACHABLE;
84}
85
86void Context::TerminateThread(Context& context) {
87 // Stop the other task first.
88 PW_DCHECK_NOTNULL(context.task_handle(), "We shall not delete ourselves!");
89 vTaskDelete(context.task_handle());
90
91 // Mark the context as unused for potential later re-use.
92 context.set_task_handle(nullptr);
93
94#if PW_THREAD_JOINING_ENABLED
95 // Just in case someone abused our API, ensure their use of the event group is
96 // properly handled by the kernel regardless.
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -080097 vEventGroupDelete(&context.join_event_group());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -080098#endif // PW_THREAD_JOINING_ENABLED
99
100#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
101 // Then free any allocations it ran on.
102 if (context.dynamically_allocated()) {
103 delete &context;
104 }
105#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
106}
107
108Thread::Thread(const thread::Options& facade_options,
109 ThreadRoutine entry,
110 void* arg)
111 : native_type_(nullptr) {
112 // Cast the generic facade options to the backend specific option of which
113 // only one type can exist at compile time.
114 auto options = static_cast<const freertos::Options&>(facade_options);
115 if (options.static_context() != nullptr) {
116 // Use the statically allocated context.
117 native_type_ = options.static_context();
118 // Can't use a context more than once.
119 PW_DCHECK_PTR_EQ(native_type_->task_handle(), nullptr);
120 // Reset the state of the static context in case it was re-used.
121 native_type_->set_detached(false);
122 native_type_->set_thread_done(false);
123#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800124 const EventGroupHandle_t event_group_handle =
125 xEventGroupCreateStatic(&native_type_->join_event_group());
126 PW_DCHECK_PTR_EQ(event_group_handle,
127 &native_type_->join_event_group(),
128 "Failed to create the joining event group");
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800129#endif // PW_THREAD_JOINING_ENABLED
130
131 // In order to support functions which return and joining, a delegate is
132 // deep copied into the context with a small wrapping function to actually
133 // invoke the task with its arg.
134 native_type_->set_thread_routine(entry, arg);
135 const TaskHandle_t task_handle =
136 xTaskCreateStatic(Context::RunThread,
137 options.name(),
138 options.static_context()->stack().size(),
139 native_type_,
140 options.priority(),
141 options.static_context()->stack().data(),
142 &options.static_context()->tcb());
143 PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
144 native_type_->set_task_handle(task_handle);
145 } else {
146#if !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
147 PW_CRASH(
148 "dynamic thread allocations are not enabled and no static_context "
149 "was provided");
150#else // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
151 // Dynamically allocate the context and the task.
152 native_type_ = new pw::thread::freertos::Context();
153 native_type_->set_dynamically_allocated();
154#if PW_THREAD_JOINING_ENABLED
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800155 const EventGroupHandle_t event_group_handle =
156 xEventGroupCreateStatic(&native_type_->join_event_group());
157 PW_DCHECK_PTR_EQ(event_group_handle,
158 &native_type_->join_event_group(),
159 "Failed to create the joining event group");
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800160#endif // PW_THREAD_JOINING_ENABLED
161
162 // In order to support functions which return and joining, a delegate is
163 // deep copied into the context with a small wrapping function to actually
164 // invoke the task with its arg.
165 native_type_->set_thread_routine(entry, arg);
166 TaskHandle_t task_handle;
167 const BaseType_t result = xTaskCreate(Context::RunThread,
168 options.name(),
169 options.stack_size_words(),
170 native_type_,
171 options.priority(),
172 &task_handle);
173
174 // Ensure it succeeded.
175 PW_CHECK_UINT_EQ(result, pdPASS);
176 native_type_->set_task_handle(task_handle);
177#endif // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
178 }
179}
180
181void Thread::detach() {
Ewout van Bekkum47941cc2021-04-12 15:39:19 -0700182 PW_CHECK(joinable());
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800183
184#if INCLUDE_vTaskSuspend == 1
185 // No need to suspend extra tasks.
186 vTaskSuspend(native_type_->task_handle());
187#else
188 vTaskSuspendAll();
189#endif // INCLUDE_vTaskSuspend == 1
190 native_type_->set_detached();
191 const bool thread_done = native_type_->thread_done();
192#if INCLUDE_vTaskSuspend == 1
193 // No need to suspend extra tasks.
194 vTaskResume(native_type_->task_handle());
195#else
196 vTaskResumeAll();
197#endif // INCLUDE_vTaskSuspend == 1
198
199 if (thread_done) {
200 // The task finished (hit end of Context::RunThread) before we invoked
201 // detach, clean up the thread.
202 Context::TerminateThread(*native_type_);
203 } else {
204 // We're detaching before the task finished, defer cleanup to the task at
205 // the end of Context::RunThread.
206 }
207
208 // Update to no longer represent a thread of execution.
209 native_type_ = nullptr;
210}
211
212#if PW_THREAD_JOINING_ENABLED
213void Thread::join() {
214 PW_CHECK(joinable());
215 PW_CHECK(this_thread::get_id() != get_id());
216
217 // Wait indefinitely until kThreadDoneBit is set.
Ewout van Bekkuma69bcd02021-03-10 08:47:27 -0800218 while (xEventGroupWaitBits(&native_type_->join_event_group(),
Ewout van Bekkumae6c03a2021-01-19 14:53:19 -0800219 kThreadDoneBit,
220 pdTRUE, // Clear the bits.
221 pdFALSE, // Any bits is fine, N/A.
222 portMAX_DELAY) != kThreadDoneBit) {
223 }
224
225 // No need for a critical section here as the thread at this point is
226 // waiting to be terminated.
227 Context::TerminateThread(*native_type_);
228
229 // Update to no longer represent a thread of execution.
230 native_type_ = nullptr;
231}
232#endif // PW_THREAD_JOINING_ENABLED
233
234} // namespace pw::thread