You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
8.5 KiB
235 lines
8.5 KiB
// Copyright 2020 The Pigweed Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
// use this file except in compliance with the License. You may obtain a copy of
|
|
// the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations under
|
|
// the License.
|
|
#include "pw_thread/thread.h"
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "pw_assert/assert.h"
|
|
#include "pw_preprocessor/compiler.h"
|
|
#include "pw_thread/id.h"
|
|
#include "pw_thread_freertos/config.h"
|
|
#include "pw_thread_freertos/context.h"
|
|
#include "pw_thread_freertos/options.h"
|
|
#include "task.h"
|
|
|
|
using pw::thread::freertos::Context;
|
|
|
|
namespace pw::thread {
|
|
namespace {
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
constexpr EventBits_t kThreadDoneBit = 1 << 0;
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
} // namespace
|
|
|
|
void Context::RunThread(void* void_context_ptr) {
|
|
Context& context = *static_cast<Context*>(void_context_ptr);
|
|
context.entry_(context.arg_);
|
|
|
|
// Use a task only critical section to guard against join() and detach().
|
|
vTaskSuspendAll();
|
|
if (context.detached()) {
|
|
// There is no threadsafe way to re-use detached threads, as there's no way
|
|
// to signal the vTaskDelete success. Joining MUST be used for this.
|
|
// However to enable unit test coverage we go ahead and clear this.
|
|
context.set_task_handle(nullptr);
|
|
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
// Just in case someone abused our API, ensure their use of the event group
|
|
// is properly handled by the kernel regardless.
|
|
vEventGroupDelete(&context.join_event_group());
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
|
|
#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
// The thread was detached before the task finished, free any allocations
|
|
// it ran on.
|
|
if (context.dynamically_allocated()) {
|
|
delete &context;
|
|
}
|
|
#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
|
|
// Re-enable the scheduler before we delete this execution.
|
|
xTaskResumeAll();
|
|
vTaskDelete(nullptr);
|
|
PW_UNREACHABLE;
|
|
}
|
|
|
|
// Otherwise the task finished before the thread was detached or joined, defer
|
|
// cleanup to Thread's join() or detach().
|
|
context.set_thread_done();
|
|
xTaskResumeAll();
|
|
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
xEventGroupSetBits(&context.join_event_group(), kThreadDoneBit);
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
|
|
while (true) {
|
|
#if INCLUDE_vTaskSuspend == 1
|
|
// Use indefinite suspension when available.
|
|
vTaskSuspend(nullptr);
|
|
#else
|
|
vTaskDelay(portMAX_DELAY);
|
|
#endif // INCLUDE_vTaskSuspend == 1
|
|
}
|
|
PW_UNREACHABLE;
|
|
}
|
|
|
|
void Context::TerminateThread(Context& context) {
|
|
// Stop the other task first.
|
|
PW_DCHECK_NOTNULL(context.task_handle(), "We shall not delete ourselves!");
|
|
vTaskDelete(context.task_handle());
|
|
|
|
// Mark the context as unused for potential later re-use.
|
|
context.set_task_handle(nullptr);
|
|
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
// Just in case someone abused our API, ensure their use of the event group is
|
|
// properly handled by the kernel regardless.
|
|
vEventGroupDelete(&context.join_event_group());
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
|
|
#if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
// Then free any allocations it ran on.
|
|
if (context.dynamically_allocated()) {
|
|
delete &context;
|
|
}
|
|
#endif // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
}
|
|
|
|
Thread::Thread(const thread::Options& facade_options,
|
|
ThreadRoutine entry,
|
|
void* arg)
|
|
: native_type_(nullptr) {
|
|
// Cast the generic facade options to the backend specific option of which
|
|
// only one type can exist at compile time.
|
|
auto options = static_cast<const freertos::Options&>(facade_options);
|
|
if (options.static_context() != nullptr) {
|
|
// Use the statically allocated context.
|
|
native_type_ = options.static_context();
|
|
// Can't use a context more than once.
|
|
PW_DCHECK_PTR_EQ(native_type_->task_handle(), nullptr);
|
|
// Reset the state of the static context in case it was re-used.
|
|
native_type_->set_detached(false);
|
|
native_type_->set_thread_done(false);
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
const EventGroupHandle_t event_group_handle =
|
|
xEventGroupCreateStatic(&native_type_->join_event_group());
|
|
PW_DCHECK_PTR_EQ(event_group_handle,
|
|
&native_type_->join_event_group(),
|
|
"Failed to create the joining event group");
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
|
|
// In order to support functions which return and joining, a delegate is
|
|
// deep copied into the context with a small wrapping function to actually
|
|
// invoke the task with its arg.
|
|
native_type_->set_thread_routine(entry, arg);
|
|
const TaskHandle_t task_handle =
|
|
xTaskCreateStatic(Context::RunThread,
|
|
options.name(),
|
|
options.static_context()->stack().size(),
|
|
native_type_,
|
|
options.priority(),
|
|
options.static_context()->stack().data(),
|
|
&options.static_context()->tcb());
|
|
PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
|
|
native_type_->set_task_handle(task_handle);
|
|
} else {
|
|
#if !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
PW_CRASH(
|
|
"dynamic thread allocations are not enabled and no static_context "
|
|
"was provided");
|
|
#else // PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
// Dynamically allocate the context and the task.
|
|
native_type_ = new pw::thread::freertos::Context();
|
|
native_type_->set_dynamically_allocated();
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
const EventGroupHandle_t event_group_handle =
|
|
xEventGroupCreateStatic(&native_type_->join_event_group());
|
|
PW_DCHECK_PTR_EQ(event_group_handle,
|
|
&native_type_->join_event_group(),
|
|
"Failed to create the joining event group");
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
|
|
// In order to support functions which return and joining, a delegate is
|
|
// deep copied into the context with a small wrapping function to actually
|
|
// invoke the task with its arg.
|
|
native_type_->set_thread_routine(entry, arg);
|
|
TaskHandle_t task_handle;
|
|
const BaseType_t result = xTaskCreate(Context::RunThread,
|
|
options.name(),
|
|
options.stack_size_words(),
|
|
native_type_,
|
|
options.priority(),
|
|
&task_handle);
|
|
|
|
// Ensure it succeeded.
|
|
PW_CHECK_UINT_EQ(result, pdPASS);
|
|
native_type_->set_task_handle(task_handle);
|
|
#endif // !PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
|
|
}
|
|
}
|
|
|
|
void Thread::detach() {
|
|
PW_CHECK(joinable());
|
|
|
|
#if INCLUDE_vTaskSuspend == 1
|
|
// No need to suspend extra tasks.
|
|
vTaskSuspend(native_type_->task_handle());
|
|
#else
|
|
vTaskSuspendAll();
|
|
#endif // INCLUDE_vTaskSuspend == 1
|
|
native_type_->set_detached();
|
|
const bool thread_done = native_type_->thread_done();
|
|
#if INCLUDE_vTaskSuspend == 1
|
|
// No need to suspend extra tasks.
|
|
vTaskResume(native_type_->task_handle());
|
|
#else
|
|
vTaskResumeAll();
|
|
#endif // INCLUDE_vTaskSuspend == 1
|
|
|
|
if (thread_done) {
|
|
// The task finished (hit end of Context::RunThread) before we invoked
|
|
// detach, clean up the thread.
|
|
Context::TerminateThread(*native_type_);
|
|
} else {
|
|
// We're detaching before the task finished, defer cleanup to the task at
|
|
// the end of Context::RunThread.
|
|
}
|
|
|
|
// Update to no longer represent a thread of execution.
|
|
native_type_ = nullptr;
|
|
}
|
|
|
|
#if PW_THREAD_JOINING_ENABLED
|
|
void Thread::join() {
|
|
PW_CHECK(joinable());
|
|
PW_CHECK(this_thread::get_id() != get_id());
|
|
|
|
// Wait indefinitely until kThreadDoneBit is set.
|
|
while (xEventGroupWaitBits(&native_type_->join_event_group(),
|
|
kThreadDoneBit,
|
|
pdTRUE, // Clear the bits.
|
|
pdFALSE, // Any bits is fine, N/A.
|
|
portMAX_DELAY) != kThreadDoneBit) {
|
|
}
|
|
|
|
// No need for a critical section here as the thread at this point is
|
|
// waiting to be terminated.
|
|
Context::TerminateThread(*native_type_);
|
|
|
|
// Update to no longer represent a thread of execution.
|
|
native_type_ = nullptr;
|
|
}
|
|
#endif // PW_THREAD_JOINING_ENABLED
|
|
|
|
} // namespace pw::thread
|