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