aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEwout van Bekkum <ewout@google.com>2021-03-05 15:05:37 -0800
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2021-04-13 00:00:30 +0000
commitf4da489b23c03963af9c82f631674d76f55065b3 (patch)
treed429a77b5122037ffdf945afa11bc70dc84d1a35
parent5a0450d263b7de5e593ed97d4d902b9719977421 (diff)
downloadpigweed-f4da489b23c03963af9c82f631674d76f55065b3.tar.gz
pw_thread_threadx: Enable pw::thread::Thread for ThreadX
Change-Id: Ied43a085786a60d7b1a0681ab51c8d00a0eb0319 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/40381 Reviewed-by: Ewout van Bekkum <ewout@google.com> Reviewed-by: Wyatt Hepler <hepler@google.com> Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com> Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
-rw-r--r--docs/BUILD.gn2
-rw-r--r--pw_thread_threadx/BUILD63
-rw-r--r--pw_thread_threadx/BUILD.gn77
-rw-r--r--pw_thread_threadx/docs.rst27
-rw-r--r--pw_thread_threadx/public/pw_thread_threadx/config.h75
-rw-r--r--pw_thread_threadx/public/pw_thread_threadx/context.h135
-rw-r--r--pw_thread_threadx/public/pw_thread_threadx/options.h141
-rw-r--r--pw_thread_threadx/public/pw_thread_threadx/thread_inline.h50
-rw-r--r--pw_thread_threadx/public/pw_thread_threadx/thread_native.h26
-rw-r--r--pw_thread_threadx/public_overrides/pw_thread_backend/thread_inline.h16
-rw-r--r--pw_thread_threadx/public_overrides/pw_thread_backend/thread_native.h16
-rw-r--r--pw_thread_threadx/test_threads.cc72
-rw-r--r--pw_thread_threadx/thread.cc201
13 files changed, 891 insertions, 10 deletions
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 57ec63704..b35db1e95 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -113,6 +113,8 @@ group("module_docs") {
"$dir_pw_sys_io_baremetal_stm32f429:docs",
"$dir_pw_sys_io_stdio:docs",
"$dir_pw_target_runner:docs",
+ "$dir_pw_thread:docs",
+ "$dir_pw_thread_threadx:docs",
"$dir_pw_tokenizer:docs",
"$dir_pw_toolchain:docs",
"$dir_pw_trace:docs",
diff --git a/pw_thread_threadx/BUILD b/pw_thread_threadx/BUILD
index bcb8a1f43..427af06d9 100644
--- a/pw_thread_threadx/BUILD
+++ b/pw_thread_threadx/BUILD
@@ -1,4 +1,4 @@
-# Copyright 2020 The Pigweed Authors
+# Copyright 2021 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
@@ -15,6 +15,7 @@
load(
"//pw_build:pigweed.bzl",
"pw_cc_library",
+ "pw_cc_test",
)
package(default_visibility = ["//visibility:public"])
@@ -45,6 +46,66 @@ pw_cc_library(
# currently do not have Bazel support.
)
+# This target provides the ThreadX specific headers needs for thread creation.
+pw_cc_library(
+ name = "thread_headers",
+ hdrs = [
+ "public/pw_thread_threadx/context.h",
+ "public/pw_thread_threadx/options.h",
+ "public/pw_thread_threadx/config.h",
+ "public/pw_thread_threadx/thread_inline.h",
+ "public/pw_thread_threadx/thread_native.h",
+ "public_overrides/pw_thread_backend/thread_inline.h",
+ "public_overrides/pw_thread_backend/thread_native.h",
+ ],
+ includes = [
+ "public",
+ "public_overrides",
+ ],
+ deps = [
+ "//pw_assert",
+ ":id",
+ "//pw_thread:thread_headers",
+ ],
+ # TODO(pwbug/317): This should depend on ThreadX but our third parties
+ # currently do not have Bazel support.
+)
+
+pw_cc_library(
+ name = "thread",
+ srcs = [
+ "thread.cc",
+ ],
+ deps = [
+ "//pw_assert",
+ ":id",
+ ":thread_headers",
+ ],
+ # TODO(pwbug/317): This should depend on ThreadX but our third parties
+ # currently do not have Bazel support.
+)
+
+pw_cc_library(
+ name = "test_threads",
+ deps = [
+ "//pw_thread:thread_facade",
+ "//pw_thread:test_threads_header",
+ "//pw_chrono:system_clock",
+ "//pw_thread:sleep",
+ ],
+ srcs = [
+ "test_threads.cc",
+ ]
+)
+
+pw_cc_test(
+ name = "thread_backend_test",
+ deps = [
+ "//pw_thread:thread_facade_test",
+ ":test_threads",
+ ]
+)
+
pw_cc_library(
name = "sleep_headers",
hdrs = [
diff --git a/pw_thread_threadx/BUILD.gn b/pw_thread_threadx/BUILD.gn
index 954615f40..f7a090b1b 100644
--- a/pw_thread_threadx/BUILD.gn
+++ b/pw_thread_threadx/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Pigweed Authors
+# Copyright 2021 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
@@ -14,10 +14,19 @@
import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/module_config.gni")
import("$dir_pw_build/target_types.gni")
import("$dir_pw_chrono/backend.gni")
import("$dir_pw_docgen/docs.gni")
import("$dir_pw_thread/backend.gni")
+import("$dir_pw_unit_test/test.gni")
+
+declare_args() {
+ # The build target that overrides the default configuration options for this
+ # module. This should point to a source set that provides defines through a
+ # public config (which may -include a file or add defines directly).
+ pw_thread_threadx_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
+}
config("public_include_path") {
include_dirs = [ "public" ]
@@ -29,6 +38,15 @@ config("backend_config") {
visibility = [ ":*" ]
}
+pw_source_set("config") {
+ public = [ "public/pw_thread_threadx/config.h" ]
+ public_configs = [ ":public_include_path" ]
+ public_deps = [
+ "$dir_pw_third_party/threadx",
+ pw_thread_threadx_CONFIG,
+ ]
+}
+
# This target provides the backend for pw::thread::Id.
pw_source_set("id") {
public_configs = [
@@ -50,7 +68,7 @@ pw_source_set("id") {
}
if (pw_chrono_SYSTEM_CLOCK_BACKEND != "" && pw_thread_SLEEP_BACKEND != "") {
- # This target provides the backend for pw::thread::sleep_{for,until}.
+ # This target provides the backend for pw::this_thread::sleep_{for,until}.
pw_source_set("sleep") {
public_configs = [
":public_include_path",
@@ -72,12 +90,37 @@ if (pw_chrono_SYSTEM_CLOCK_BACKEND != "" && pw_thread_SLEEP_BACKEND != "") {
pw_thread_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
pw_chrono_SYSTEM_CLOCK_BACKEND ==
"$dir_pw_chrono_threadx:system_clock",
- "The ThreadX pw::thread::sleep_{for,until} backend only works with " +
- "the ThreadX pw::chrono::SystemClock backend.")
+ "The ThreadX pw::this_thread::sleep_{for,until} backend only works with " + "the ThreadX pw::chrono::SystemClock backend.")
}
}
-# This target provides the backend for pw::thread::yield.
+# This target provides the backend for pw::thread::Thread and the headers needed
+# for thread creation.
+pw_source_set("thread") {
+ public_configs = [
+ ":public_include_path",
+ ":backend_config",
+ ]
+ public_deps = [
+ ":config",
+ "$dir_pw_assert",
+ "$dir_pw_third_party/threadx",
+ "$dir_pw_thread:id",
+ "$dir_pw_thread:thread.facade",
+ ]
+ public = [
+ "public/pw_thread_threadx/context.h",
+ "public/pw_thread_threadx/options.h",
+ "public/pw_thread_threadx/thread_inline.h",
+ "public/pw_thread_threadx/thread_native.h",
+ "public_overrides/pw_thread_backend/thread_inline.h",
+ "public_overrides/pw_thread_backend/thread_native.h",
+ ]
+ allow_circular_includes_from = [ "$dir_pw_thread:thread.facade" ]
+ sources = [ "thread.cc" ]
+}
+
+# This target provides the backend for pw::this_thread::yield.
pw_source_set("yield") {
public_configs = [
":public_include_path",
@@ -95,6 +138,30 @@ pw_source_set("yield") {
deps = [ "$dir_pw_thread:yield.facade" ]
}
+pw_test_group("tests") {
+ tests = [ ":thread_backend_test" ]
+}
+
+pw_source_set("test_threads") {
+ public_deps = [ "$dir_pw_thread:test_threads" ]
+ sources = [ "test_threads.cc" ]
+ deps = [
+ "$dir_pw_chrono:system_clock",
+ "$dir_pw_thread:sleep",
+ "$dir_pw_thread:thread",
+ dir_pw_assert,
+ dir_pw_log,
+ ]
+}
+
+pw_test("thread_backend_test") {
+ enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_threadx:thread"
+ deps = [
+ ":test_threads",
+ "$dir_pw_thread:thread_facade_test",
+ ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
}
diff --git a/pw_thread_threadx/docs.rst b/pw_thread_threadx/docs.rst
index a4579781f..527b487e1 100644
--- a/pw_thread_threadx/docs.rst
+++ b/pw_thread_threadx/docs.rst
@@ -1,8 +1,27 @@
.. _module-pw_thread_threadx:
------------------
+=================
pw_thread_threadx
------------------
-This is a set of backends for pw_thread based on ThreadX. It is not ready for
-use, and is under construction.
+=================
+This is a set of backends for pw_thread based on ThreadX.
+.. Warning::
+ This module is still under construction, the API is not yet stable.
+
+.. list-table::
+
+ * - :ref:`module-pw_thread` Facade
+ - Backend Target
+ - Description
+ * - ``pw_thread:id``
+ - ``pw_thread_threadx:id``
+ - Thread identification.
+ * - ``pw_thread:yield``
+ - ``pw_thread_threadx:yield``
+ - Thread scheduler yielding.
+ * - ``pw_thread:sleep``
+ - ``pw_thread_threadx:sleep``
+ - Thread scheduler sleeping.
+ * - ``pw_thread:thread``
+ - ``pw_thread_threadx:thread``
+ - Thread creation.
diff --git a/pw_thread_threadx/public/pw_thread_threadx/config.h b/pw_thread_threadx/public/pw_thread_threadx/config.h
new file mode 100644
index 000000000..0198f4b97
--- /dev/null
+++ b/pw_thread_threadx/public/pw_thread_threadx/config.h
@@ -0,0 +1,75 @@
+// Copyright 2021 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.
+// Configuration macros for the tokenizer module.
+#pragma once
+
+#include "tx_api.h"
+
+// Whether thread joining is enabled. By default this is disabled.
+// When enabled this adds a TX_EVENT_FLAGS_GROUP to every pw::thread::Thread's
+// context.
+#ifndef PW_THREAD_THREADX_CONFIG_JOINING_ENABLED
+#define PW_THREAD_THREADX_CONFIG_JOINING_ENABLED 0
+#endif // PW_THREAD_THREADX_CONFIG_JOINING_ENABLED
+#define PW_THREAD_JOINING_ENABLED PW_THREAD_THREADX_CONFIG_JOINING_ENABLED
+
+// The default stack size in words. By default this uses the minimal ThreadX
+// stack size.
+#ifndef PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS
+#define PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS \
+ TX_MINIMUM_STACK / sizeof(ULONG)
+#endif // PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS
+
+// The maximum length of a thread's name, not including null termination. By
+// default this is arbitrarily set to 15. This results in an array of characters
+// which is this length + 1 bytes in every pw::thread::Thread's context.
+#ifndef PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN
+#define PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN 15
+#endif // PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN
+
+// The round robin time slice tick interval for threads at the same priority.
+// By default this is disabled as not all ports support this, using a value of 0
+// ticks.
+#ifndef PW_THREAD_THREADX_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
+#define PW_THREAD_THREADX_CONFIG_DEFAULT_TIME_SLICE_INTERVAL TX_NO_TIME_SLICE
+#endif // PW_THREAD_THREADX_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
+
+// The minimum priority level, this is normally based on the number of priority
+// levels.
+#ifndef PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
+#define PW_THREAD_THREADX_CONFIG_MIN_PRIORITY TX_MAX_PRIORITIES - 1
+#endif // PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
+
+// The default stack size in words. By default this uses the minimal ThreadX
+// priority level, given that 0 is the highest priority.
+#ifndef PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY
+#define PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY \
+ PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
+#endif // PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY
+
+namespace pw::thread::threadx::config {
+
+inline constexpr size_t kMaximumNameLength =
+ PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN + 1;
+inline constexpr size_t kMinimumStackSizeWords =
+ TX_MINIMUM_STACK / sizeof(ULONG);
+inline constexpr size_t kDefaultStackSizeWords =
+ PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS;
+inline constexpr UINT kMinimumPriority = PW_THREAD_THREADX_CONFIG_MIN_PRIORITY;
+inline constexpr UINT kDefaultPriority =
+ PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY;
+inline constexpr ULONG kDefaultTimeSliceInterval =
+ PW_THREAD_THREADX_CONFIG_DEFAULT_TIME_SLICE_INTERVAL;
+
+} // namespace pw::thread::threadx::config
diff --git a/pw_thread_threadx/public/pw_thread_threadx/context.h b/pw_thread_threadx/public/pw_thread_threadx/context.h
new file mode 100644
index 000000000..162e3b306
--- /dev/null
+++ b/pw_thread_threadx/public/pw_thread_threadx/context.h
@@ -0,0 +1,135 @@
+// Copyright 2021 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.
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <span>
+
+#include "pw_thread_threadx/config.h"
+#include "tx_api.h"
+#include "tx_thread.h"
+
+namespace pw::thread {
+
+class Thread; // Forward declare Thread which depends on Context.
+
+} // namespace pw::thread
+
+namespace pw::thread::threadx {
+
+// Static thread context allocation including the TCB, an event group for
+// joining if enabled, and an external statically allocated stack.
+//
+// Example usage:
+//
+// std::array<ULONG, 42> example_thread_stack;
+// pw::thread::threadx::Context example_thread_context(example_thread_stack);
+// void StartExampleThread() {
+// pw::thread::Thread(
+// pw::thread::threadx::Options()
+// .set_name("static_example_thread")
+// .set_priority(kFooPriority)
+// .set_static_context(example_thread_context),
+// example_thread_function).detach();
+// }
+class Context {
+ public:
+ explicit Context(std::span<ULONG> stack_span)
+ : tcb_{}, stack_span_(stack_span) {}
+ Context(const Context&) = delete;
+ Context& operator=(const Context&) = delete;
+
+ // Intended for unit test & Thread use only.
+ TX_THREAD& tcb() { return tcb_; }
+
+ private:
+ friend Thread;
+
+ std::span<ULONG> stack() { return stack_span_; }
+
+ bool in_use() const { return in_use_; }
+ void set_in_use(bool in_use = true) { in_use_ = in_use; }
+
+ const char* name() const { return name_.data(); }
+ void set_name(const char* name) {
+ strncpy(name_.data(), name, name_.size() - 1);
+ // Forcefully NULL terminate as strncpy does not NULL terminate if the count
+ // limit is hit.
+ name_[name_.size() - 1] = '\0';
+ }
+
+ using ThreadRoutine = void (*)(void* arg);
+ void set_thread_routine(ThreadRoutine entry, void* arg) {
+ entry_ = entry;
+ arg_ = arg;
+ }
+
+ bool detached() const { return detached_; }
+ void set_detached(bool value = true) { detached_ = value; }
+
+ bool thread_done() const { return thread_done_; }
+ void set_thread_done(bool value = true) { thread_done_ = value; }
+
+#if PW_THREAD_JOINING_ENABLED
+ TX_EVENT_FLAGS_GROUP& join_event_group() { return event_group_; }
+#endif // PW_THREAD_JOINING_ENABLED
+
+ static void RunThread(ULONG void_context_ptr);
+ static void DeleteThread(Context& context);
+
+ TX_THREAD tcb_;
+ std::span<ULONG> stack_span_;
+
+ ThreadRoutine entry_ = nullptr;
+ void* arg_ = nullptr;
+#if PW_THREAD_JOINING_ENABLED
+ // Note that the ThreadX life cycle of this event group is managed together
+ // with the thread life cycle, not this object's life cycle.
+ TX_EVENT_FLAGS_GROUP event_group_;
+#endif // PW_THREAD_JOINING_ENABLED
+ bool in_use_ = false;
+ bool detached_ = false;
+ bool thread_done_ = false;
+
+ // The TCB does not have storage for the name, ergo we provide storage for
+ // the thread's name which can be truncated down to just a null delimeter.
+ std::array<char, config::kMaximumNameLength> name_;
+};
+
+// Static thread context allocation including the stack along with the Context.
+//
+// Example usage:
+//
+// pw::thread::threadx::ContextWithStack<42> example_thread_context;
+// void StartExampleThread() {
+// pw::thread::Thread(
+// pw::thread::threadx::Options()
+// .set_name("static_example_thread")
+// .set_priority(kFooPriority)
+// .set_static_context(example_thread_context),
+// example_thread_function).detach();
+// }
+template <size_t kStackSizeWords = config::kDefaultStackSizeWords>
+class ContextWithStack final : public Context {
+ public:
+ constexpr ContextWithStack() : Context(stack_storage_) {
+ static_assert(kStackSizeWords >= config::kMinimumStackSizeWords);
+ }
+
+ private:
+ std::array<ULONG, kStackSizeWords> stack_storage_;
+};
+
+} // namespace pw::thread::threadx
diff --git a/pw_thread_threadx/public/pw_thread_threadx/options.h b/pw_thread_threadx/public/pw_thread_threadx/options.h
new file mode 100644
index 000000000..cdd3414fd
--- /dev/null
+++ b/pw_thread_threadx/public/pw_thread_threadx/options.h
@@ -0,0 +1,141 @@
+// Copyright 2021 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.
+#pragma once
+
+#include "pw_assert/assert.h"
+#include "pw_thread/thread.h"
+#include "pw_thread_threadx/config.h"
+#include "pw_thread_threadx/context.h"
+#include "tx_api.h"
+
+namespace pw::thread::threadx {
+
+// pw::thread::Options for ThreadX.
+//
+// Example usage:
+//
+// // Uses the default priority and time slice interval (which may be
+// // disabled), but specifies a custom name and pre-allocated context.
+// // Note that the preemption threshold is disabled by default.
+// pw::thread::Thread example_thread(
+// pw::thread::threadx::Options()
+// .set_name("example_thread"),
+// .set_context(static_example_thread_context),
+// example_thread_function);
+//
+// // Specifies the name, priority, time slice interval, and pre-allocated
+// // context, but does not use a preemption threshold.
+// pw::thread::Thread static_example_thread(
+// pw::thread::threadx::Options()
+// .set_name("static_example_thread")
+// .set_priority(kFooPriority)
+// .set_time_slice_interval(1)
+// .set_context(static_example_thread_context),
+// example_thread_function);
+//
+class Options : public thread::Options {
+ public:
+ constexpr Options() = default;
+ constexpr Options(const Options&) = default;
+ constexpr Options(Options&& other) = default;
+
+ // Sets the name for the ThreadX thread, note that this will be deep copied
+ // into the context and may be truncated based on
+ // PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN.
+ constexpr Options set_name(const char* name) {
+ name_ = name;
+ return *this;
+ }
+
+ // Sets the priority for the ThreadX thread from 0 through 31, where a value
+ // of 0 represents the highest priority, see ThreadX tx_thread_create for
+ // more detail.
+ constexpr Options set_priority(UINT priority) {
+ PW_DASSERT(priority <= PW_THREAD_THREADX_CONFIG_MIN_PRIORITY);
+ priority_ = priority;
+ return *this;
+ }
+
+ // Optionally sets the preemption threshold for the ThreadX thread from 0
+ // through 31.
+ //
+ // Only priorities higher than this level (i.e. lower number) are allowed to
+ // preempt this thread. In other words this allows the thread to specify the
+ // priority ceiling for disabling preemption. Threads that have a higher
+ // priority than the ceiling are still allowed to preempt while those with
+ // less than the ceiling are not allowed to preempt.
+ //
+ // Not setting the preemption threshold or explicitly specifying a value
+ // equal to the priority disables preemption threshold.
+ //
+ // Time slicing is disabled while the preemption threshold is enabled, i.e.
+ // not equal to the priority, even if a time slice interval was specified.
+ //
+ // The preemption threshold can be adjusted at run time, this only sets the
+ // initial threshold.
+ //
+ // Precondition: preemption_threshold <= priority
+ constexpr Options set_preemption_threshold(UINT preemption_threshold) {
+ PW_DASSERT(preemption_threshold < PW_THREAD_THREADX_CONFIG_MIN_PRIORITY);
+ possible_preemption_threshold_ = preemption_threshold;
+ return *this;
+ }
+
+ // Sets the number of ticks this thread is allowed to run before other ready
+ // threads of the same priority are given a chance to run.
+ //
+ // Time slicing is disabled while the preemption threshold is enabled, i.e.
+ // not equal to the priority, even if a time slice interval was specified.
+ //
+ // A value of TX_NO_TIME_SLICE (a value of 0) disables time-slicing of this
+ // thread.
+ //
+ // Using time slicing results in a slight amount of system overhead, threads
+ // with a unique priority should consider TX_NO_TIME_SLICE.
+ constexpr Options set_time_slice_interval(ULONG time_slice_interval) {
+ time_slice_interval_ = time_slice_interval;
+ return *this;
+ }
+
+ // Set the pre-allocated context (all memory needed to run a thread), see the
+ // pw::thread::threadx::Context for more detail.
+ constexpr Options set_context(Context& context) {
+ context_ = &context;
+ return *this;
+ }
+
+ private:
+ friend thread::Thread;
+ // Note that the default name may end up truncated due to
+ // PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN.
+ static constexpr char kDefaultName[] = "pw::Thread";
+
+ const char* name() const { return name_; }
+ UINT priority() const { return priority_; }
+ UINT preemption_threshold() const {
+ return possible_preemption_threshold_.value_or(priority_);
+ }
+ ULONG time_slice_interval() const { return time_slice_interval_; }
+ Context* context() const { return context_; }
+
+ const char* name_ = kDefaultName;
+ UINT priority_ = config::kDefaultPriority;
+ // A default value cannot be used for the preemption threshold as it would
+ // have to be based on the selected priority.
+ std::optional<UINT> possible_preemption_threshold_ = std::nullopt;
+ ULONG time_slice_interval_ = config::kDefaultTimeSliceInterval;
+ Context* context_ = nullptr;
+};
+
+} // namespace pw::thread::threadx
diff --git a/pw_thread_threadx/public/pw_thread_threadx/thread_inline.h b/pw_thread_threadx/public/pw_thread_threadx/thread_inline.h
new file mode 100644
index 000000000..33ab70f27
--- /dev/null
+++ b/pw_thread_threadx/public/pw_thread_threadx/thread_inline.h
@@ -0,0 +1,50 @@
+// Copyright 2021 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.
+#pragma once
+
+#include <algorithm>
+
+#include "pw_assert/assert.h"
+#include "pw_thread/id.h"
+#include "pw_thread_threadx/config.h"
+#include "pw_thread_threadx/options.h"
+
+namespace pw::thread {
+
+inline Thread::Thread() : native_type_(nullptr) {}
+
+inline Thread& Thread::operator=(Thread&& other) {
+ native_type_ = other.native_type_;
+ other.native_type_ = nullptr;
+ return *this;
+}
+
+inline Thread::~Thread() { PW_DASSERT(native_type_ == nullptr); }
+
+inline Id Thread::get_id() const {
+ if (native_type_ == nullptr) {
+ return Id(nullptr);
+ }
+ return Id(&native_type_->tcb());
+}
+
+inline void Thread::swap(Thread& other) {
+ std::swap(native_type_, other.native_type_);
+}
+
+inline Thread::native_handle_type Thread::native_handle() {
+ return native_type_;
+}
+
+} // namespace pw::thread
diff --git a/pw_thread_threadx/public/pw_thread_threadx/thread_native.h b/pw_thread_threadx/public/pw_thread_threadx/thread_native.h
new file mode 100644
index 000000000..3fb42feaf
--- /dev/null
+++ b/pw_thread_threadx/public/pw_thread_threadx/thread_native.h
@@ -0,0 +1,26 @@
+// Copyright 2021 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.
+#pragma once
+
+#include "pw_thread_threadx/context.h"
+
+namespace pw::thread::backend {
+
+// The native thread is a pointer to a thread's context.
+using NativeThread = pw::thread::threadx::Context*;
+
+// The native thread handle is the same as the NativeThread.
+using NativeThreadHandle = pw::thread::threadx::Context*;
+
+} // namespace pw::thread::backend
diff --git a/pw_thread_threadx/public_overrides/pw_thread_backend/thread_inline.h b/pw_thread_threadx/public_overrides/pw_thread_backend/thread_inline.h
new file mode 100644
index 000000000..de91c73d7
--- /dev/null
+++ b/pw_thread_threadx/public_overrides/pw_thread_backend/thread_inline.h
@@ -0,0 +1,16 @@
+// Copyright 2021 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.
+#pragma once
+
+#include "pw_thread_threadx/thread_inline.h"
diff --git a/pw_thread_threadx/public_overrides/pw_thread_backend/thread_native.h b/pw_thread_threadx/public_overrides/pw_thread_backend/thread_native.h
new file mode 100644
index 000000000..dfa8d2174
--- /dev/null
+++ b/pw_thread_threadx/public_overrides/pw_thread_backend/thread_native.h
@@ -0,0 +1,16 @@
+// Copyright 2021 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.
+#pragma once
+
+#include "pw_thread_threadx/thread_native.h"
diff --git a/pw_thread_threadx/test_threads.cc b/pw_thread_threadx/test_threads.cc
new file mode 100644
index 000000000..0cf222b56
--- /dev/null
+++ b/pw_thread_threadx/test_threads.cc
@@ -0,0 +1,72 @@
+// Copyright 2021 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/test_threads.h"
+
+#include <chrono>
+
+#include "pw_assert/check.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_log/log.h"
+#include "pw_thread/sleep.h"
+#include "pw_thread_threadx/context.h"
+#include "pw_thread_threadx/options.h"
+
+namespace pw::thread::test {
+namespace {
+
+std::array<threadx::ContextWithStack<>, 2> thread_contexts;
+
+} // namespace
+
+const Options& TestOptionsThread0() {
+ static constexpr threadx::Options thread_0_options =
+ threadx::Options()
+ .set_name("pw::TestThread0")
+ .set_context(thread_contexts[0]);
+ return thread_0_options;
+}
+
+const Options& TestOptionsThread1() {
+ static constexpr threadx::Options thread_1_options =
+ threadx::Options()
+ .set_name("pw::TestThread1")
+ .set_context(thread_contexts[1]);
+ return thread_1_options;
+}
+
+void WaitUntilDetachedThreadsCleanedUp() {
+ // ThreadX does not permit the running thread to delete itself, which means
+ // we have to do this to re-use a TCB as otherwise we will be leaking stale
+ // references in the kernel.
+ for (auto& context : thread_contexts) {
+ if (context.tcb().tx_thread_id != TX_THREAD_ID) {
+ // The TCB was either not used or was already deleted. Note that
+ // tx_thread_terminate does NOT clear this state by design.
+ continue;
+ }
+
+ // If the thread was created but has not been deleted, it means that the
+ // thread was detached before it finished. Wait until it is completed.
+ while (context.tcb().tx_thread_state != TX_COMPLETED) {
+ pw::this_thread::sleep_for(
+ chrono::SystemClock::for_at_least(std::chrono::milliseconds(1)));
+ }
+
+ const UINT result = tx_thread_delete(&context.tcb());
+ PW_CHECK_UINT_EQ(TX_SUCCESS, result, "Failed to delete thread");
+ }
+}
+
+} // namespace pw::thread::test
diff --git a/pw_thread_threadx/thread.cc b/pw_thread_threadx/thread.cc
new file mode 100644
index 000000000..72befae66
--- /dev/null
+++ b/pw_thread_threadx/thread.cc
@@ -0,0 +1,201 @@
+// Copyright 2021 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 "pw_assert/assert.h"
+#include "pw_preprocessor/compiler.h"
+#include "pw_thread/id.h"
+#include "pw_thread_threadx/config.h"
+#include "pw_thread_threadx/context.h"
+#include "pw_thread_threadx/options.h"
+#include "tx_event_flags.h"
+
+using pw::thread::threadx::Context;
+
+namespace pw::thread {
+namespace {
+#if PW_THREAD_JOINING_ENABLED
+constexpr ULONG kThreadDoneBit = 1;
+#endif // PW_THREAD_JOINING_ENABLED
+} // namespace
+
+void Context::RunThread(ULONG void_context_ptr) {
+ Context& context = *reinterpret_cast<Context*>(void_context_ptr);
+ context.entry_(context.arg_);
+
+ // Raise our preemption threshold as a thread only critical section to guard
+ // against join() and detach().
+ UINT original_preemption_threshold = TX_MAX_PRIORITIES; // Invalid.
+ UINT preemption_success = tx_thread_preemption_change(
+ &context.tcb(), 0, &original_preemption_threshold);
+ PW_DCHECK_UINT_EQ(TX_SUCCESS,
+ preemption_success,
+ "Failed to enter thread critical section");
+ if (context.detached()) {
+ // There is no threadsafe way to re-use detached threads, as there's no way
+ // to invoke tx_thread_delete() from the running thread! Joining MUST be
+ // used for this. However to enable unit test coverage we go ahead and clear
+ // this.
+ context.set_in_use(false);
+
+#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.
+ const UINT event_group_result =
+ tx_event_flags_delete(&context.join_event_group());
+ PW_DCHECK_UINT_EQ(TX_SUCCESS,
+ event_group_result,
+ "Failed to delete the join event group");
+#endif // PW_THREAD_JOINING_ENABLED
+
+ // Note that we do not have to restore our preemption threshold as this
+ // thread is completing execution.
+
+ // WARNING: The thread at this point continues to be registered with the
+ // kernel in TX_COMPLETED state, as tx_thread_delete cannot be invoked!
+ return;
+ }
+
+ // Otherwise the task finished before the thread was detached or joined, defer
+ // cleanup to Thread's join() or detach().
+ context.set_thread_done();
+ UINT unused = 0;
+ preemption_success = tx_thread_preemption_change(
+ &context.tcb(), original_preemption_threshold, &unused);
+ PW_DCHECK_UINT_EQ(TX_SUCCESS,
+ preemption_success,
+ "Failed to leave thread critical section");
+
+#if PW_THREAD_JOINING_ENABLED
+ const UINT result =
+ tx_event_flags_set(&context.join_event_group(), kThreadDoneBit, TX_OR);
+ PW_DCHECK_UINT_EQ(TX_SUCCESS, result, "Failed to set the join event");
+#endif // PW_THREAD_JOINING_ENABLED
+ return;
+}
+
+void Context::DeleteThread(Context& context) {
+ // Stop the other task first.
+ UINT thread_result = tx_thread_terminate(&context.tcb());
+ PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to terminate the thread");
+
+ // Delete the thread, removing it out of the kernel.
+ thread_result = tx_thread_delete(&context.tcb());
+ PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to delete the thread");
+
+ // Mark the context as unused for potential later re-use.
+ context.set_in_use(false);
+
+#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.
+ const UINT event_group_result =
+ tx_event_flags_delete(&context.join_event_group());
+ PW_DCHECK_UINT_EQ(
+ TX_SUCCESS, event_group_result, "Failed to delete the join event group");
+#endif // PW_THREAD_JOINING_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 threadx::Options&>(facade_options);
+ PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
+ native_type_ = options.context();
+
+ // Can't use a context more than once.
+ PW_DCHECK(!native_type_->in_use());
+
+ // Reset the state of the static context in case it was re-used.
+ native_type_->set_in_use(false);
+ native_type_->set_detached(false);
+ native_type_->set_thread_done(false);
+#if PW_THREAD_JOINING_ENABLED
+ static const char* join_event_group_name = "pw::Thread";
+ const UINT event_group_result =
+ tx_event_flags_create(&options.context()->join_event_group(),
+ const_cast<char*>(join_event_group_name));
+ PW_DCHECK_UINT_EQ(
+ TX_SUCCESS, event_group_result, "Failed to create the join event group");
+#endif // PW_THREAD_JOINING_ENABLED
+
+ // Copy over the thread name.
+ native_type_->set_name(options.name());
+
+ // 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 UINT thread_result =
+ tx_thread_create(&options.context()->tcb(),
+ const_cast<char*>(native_type_->name()),
+ Context::RunThread,
+ reinterpret_cast<ULONG>(native_type_),
+ options.context()->stack().data(),
+ options.context()->stack().size_bytes(),
+ options.priority(),
+ options.preemption_threshold(),
+ options.time_slice_interval(),
+ TX_AUTO_START);
+ PW_CHECK_UINT_EQ(TX_SUCCESS, thread_result, "Failed to create the thread");
+}
+
+void Thread::detach() {
+ PW_CHECK(joinable());
+
+ tx_thread_suspend(&native_type_->tcb());
+ native_type_->set_detached();
+ const bool thread_done = native_type_->thread_done();
+ tx_thread_resume(&native_type_->tcb());
+
+ if (thread_done) {
+ // The task finished (hit end of Context::RunThread) before we invoked
+ // detach, clean up the thread.
+ Context::DeleteThread(*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());
+
+ ULONG actual_flags = 0;
+ const UINT result = tx_event_flags_get(&native_type_->join_event_group(),
+ kThreadDoneBit,
+ TX_OR_CLEAR,
+ &actual_flags,
+ TX_WAIT_FOREVER);
+ PW_DCHECK_UINT_EQ(TX_SUCCESS, result, "Failed to get the join event");
+
+ // No need for a critical section here as the thread at this point is
+ // waiting to be deleted.
+ Context::DeleteThread(*native_type_);
+
+ // Update to no longer represent a thread of execution.
+ native_type_ = nullptr;
+}
+#endif // PW_THREAD_JOINING_ENABLED
+
+} // namespace pw::thread