• 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/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 
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 
RunThread(void * void_context_ptr)34 void 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.
49     vEventGroupDelete(&context.join_event_group());
50 #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
72   xEventGroupSetBits(&context.join_event_group(), kThreadDoneBit);
73 #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 
TerminateThread(Context & context)86 void 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.
97   vEventGroupDelete(&context.join_event_group());
98 #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 
Thread(const thread::Options & facade_options,ThreadRoutine entry,void * arg)108 Thread::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
124     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");
129 #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
155     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");
160 #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 
detach()181 void Thread::detach() {
182   PW_CHECK(joinable());
183 
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
join()213 void Thread::join() {
214   PW_CHECK(joinable());
215   PW_CHECK(this_thread::get_id() != get_id());
216 
217   // Wait indefinitely until kThreadDoneBit is set.
218   while (xEventGroupWaitBits(&native_type_->join_event_group(),
219                              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
235