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