• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 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 "pw_assert/check.h"
17 #include "pw_thread/id.h"
18 #include "pw_thread_embos/config.h"
19 #include "pw_thread_embos/context.h"
20 #include "pw_thread_embos/options.h"
21 
22 using pw::thread::embos::Context;
23 
24 namespace pw::thread {
25 
ThreadEntryPoint(void * void_context_ptr)26 void Context::ThreadEntryPoint(void* void_context_ptr) {
27   Context& context = *reinterpret_cast<Context*>(void_context_ptr);
28 
29   // Invoke the user's thread function. This may never return.
30   context.fn_();
31   context.fn_ = nullptr;
32 
33   // Use a task only critical section to guard against join() and detach().
34   OS_SuspendAllTasks();
35   if (context.detached()) {
36     // There is no threadsafe way to re-use detached threads. Callbacks
37     // registered through OS_AddOnTerminateHook CANNOT be used for this as they
38     // are invoked before the kernel is done using the task's TCB!
39     // However to enable unit test coverage we go ahead and clear this.
40     context.set_in_use(false);
41 
42 #if PW_THREAD_JOINING_ENABLED
43     // If the thread handle was detached before the thread finished execution,
44     // i.e. got here, then we are responsible for cleaning up the join event
45     // object.
46     OS_EVENT_Delete(&context.join_event_object());
47 #endif  // PW_THREAD_JOINING_ENABLED
48 
49     // Re-enable the scheduler before we delete this execution. Note this name
50     // is a bit misleading as reference counting is used.
51     OS_ResumeAllSuspendedTasks();
52     OS_TerminateTask(nullptr);
53     PW_UNREACHABLE;
54   }
55 
56   // Otherwise the task finished before the thread was detached or joined, defer
57   // cleanup to Thread's join() or detach().
58   context.set_thread_done();
59   OS_ResumeAllSuspendedTasks();
60 
61 #if PW_THREAD_JOINING_ENABLED
62   OS_EVENT_Set(&context.join_event_object());
63 #endif  // PW_THREAD_JOINING_ENABLED
64 
65   // Let the thread handle owner terminate this task when they detach or join.
66   OS_Suspend(nullptr);
67   PW_UNREACHABLE;
68 }
69 
TerminateThread(Context & context)70 void Context::TerminateThread(Context& context) {
71   // Stop the other task first.
72   OS_TerminateTask(&context.tcb());
73 
74   // Mark the context as unused for potential later re-use.
75   context.set_in_use(false);
76 
77 #if PW_THREAD_JOINING_ENABLED
78   // Just in case someone abused our API, ensure their use of the event group is
79   // properly handled by the kernel regardless.
80   OS_EVENT_Delete(&context.join_event_object());
81 #endif  // PW_THREAD_JOINING_ENABLED
82 }
83 
CreateThread(const embos::Options & options,DeprecatedOrNewThreadFn && thread_fn)84 void Context::CreateThread(const embos::Options& options,
85                            DeprecatedOrNewThreadFn&& thread_fn) {
86   // Can't use a context more than once.
87   PW_DCHECK(!in_use());
88 
89   // Reset the state of the static context in case it was re-used.
90   set_in_use(true);
91   set_detached(false);
92   set_thread_done(false);
93 #if PW_THREAD_JOINING_ENABLED
94   OS_EVENT_CreateEx(&join_event_object(), OS_EVENT_RESET_MODE_AUTO);
95 #endif  // PW_THREAD_JOINING_ENABLED
96 
97   // Copy over the thread name.
98   set_name(options.name());
99 
100   // In order to support functions which return and joining, a delegate is
101   // deep copied into the context with a small wrapping function to actually
102   // invoke the task with its arg.
103   set_thread_routine(std::move(thread_fn));
104 
105   OS_CreateTaskEx(&tcb(),
106                   name(),
107                   options.priority(),
108                   Context::ThreadEntryPoint,
109                   stack().data(),
110                   static_cast<OS_UINT>(stack().size_bytes()),
111                   options.time_slice_interval(),
112                   this);
113 }
114 
Thread(const thread::Options & facade_options,Function<void ()> && entry)115 Thread::Thread(const thread::Options& facade_options, Function<void()>&& entry)
116     : native_type_(nullptr) {
117   // Cast the generic facade options to the backend specific option of which
118   // only one type can exist at compile time.
119   auto options = static_cast<const embos::Options&>(facade_options);
120   PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
121   native_type_ = options.context();
122   native_type_->CreateThread(options, std::move(entry));
123 }
124 
Thread(const thread::Options & facade_options,ThreadRoutine entry,void * arg)125 Thread::Thread(const thread::Options& facade_options,
126                ThreadRoutine entry,
127                void* arg)
128     : native_type_(nullptr) {
129   // Cast the generic facade options to the backend specific option of which
130   // only one type can exist at compile time.
131   auto options = static_cast<const embos::Options&>(facade_options);
132   PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
133   native_type_ = options.context();
134   native_type_->CreateThread(options, DeprecatedFnPtrAndArg{entry, arg});
135 }
136 
detach()137 void Thread::detach() {
138   PW_CHECK(joinable());
139 
140   OS_Suspend(&native_type_->tcb());
141   native_type_->set_detached();
142   const bool thread_done = native_type_->thread_done();
143   OS_Resume(&native_type_->tcb());
144 
145   if (thread_done) {
146     // The task finished (hit end of Context::ThreadEntryPoint) before we
147     // invoked detach, clean up the thread.
148     Context::TerminateThread(*native_type_);
149   } else {
150     // We're detaching before the task finished, defer cleanup to the task at
151     // the end of Context::ThreadEntryPoint.
152   }
153 
154   // Update to no longer represent a thread of execution.
155   native_type_ = nullptr;
156 }
157 
158 #if PW_THREAD_JOINING_ENABLED
join()159 void Thread::join() {
160   PW_CHECK(joinable());
161   PW_CHECK(this_thread::get_id() != get_id());
162 
163   OS_EVENT_Wait(&native_type_->join_event_object());
164 
165   // No need for a critical section here as the thread at this point is
166   // waiting to be deleted.
167   Context::TerminateThread(*native_type_);
168 
169   // Update to no longer represent a thread of execution.
170   native_type_ = nullptr;
171 }
172 #endif  // PW_THREAD_JOINING_ENABLED
173 
174 }  // namespace pw::thread
175