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