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