• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/deprecated_or_new_thread_function.h"
20 #include "pw_thread/id.h"
21 #include "pw_thread_freertos/config.h"
22 #include "pw_thread_freertos/context.h"
23 #include "pw_thread_freertos/options.h"
24 #include "task.h"
25 
26 using pw::thread::freertos::Context;
27 
28 namespace pw::thread {
29 namespace {
30 
31 #if (INCLUDE_xTaskGetSchedulerState != 1) && (configUSE_TIMERS != 1)
32 #error "xTaskGetSchedulerState is required for pw::thread::Thread"
33 #endif
34 
35 #if PW_THREAD_JOINING_ENABLED
36 constexpr EventBits_t kThreadDoneBit = 1 << 0;
37 #endif  // PW_THREAD_JOINING_ENABLED
38 }  // namespace
39 
ThreadEntryPoint(void * void_context_ptr)40 void Context::ThreadEntryPoint(void* void_context_ptr) {
41   Context& context = *static_cast<Context*>(void_context_ptr);
42 
43   // Invoke the user's thread function. This may never return.
44   context.fn_();
45   context.fn_ = nullptr;
46 
47   // Use a task only critical section to guard against join() and detach().
48   vTaskSuspendAll();
49   if (context.detached()) {
50     // There is no threadsafe way to re-use detached threads, as there's no way
51     // to signal the vTaskDelete success. Joining MUST be used for this.
52     // However to enable unit test coverage we go ahead and clear this.
53     context.set_task_handle(nullptr);
54 
55 #if PW_THREAD_JOINING_ENABLED
56     // If the thread handle was detached before the thread finished execution,
57     // i.e. got here, then we are responsible for cleaning up the join event
58     // group.
59     vEventGroupDelete(
60         reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
61 #endif  // PW_THREAD_JOINING_ENABLED
62 
63 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
64     // The thread was detached before the task finished, free any allocations
65     // it ran on.
66     if (context.dynamically_allocated()) {
67       delete &context;
68     }
69 #endif  // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
70 
71     // Re-enable the scheduler before we delete this execution.
72     xTaskResumeAll();
73     vTaskDelete(nullptr);
74     PW_UNREACHABLE;
75   }
76 
77   // Otherwise the task finished before the thread was detached or joined, defer
78   // cleanup to Thread's join() or detach().
79   context.set_thread_done();
80   xTaskResumeAll();
81 
82 #if PW_THREAD_JOINING_ENABLED
83   xEventGroupSetBits(
84       reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()),
85       kThreadDoneBit);
86 #endif  // PW_THREAD_JOINING_ENABLED
87 
88   while (true) {
89 #if INCLUDE_vTaskSuspend == 1
90     // Use indefinite suspension when available.
91     vTaskSuspend(nullptr);
92 #else
93     vTaskDelay(portMAX_DELAY);
94 #endif  // INCLUDE_vTaskSuspend == 1
95   }
96   PW_UNREACHABLE;
97 }
98 
TerminateThread(Context & context)99 void Context::TerminateThread(Context& context) {
100   // Stop the other task first.
101   PW_DCHECK_NOTNULL(context.task_handle(), "We shall not delete ourselves!");
102   vTaskDelete(context.task_handle());
103 
104   // Mark the context as unused for potential later re-use.
105   context.set_task_handle(nullptr);
106 
107 #if PW_THREAD_JOINING_ENABLED
108   // Just in case someone abused our API, ensure their use of the event group is
109   // properly handled by the kernel regardless.
110   vEventGroupDelete(
111       reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
112 #endif  // PW_THREAD_JOINING_ENABLED
113 
114 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
115   // Then free any allocations it ran on.
116   if (context.dynamically_allocated()) {
117     delete &context;
118   }
119 #endif  // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
120 }
121 
AddToEventGroup()122 void Context::AddToEventGroup() {
123 #if PW_THREAD_JOINING_ENABLED
124   const EventGroupHandle_t event_group_handle =
125       xEventGroupCreateStatic(&join_event_group());
126   PW_DCHECK_PTR_EQ(event_group_handle,
127                    &join_event_group(),
128                    "Failed to create the joining event group");
129 #endif  // PW_THREAD_JOINING_ENABLED
130 }
131 
CreateThread(const freertos::Options & options,DeprecatedOrNewThreadFn && thread_fn,Context * & native_type_out)132 void Context::CreateThread(const freertos::Options& options,
133                            DeprecatedOrNewThreadFn&& thread_fn,
134                            Context*& native_type_out) {
135   TaskHandle_t task_handle;
136   if (options.static_context() != nullptr) {
137     // Use the statically allocated context.
138     native_type_out = options.static_context();
139     // Can't use a context more than once.
140     PW_DCHECK_PTR_EQ(native_type_out->task_handle(), nullptr);
141     // Reset the state of the static context in case it was re-used.
142     native_type_out->set_detached(false);
143     native_type_out->set_thread_done(false);
144     native_type_out->AddToEventGroup();
145 
146     // In order to support functions which return and joining, a delegate is
147     // deep copied into the context with a small wrapping function to actually
148     // invoke the task with its arg.
149     native_type_out->set_thread_routine(std::move(thread_fn));
150     task_handle = xTaskCreateStatic(Context::ThreadEntryPoint,
151                                     options.name(),
152                                     options.static_context()->stack().size(),
153                                     native_type_out,
154                                     options.priority(),
155                                     options.static_context()->stack().data(),
156                                     &options.static_context()->tcb());
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_out = new pw::thread::freertos::Context();
165     native_type_out->set_dynamically_allocated();
166     native_type_out->AddToEventGroup();
167 
168     // In order to support functions which return and joining, a delegate is
169     // deep copied into the context with a small wrapping function to actually
170     // invoke the task with its arg.
171     native_type_out->set_thread_routine(std::move(thread_fn));
172     const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
173                                           options.name(),
174                                           options.stack_size_words(),
175                                           native_type_out,
176                                           options.priority(),
177                                           &task_handle);
178 
179     // Ensure it succeeded.
180     PW_CHECK_UINT_EQ(result, pdPASS);
181 #endif  // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
182   }
183   PW_CHECK_NOTNULL(task_handle);  // Ensure it succeeded.
184   native_type_out->set_task_handle(task_handle);
185 }
186 
Thread(const thread::Options & facade_options,Function<void ()> && entry)187 Thread::Thread(const thread::Options& facade_options, Function<void()>&& entry)
188     : native_type_(nullptr) {
189   // Cast the generic facade options to the backend specific option of which
190   // only one type can exist at compile time.
191   auto options = static_cast<const freertos::Options&>(facade_options);
192   Context::CreateThread(options, std::move(entry), native_type_);
193 }
194 
Thread(const thread::Options & facade_options,ThreadRoutine routine,void * arg)195 Thread::Thread(const thread::Options& facade_options,
196                ThreadRoutine routine,
197                void* arg)
198     : native_type_(nullptr) {
199   // Cast the generic facade options to the backend specific option of which
200   // only one type can exist at compile time.
201   auto options = static_cast<const freertos::Options&>(facade_options);
202   Context::CreateThread(
203       options, DeprecatedFnPtrAndArg{routine, arg}, native_type_);
204 }
205 
detach()206 void Thread::detach() {
207   PW_CHECK(joinable());
208 
209   // xTaskResumeAll() can only be used after the scheduler has been started.
210   const bool scheduler_initialized =
211       xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED;
212 
213   if (scheduler_initialized) {
214     // We don't want to individually suspend and resume this task using
215     // vTaskResume() as that can cause tasks to prematurely wake up and return
216     // from blocking APIs (b/303885539).
217     vTaskSuspendAll();
218   }
219   native_type_->set_detached();
220   const bool thread_done = native_type_->thread_done();
221   if (scheduler_initialized) {
222     xTaskResumeAll();
223   }
224 
225   if (thread_done) {
226     // The task finished (hit end of Context::ThreadEntryPoint) before we
227     // invoked detach, clean up the thread.
228     Context::TerminateThread(*native_type_);
229   } else {
230     // We're detaching before the task finished, defer cleanup to the task at
231     // the end of Context::ThreadEntryPoint.
232   }
233 
234   // Update to no longer represent a thread of execution.
235   native_type_ = nullptr;
236 }
237 
238 #if PW_THREAD_JOINING_ENABLED
join()239 void Thread::join() {
240   PW_CHECK(joinable());
241   PW_CHECK(this_thread::get_id() != get_id());
242 
243   // Wait indefinitely until kThreadDoneBit is set.
244   while (xEventGroupWaitBits(reinterpret_cast<EventGroupHandle_t>(
245                                  &native_type_->join_event_group()),
246                              kThreadDoneBit,
247                              pdTRUE,   // Clear the bits.
248                              pdFALSE,  // Any bits is fine, N/A.
249                              portMAX_DELAY) != kThreadDoneBit) {
250   }
251 
252   // No need for a critical section here as the thread at this point is
253   // waiting to be terminated.
254   Context::TerminateThread(*native_type_);
255 
256   // Update to no longer represent a thread of execution.
257   native_type_ = nullptr;
258 }
259 #endif  // PW_THREAD_JOINING_ENABLED
260 
261 }  // namespace pw::thread
262