• 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_preprocessor/compiler.h"
18 #include "pw_thread/deprecated_or_new_thread_function.h"
19 #include "pw_thread/id.h"
20 #include "pw_thread_threadx/config.h"
21 #include "pw_thread_threadx/context.h"
22 #include "pw_thread_threadx/options.h"
23 #include "tx_event_flags.h"
24 
25 using pw::thread::threadx::Context;
26 
27 namespace pw::thread {
28 namespace {
29 #if PW_THREAD_JOINING_ENABLED
30 constexpr ULONG kThreadDoneBit = 1;
31 #endif  // PW_THREAD_JOINING_ENABLED
32 }  // namespace
33 
ThreadEntryPoint(ULONG void_context_ptr)34 void Context::ThreadEntryPoint(ULONG void_context_ptr) {
35   Context& context = *reinterpret_cast<Context*>(void_context_ptr);
36 
37   // Invoke the user's thread function. This may never return.
38   context.fn_();
39   context.fn_ = nullptr;
40 
41   // Raise our preemption threshold as a thread only critical section to guard
42   // against join() and detach().
43   UINT original_preemption_threshold = TX_MAX_PRIORITIES;  // Invalid.
44   UINT preemption_success = tx_thread_preemption_change(
45       &context.tcb(), 0, &original_preemption_threshold);
46   PW_DCHECK_UINT_EQ(TX_SUCCESS,
47                     preemption_success,
48                     "Failed to enter thread critical section");
49   if (context.detached()) {
50     // There is no threadsafe way to re-use detached threads, as there's no way
51     // to invoke tx_thread_delete() from the running thread! Joining MUST be
52     // used for this. However to enable unit test coverage we go ahead and clear
53     // this.
54     context.set_in_use(false);
55 
56 #if PW_THREAD_JOINING_ENABLED
57     // If the thread handle was detached before the thread finished execution,
58     // i.e. got here, then we are responsible for cleaning up the join event
59     // group.
60     const UINT event_group_result =
61         tx_event_flags_delete(&context.join_event_group());
62     PW_DCHECK_UINT_EQ(TX_SUCCESS,
63                       event_group_result,
64                       "Failed to delete the join event group");
65 #endif  // PW_THREAD_JOINING_ENABLED
66 
67     // Note that we do not have to restore our preemption threshold as this
68     // thread is completing execution.
69 
70     // WARNING: The thread at this point continues to be registered with the
71     // kernel in TX_COMPLETED state, as tx_thread_delete cannot be invoked!
72     return;
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   UINT unused = 0;
79   preemption_success = tx_thread_preemption_change(
80       &context.tcb(), original_preemption_threshold, &unused);
81   PW_DCHECK_UINT_EQ(TX_SUCCESS,
82                     preemption_success,
83                     "Failed to leave thread critical section");
84 
85 #if PW_THREAD_JOINING_ENABLED
86   const UINT result =
87       tx_event_flags_set(&context.join_event_group(), kThreadDoneBit, TX_OR);
88   PW_DCHECK_UINT_EQ(TX_SUCCESS, result, "Failed to set the join event");
89 #endif  // PW_THREAD_JOINING_ENABLED
90   return;
91 }
92 
DeleteThread(Context & context)93 void Context::DeleteThread(Context& context) {
94   // Stop the other task first.
95   UINT thread_result = tx_thread_terminate(&context.tcb());
96   PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to terminate the thread");
97 
98   // Delete the thread, removing it out of the kernel.
99   thread_result = tx_thread_delete(&context.tcb());
100   PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to delete the thread");
101 
102   // Mark the context as unused for potential later re-use.
103   context.set_in_use(false);
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   const UINT event_group_result =
109       tx_event_flags_delete(&context.join_event_group());
110   PW_DCHECK_UINT_EQ(
111       TX_SUCCESS, event_group_result, "Failed to delete the join event group");
112 #endif  // PW_THREAD_JOINING_ENABLED
113 }
114 
CreateThread(const threadx::Options & options,DeprecatedOrNewThreadFn && thread_fn)115 void Context::CreateThread(const threadx::Options& options,
116                            DeprecatedOrNewThreadFn&& thread_fn) {
117   // Can't use a context more than once.
118   PW_DCHECK(!in_use());
119 
120   // Reset the state of the static context in case it was re-used.
121   set_in_use(true);
122   set_detached(false);
123   set_thread_done(false);
124 #if PW_THREAD_JOINING_ENABLED
125   static const char* join_event_group_name = "pw::Thread";
126   const UINT event_group_result = tx_event_flags_create(
127       &join_event_group(), const_cast<char*>(join_event_group_name));
128   PW_DCHECK_UINT_EQ(
129       TX_SUCCESS, event_group_result, "Failed to create the join event group");
130 #endif  // PW_THREAD_JOINING_ENABLED
131 
132   // Copy over the thread name.
133   set_name(options.name());
134 
135   // In order to support functions which return and joining, a delegate is
136   // deep copied into the context with a small wrapping function to actually
137   // invoke the task with its arg.
138   set_thread_routine(std::move(thread_fn));
139 
140   const UINT thread_result = tx_thread_create(&tcb(),
141                                               const_cast<char*>(name()),
142                                               Context::ThreadEntryPoint,
143                                               reinterpret_cast<ULONG>(this),
144                                               stack().data(),
145                                               stack().size_bytes(),
146                                               options.priority(),
147                                               options.preemption_threshold(),
148                                               options.time_slice_interval(),
149                                               TX_AUTO_START);
150   PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to create the thread");
151 }
152 
Thread(const thread::Options & facade_options,Function<void ()> && entry)153 Thread::Thread(const thread::Options& facade_options, Function<void()>&& entry)
154     : native_type_(nullptr) {
155   // Cast the generic facade options to the backend specific option of which
156   // only one type can exist at compile time.
157   auto options = static_cast<const threadx::Options&>(facade_options);
158   PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
159   native_type_ = options.context();
160   native_type_->CreateThread(options, std::move(entry));
161 }
162 
Thread(const thread::Options & facade_options,ThreadRoutine entry,void * arg)163 Thread::Thread(const thread::Options& facade_options,
164                ThreadRoutine entry,
165                void* arg)
166     : native_type_(nullptr) {
167   // Cast the generic facade options to the backend specific option of which
168   // only one type can exist at compile time.
169   auto options = static_cast<const threadx::Options&>(facade_options);
170   PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
171   native_type_ = options.context();
172   native_type_->CreateThread(options, DeprecatedFnPtrAndArg{entry, arg});
173 }
174 
detach()175 void Thread::detach() {
176   PW_CHECK(joinable());
177 
178   tx_thread_suspend(&native_type_->tcb());
179   native_type_->set_detached();
180   const bool thread_done = native_type_->thread_done();
181   tx_thread_resume(&native_type_->tcb());
182 
183   if (thread_done) {
184     // The task finished (hit end of Context::ThreadEntryPoint) before we
185     // invoked detach, clean up the thread.
186     Context::DeleteThread(*native_type_);
187   } else {
188     // We're detaching before the task finished, defer cleanup to the task at
189     // the end of Context::ThreadEntryPoint.
190   }
191 
192   // Update to no longer represent a thread of execution.
193   native_type_ = nullptr;
194 }
195 
196 #if PW_THREAD_JOINING_ENABLED
join()197 void Thread::join() {
198   PW_CHECK(joinable());
199   PW_CHECK(this_thread::get_id() != get_id());
200 
201   ULONG actual_flags = 0;
202   const UINT result = tx_event_flags_get(&native_type_->join_event_group(),
203                                          kThreadDoneBit,
204                                          TX_OR_CLEAR,
205                                          &actual_flags,
206                                          TX_WAIT_FOREVER);
207   PW_DCHECK_UINT_EQ(TX_SUCCESS, result, "Failed to get the join event");
208 
209   // No need for a critical section here as the thread at this point is
210   // waiting to be deleted.
211   Context::DeleteThread(*native_type_);
212 
213   // Update to no longer represent a thread of execution.
214   native_type_ = nullptr;
215 }
216 #endif  // PW_THREAD_JOINING_ENABLED
217 
218 }  // namespace pw::thread
219