• 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/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 
25 using pw::thread::freertos::Context;
26 
27 namespace pw::thread {
28 namespace {
29 #if PW_THREAD_JOINING_ENABLED
30 constexpr EventBits_t kThreadDoneBit = 1 << 0;
31 #endif  // PW_THREAD_JOINING_ENABLED
32 }  // namespace
33 
ThreadEntryPoint(void * void_context_ptr)34 void Context::ThreadEntryPoint(void* void_context_ptr) {
35   Context& context = *static_cast<Context*>(void_context_ptr);
36 
37   // Invoke the user's thread function. This may never return.
38   context.user_thread_entry_function_(context.user_thread_entry_arg_);
39 
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
49     // 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.
52     vEventGroupDelete(
53         reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
54 #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
76   xEventGroupSetBits(
77       reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()),
78       kThreadDoneBit);
79 #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 
TerminateThread(Context & context)92 void 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.
103   vEventGroupDelete(
104       reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
105 #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 
Thread(const thread::Options & facade_options,ThreadRoutine entry,void * arg)115 Thread::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
131     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");
136 #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 =
143         xTaskCreateStatic(Context::ThreadEntryPoint,
144                           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
162     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");
167 #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;
174     const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
175                                           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 
detach()188 void Thread::detach() {
189   PW_CHECK(joinable());
190 
191 #if (INCLUDE_vTaskSuspend == 1) && (INCLUDE_xTaskGetSchedulerState == 1)
192   // No need to suspend extra tasks.
193   if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
194     vTaskSuspend(native_type_->task_handle());
195   }
196 #else
197   // Safe to suspend all tasks while scheduler is not running.
198   vTaskSuspendAll();
199 #endif  // INCLUDE_vTaskSuspend == 1
200   native_type_->set_detached();
201   const bool thread_done = native_type_->thread_done();
202 #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   }
208 #else
209   // Safe to resume all tasks while scheduler is not running.
210   xTaskResumeAll();
211 #endif  // INCLUDE_vTaskSuspend == 1
212 
213   if (thread_done) {
214     // The task finished (hit end of Context::ThreadEntryPoint) before we
215     // invoked detach, clean up the thread.
216     Context::TerminateThread(*native_type_);
217   } else {
218     // We're detaching before the task finished, defer cleanup to the task at
219     // the end of Context::ThreadEntryPoint.
220   }
221 
222   // Update to no longer represent a thread of execution.
223   native_type_ = nullptr;
224 }
225 
226 #if PW_THREAD_JOINING_ENABLED
join()227 void Thread::join() {
228   PW_CHECK(joinable());
229   PW_CHECK(this_thread::get_id() != get_id());
230 
231   // Wait indefinitely until kThreadDoneBit is set.
232   while (xEventGroupWaitBits(reinterpret_cast<EventGroupHandle_t>(
233                                  &native_type_->join_event_group()),
234                              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
250