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>
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 57ec637..b35db1e 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -113,6 +113,8 @@
     "$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 bcb8a1f..427af06 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 @@
 		# 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 954615f..f7a090b 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 @@
   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 @@
 }
 
 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 @@
         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 @@
   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 a457978..527b487 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 0000000..0198f4b9
--- /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 0000000..162e3b3
--- /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 0000000..cdd3414
--- /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 0000000..33ab70f
--- /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 0000000..3fb42fe
--- /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 0000000..de91c73
--- /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 0000000..dfa8d21
--- /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 0000000..0cf222b
--- /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 0000000..72befae
--- /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