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