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