pw_thread_embos: add thread creation for embOS

Adds thread creation support to embOS.

Also updates existing documentation to reflect that this is now
supported.

Change-Id: Id95dbb5f7b283733b7cd071831ddcf95c0c549d0
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/44144
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
diff --git a/docs/module_structure.rst b/docs/module_structure.rst
index 17d3a07..dc2e4b0 100644
--- a/docs/module_structure.rst
+++ b/docs/module_structure.rst
@@ -188,6 +188,8 @@
     BUILD.gn
     README.md
 
+.. _module-structure-compile-time-configuration:
+
 Compile-time configuration
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 Pigweed modules are intended to be used in a wide variety of environments.
diff --git a/docs/os_abstraction_layers.rst b/docs/os_abstraction_layers.rst
index 40238ab..8a0d8d8 100644
--- a/docs/os_abstraction_layers.rst
+++ b/docs/os_abstraction_layers.rst
@@ -27,7 +27,7 @@
   * - `Azure RTOS (formerly ThreadX) <https://azure.microsoft.com/en-us/services/rtos/>`_
     - **✔ Supported**
   * - `SEGGER embOS <https://www.segger.com/products/rtos/embos/>`_
-    - *In Progress*
+    - **✔ Supported**
   * - Baremetal
     - *In Progress*
   * - `Zephyr <https://www.zephyrproject.org/>`_
@@ -313,17 +313,17 @@
     - **Thread Creation**
     - **Thread Id/Sleep/Yield**
   * - FreeRTOS
-    - :ref:`module-pw_sync_freertos`
-    - :ref:`module-pw_sync_freertos`
+    - :ref:`module-pw_thread_freertos`
+    - :ref:`module-pw_thread_freertos`
   * - ThreadX
-    - :ref:`module-pw_sync_threadx`
-    - :ref:`module-pw_sync_threadx`
+    - :ref:`module-pw_thread_threadx`
+    - :ref:`module-pw_thread_threadx`
   * - embOS
-    - Under Development
-    - :ref:`module-pw_sync_embos`
+    - :ref:`module-pw_thread_embos`
+    - :ref:`module-pw_thread_embos`
   * - STL
-    - :ref:`module-pw_sync_stl`
-    - :ref:`module-pw_sync_stl`
+    - :ref:`module-pw_thread_stl`
+    - :ref:`module-pw_thread_stl`
   * - Zephyr
     - Planned
     - Planned
@@ -475,22 +475,21 @@
 That being said, the following concrete areas are being worked on and can be
 expected to land at some point in the future:
 
-1. Thread creation support for embOS is in progress.
-2. We'd like to offer a system clock based timer abstraction facade which can be
+1. We'd like to offer a system clock based timer abstraction facade which can be
    used on either an RTOS or a hardware timer.
-3. We are evaluating a less-portable but very useful portability facade for
+2. We are evaluating a less-portable but very useful portability facade for
    event flags / groups. This would make it even easier to ensure all firmware
    can be fully executed on the host.
-4. Cooperative cancellation thread joining along with a ``std::jhtread`` like
+3. Cooperative cancellation thread joining along with a ``std::jhtread`` like
    wrapper is in progress.
-5. We'd like to add support for queues, message queues, and similar channel
+4. We'd like to add support for queues, message queues, and similar channel
    abstractions which also support interprocessor communication in a transparent
    manner.
-6. We're interested in supporting asynchronous worker queues and worker queue
+5. We're interested in supporting asynchronous worker queues and worker queue
    pools.
-7. Migrate HAL and similar APIs to use deadlines for the backend virtual
+6. Migrate HAL and similar APIs to use deadlines for the backend virtual
    interfaces to permit a smaller vtable which supports both public timeout and
    deadline semantics.
-8. Baremetal support is partially in place today, but it's not ready for use.
-9. Most of our APIs today are focused around synchronous blocking APIs, however
+7. Baremetal support is partially in place today, but it's not ready for use.
+8. Most of our APIs today are focused around synchronous blocking APIs, however
    we would love to extend this to include asynchronous APIs.
diff --git a/pw_thread/docs.rst b/pw_thread/docs.rst
index 99003cd..9b03369 100644
--- a/pw_thread/docs.rst
+++ b/pw_thread/docs.rst
@@ -58,7 +58,7 @@
   * - ThreadX
     - :ref:`module-pw_thread_threadx`
   * - embOS
-    - Planned
+    - :ref:`module-pw_thread_embos`
   * - STL
     - :ref:`module-pw_thread_stl`
   * - Zephyr
diff --git a/pw_thread_embos/BUILD b/pw_thread_embos/BUILD
index 3205087..d0a23e2 100644
--- a/pw_thread_embos/BUILD
+++ b/pw_thread_embos/BUILD
@@ -76,6 +76,59 @@
     # currently do not have Bazel support.
 )
 
+# This target provides the embOS specific headers needs for thread creation.
+pw_cc_library(
+    name = "thread_headers",
+    hdrs = [
+        "public/pw_thread_embos/config.h",
+        "public/pw_thread_embos/context.h",
+        "public/pw_thread_embos/options.h",
+        "public/pw_thread_embos/thread_inline.h",
+        "public/pw_thread_embos/thread_native.h",
+        "public_overrides/pw_thread_backend/thread_inline.h",
+        "public_overrides/pw_thread_backend/thread_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        ":id",
+        "//pw_assert",
+        "//pw_string",
+        "//pw_thread:thread_headers",
+    ],
+    # TODO(pwbug/317): This should depend on embOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "thread",
+    srcs = [
+        "thread.cc",
+    ],
+    deps = [
+        ":id",
+        ":thread_headers",
+        "//pw_assert",
+    ],
+    # TODO(pwbug/317): This should depend on embOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "test_threads",
+    srcs = [
+        "test_threads.cc",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep",
+        "//pw_thread:test_threads_header",
+        "//pw_thread:thread_facade",
+    ],
+)
+
 pw_cc_library(
     name = "yield_headers",
     hdrs = [
diff --git a/pw_thread_embos/BUILD.gn b/pw_thread_embos/BUILD.gn
index 4e76548..383b6a6 100644
--- a/pw_thread_embos/BUILD.gn
+++ b/pw_thread_embos/BUILD.gn
@@ -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_embos_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_embos/config.h" ]
+  public_configs = [ ":public_include_path" ]
+  public_deps = [
+    "$dir_pw_third_party/embos",
+    pw_thread_embos_CONFIG,
+  ]
+}
+
 # This target provides the backend for pw::thread::Id.
 pw_source_set("id") {
   public_configs = [
@@ -77,6 +95,33 @@
   }
 }
 
+# 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_string",
+    "$dir_pw_third_party/embos",
+    "$dir_pw_thread:id",
+    "$dir_pw_thread:thread.facade",
+  ]
+  public = [
+    "public/pw_thread_embos/context.h",
+    "public/pw_thread_embos/options.h",
+    "public/pw_thread_embos/thread_inline.h",
+    "public/pw_thread_embos/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::thread::yield.
 pw_source_set("yield") {
   public_configs = [
@@ -95,6 +140,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_embos:thread"
+  deps = [
+    ":test_threads",
+    "$dir_pw_thread:thread_facade_test",
+  ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_thread_embos/docs.rst b/pw_thread_embos/docs.rst
index cad1908..16de090 100644
--- a/pw_thread_embos/docs.rst
+++ b/pw_thread_embos/docs.rst
@@ -1,8 +1,150 @@
 .. _module-pw_thread_embos:
 
----------------
+===============
 pw_thread_embos
----------------
-This is a set of backends for pw_thread based on embOS v4. It is not ready for
-use, and is under construction.
+===============
+This is a set of backends for pw_thread based on embOS v4.
 
+.. contents::
+   :local:
+   :depth: 1
+
+.. Warning::
+  This module is still under construction, the API is not yet stable.
+
+-----------------------
+Thread Creation Backend
+-----------------------
+A backend or ``pw::thread::Thread`` is offered using ``OS_CreateTaskEx()``.
+Optional joining support is enabled via an ``OS_EVENT`` in each thread's
+context.
+
+This backend permits users to start threads where contexts must be explicitly
+allocated and passed in as an option. As a quick example, a detached thread
+can be created as follows:
+
+.. code-block:: cpp
+
+  #include "pw_thread/detached_thread.h"
+  #include "pw_thread_embos/context.h"
+  #include "pw_thread_embos/options.h"
+  #include "RTOS.h"  // For the embOS types.
+
+  pw::thread::embos::ContextWithStack<42> example_thread_context;
+  void StartExampleThread() {
+    pw::thread::DetachedThread(
+        pw::thread::embos::Options()
+            .set_name("static_example_thread")
+            .set_priority(kFooPriority)
+            .set_time_slice_interval(kFooTimeSliceInterval)
+            .set_context(example_thread_context),
+        example_thread_function)
+  }
+
+
+Module Configuration Options
+============================
+The following configurations can be adjusted via compile-time configuration of
+this module, see the
+:ref:`module documentation <module-structure-compile-time-configuration>` for
+more details.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
+
+  Whether thread joining is enabled. By default this is disabled.
+
+  We suggest only enabling this when thread joining is required to minimize
+  the RAM and ROM cost of threads.
+
+  Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
+  an OS_EVENT to every thread's pw::thread::embos::Context. In addition, there
+  is a minute ROM cost to construct and destroy this added object.
+
+  PW_THREAD_JOINING_ENABLED gets set to this value.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS
+
+  The minimum stack size in words. By default this uses Segger's recommendation
+  of 68 bytes.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
+
+  The default stack size in words. By default this uses Segger's recommendation
+  of 256 bytes to start.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN
+
+  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.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
+
+  The minimum priority level, this is normally 1, since 0 is not a valid
+  priority level.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY
+
+  The default priority level. By default this uses the minimal embOS priority.
+
+.. c:macro:: PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
+
+  The round robin time slice tick interval for threads at the same priority.
+  By default this is set to 2 ticks based on the embOS default.
+
+
+embOS Thread Options
+====================
+.. cpp:class:: pw::thread::embos::Options
+
+  .. cpp:function:: set_name(const char* name)
+
+    Sets the name for the embOS task, this is optional.
+    Note that this will be deep copied into the context and may be truncated
+    based on ``PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN``.
+
+  .. cpp:function:: set_priority(OS_PRIO priority)
+
+    Sets the priority for the embOS task. Higher values are higher priority,
+    see embOS OS_CreateTaskEx for more detail.
+    Precondition: This must be >= ``PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY``.
+
+  .. cpp:function:: set_time_slice_interval(OS_UINT time_slice_interval)
+
+    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.
+
+    A value of 0 disables time-slicing of this thread.
+
+    Precondition: This must be <= 255 ticks.
+
+  .. cpp:function:: set_context(pw::thread::embos::Context& context)
+
+    Set the pre-allocated context (all memory needed to run a thread). Note that
+    this is required for this thread creation backend! The ``Context`` can
+    either be constructed with an externally provided ``std::span<OS_UINT>``
+    stack or the templated form of ``ContextWithStack<kStackSizeWords>`` can
+    be used.
+
+
+-----------------------------
+Thread Identification Backend
+-----------------------------
+A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offerred using
+``OS_GetTaskID()``. It uses ``DASSERT`` to ensure that the scheduler has started
+via ``OS_IsRunning()``.
+
+--------------------
+Thread Sleep Backend
+--------------------
+A backend for ``pw::thread::sleep_for()`` and ``pw::thread::sleep_until()`` is
+offerred using ``OS_Delay()`` if the duration is at least one tick, else
+``OS_Yield()`` is used. It uses ``pw::this_thread::get_id() != thread::Id()`` to
+ensure it invoked only from a thread.
+
+--------------------
+Thread Yield Backend
+--------------------
+A backend for ``pw::thread::yield()`` is offered using via ``OS_Yield()``.
+It uses ``pw::this_thread::get_id() != thread::Id()`` to ensure it invoked only
+from a thread.
diff --git a/pw_thread_embos/public/pw_thread_embos/config.h b/pw_thread_embos/public/pw_thread_embos/config.h
new file mode 100644
index 0000000..f277f27
--- /dev/null
+++ b/pw_thread_embos/public/pw_thread_embos/config.h
@@ -0,0 +1,85 @@
+// 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 "RTOS.h"
+
+// Whether thread joining is enabled. By default this is disabled.
+//
+// We suggest only enabling this when thread joining is required to minimize
+// the RAM and ROM cost of threads.
+//
+// Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
+// an OS_EVENT to every thread's pw::thread::embos::Context. In addition, there
+// is a minute ROM cost to construct and destroy this added object.
+//
+// PW_THREAD_JOINING_ENABLED gets set to this value.
+#ifndef PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
+#define PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED 0
+#endif  // PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
+#define PW_THREAD_JOINING_ENABLED PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
+
+// The minimum stack size in words. By default this uses Segger's recommendation
+// of 68 bytes.
+#ifndef PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS
+#define PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS (68 / sizeof(OS_UINT))
+#endif  // PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS
+
+// The default stack size in words. By default this uses Segger's recommendation
+// of 256 bytes to start.
+#ifndef PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
+#define PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS (256 / sizeof(OS_UINT))
+#endif  // PW_THREAD_EMBOS_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_EMBOS_CONFIG_MAX_THREAD_NAME_LEN
+#define PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN 15
+#endif  // PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN
+
+// The minimum priority level, this is normally 1, since 0 is not a valid
+// priority level.
+#ifndef PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
+#define PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY 1
+#endif  // PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
+
+// The default priority level. By default this uses the minimal embOS priority.
+#ifndef PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY
+#define PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY \
+  PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
+#endif  // PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY
+
+// The round robin time slice tick interval for threads at the same priority.
+// By default this is set to 2 ticks based on the embOS default.
+#ifndef PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
+#define PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL 2
+#endif  // PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
+
+namespace pw::thread::embos::config {
+
+inline constexpr size_t kMaximumNameLength =
+    PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN;
+inline constexpr size_t kMinimumStackSizeWords =
+    PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS;
+inline constexpr size_t kDefaultStackSizeWords =
+    PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS;
+inline constexpr OS_PRIO kMinimumPriority = PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY;
+inline constexpr OS_PRIO kDefaultPriority =
+    PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY;
+inline constexpr OS_PRIO kDefaultTimeSliceInterval =
+    PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL;
+
+}  // namespace pw::thread::embos::config
diff --git a/pw_thread_embos/public/pw_thread_embos/context.h b/pw_thread_embos/public/pw_thread_embos/context.h
new file mode 100644
index 0000000..ffce52f
--- /dev/null
+++ b/pw_thread_embos/public/pw_thread_embos/context.h
@@ -0,0 +1,129 @@
+// 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 <cstring>
+#include <span>
+
+#include "RTOS.h"
+#include "pw_string/util.h"
+#include "pw_thread_embos/config.h"
+
+namespace pw::thread {
+
+class Thread;  // Forward declare Thread which depends on Context.
+
+}  // namespace pw::thread
+
+namespace pw::thread::embos {
+
+// 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::embos::Context example_thread_context(example_thread_stack);
+//   void StartExampleThread() {
+//      pw::thread::DetachedThread(
+//        pw::thread::embos::Options()
+//            .set_name("static_example_thread")
+//            .set_priority(kFooPriority)
+//            .set_context(example_thread_context),
+//        example_thread_function);
+//   }
+class Context {
+ public:
+  explicit Context(std::span<OS_UINT> stack_span)
+      : tcb_{}, stack_span_(stack_span) {}
+  Context(const Context&) = delete;
+  Context& operator=(const Context&) = delete;
+
+  // Intended for unit test & Thread use only.
+  OS_TASK& tcb() { return tcb_; }
+
+ private:
+  friend Thread;
+
+  std::span<OS_UINT> 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) { string::Copy(name, name_); }
+
+  using ThreadRoutine = void (*)(void* arg);
+  void set_thread_routine(ThreadRoutine entry, void* arg) {
+    user_thread_entry_function_ = entry;
+    user_thread_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
+  OS_EVENT& join_event_object() { return event_object_; }
+#endif  // PW_THREAD_JOINING_ENABLED
+
+  static void ThreadEntryPoint(void* void_context_ptr);
+  static void TerminateThread(Context& context);
+
+  OS_TASK tcb_;
+  std::span<OS_UINT> stack_span_;
+
+  ThreadRoutine user_thread_entry_function_ = nullptr;
+  void* user_thread_entry_arg_ = nullptr;
+#if PW_THREAD_JOINING_ENABLED
+  // Note that the embOS life cycle of this event object is managed together
+  // with the thread life cycle, not this object's life cycle.
+  OS_EVENT event_object_;
+#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 delimiter.
+  std::array<char, config::kMaximumNameLength + 1> name_;
+};
+
+// Static thread context allocation including the stack along with the Context.
+//
+// Example usage:
+//
+//   pw::thread::embos::ContextWithStack<42> example_thread_context;
+//   void StartExampleThread() {
+//      pw::thread::DetachedThread(
+//        pw::thread::embos::Options()
+//            .set_name("static_example_thread")
+//            .set_priority(kFooPriority)
+//            .set_context(example_thread_context),
+//        example_thread_function);
+//   }
+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<OS_UINT, kStackSizeWords> stack_storage_;
+};
+
+}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/public/pw_thread_embos/options.h b/pw_thread_embos/public/pw_thread_embos/options.h
new file mode 100644
index 0000000..fb33066
--- /dev/null
+++ b/pw_thread_embos/public/pw_thread_embos/options.h
@@ -0,0 +1,100 @@
+// 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 "RTOS.h"
+#include "pw_assert/assert.h"
+#include "pw_thread/thread.h"
+#include "pw_thread_embos/config.h"
+#include "pw_thread_embos/context.h"
+
+namespace pw::thread::embos {
+
+// pw::thread::Options for FreeRTOS.
+//
+// Example usage:
+//
+//   // Uses the default priority, but specifies a custom name and context.
+//   pw::thread::Thread example_thread(
+//     pw::thread::embos::Options()
+//         .set_name("example_thread"),
+//         .set_context(static_example_thread_context),
+//     example_thread_function);
+//
+//   // Provides the name, priority, and pre-allocated context.
+//   pw::thread::Thread static_example_thread(
+//     pw::thread::embos::Options()
+//         .set_name("static_example_thread")
+//         .set_priority(kFooPriority)
+//         .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 embOS task, this is optional.
+  // Note that this will be deep copied into the context and may be truncated
+  // based on PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN.
+  constexpr Options set_name(const char* name) {
+    name_ = name;
+    return *this;
+  }
+
+  // Sets the priority for the embOS task. Higher values are higher priority,
+  // see embOS OS_CreateTaskEx for more detail.
+  //
+  // Precondition: This must be >= PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY.
+  constexpr Options set_priority(OS_PRIO priority) {
+    PW_DASSERT(priority >= config::kMinimumPriority);
+    priority_ = priority;
+    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.
+  //
+  // A value of 0 disables time-slicing of this thread.
+  //
+  // Precondition: This must be <= 255 ticks.
+  constexpr Options set_time_slice_interval(OS_UINT time_slice_interval) {
+    PW_DASSERT(time_slice_interval <= 255);
+    time_slice_interval_ = time_slice_interval;
+    return *this;
+  }
+
+  // Set the pre-allocated context (all memory needed to run a thread), see the
+  // pw::thread::embos::Context for more detail.
+  constexpr Options set_context(Context& context) {
+    context_ = &context;
+    return *this;
+  }
+
+ private:
+  friend thread::Thread;
+
+  const char* name() const { return name_; }
+  OS_PRIO priority() const { return priority_; }
+  OS_PRIO time_slice_interval() const { return time_slice_interval_; }
+  Context* context() const { return context_; }
+
+  const char* name_ = nullptr;
+  OS_PRIO priority_ = config::kDefaultPriority;
+  OS_PRIO time_slice_interval_ = config::kDefaultTimeSliceInterval;
+  Context* context_ = nullptr;
+};
+
+}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/public/pw_thread_embos/thread_inline.h b/pw_thread_embos/public/pw_thread_embos/thread_inline.h
new file mode 100644
index 0000000..ed8cc4f
--- /dev/null
+++ b/pw_thread_embos/public/pw_thread_embos/thread_inline.h
@@ -0,0 +1,49 @@
+// 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_embos/context.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_embos/public/pw_thread_embos/thread_native.h b/pw_thread_embos/public/pw_thread_embos/thread_native.h
new file mode 100644
index 0000000..ba7a339
--- /dev/null
+++ b/pw_thread_embos/public/pw_thread_embos/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_embos/context.h"
+
+namespace pw::thread::backend {
+
+// The native thread is a pointer to a thread's context.
+using NativeThread = pw::thread::embos::Context*;
+
+// The native thread handle is the same as the NativeThread.
+using NativeThreadHandle = pw::thread::embos::Context*;
+
+}  // namespace pw::thread::backend
diff --git a/pw_thread_embos/public_overrides/pw_thread_backend/thread_inline.h b/pw_thread_embos/public_overrides/pw_thread_backend/thread_inline.h
new file mode 100644
index 0000000..fd5df5c
--- /dev/null
+++ b/pw_thread_embos/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_embos/thread_inline.h"
diff --git a/pw_thread_embos/public_overrides/pw_thread_backend/thread_native.h b/pw_thread_embos/public_overrides/pw_thread_backend/thread_native.h
new file mode 100644
index 0000000..f8dd122
--- /dev/null
+++ b/pw_thread_embos/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_embos/thread_native.h"
diff --git a/pw_thread_embos/test_threads.cc b/pw_thread_embos/test_threads.cc
new file mode 100644
index 0000000..db95071
--- /dev/null
+++ b/pw_thread_embos/test_threads.cc
@@ -0,0 +1,62 @@
+// 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 "RTOS.h"
+#include "pw_assert/check.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_log/log.h"
+#include "pw_thread/sleep.h"
+#include "pw_thread_embos/context.h"
+#include "pw_thread_embos/options.h"
+
+namespace pw::thread::test {
+namespace {
+
+std::array<embos::ContextWithStack<>, 2> thread_contexts;
+
+}  // namespace
+
+const Options& TestOptionsThread0() {
+  static constexpr embos::Options thread_0_options =
+      embos::Options()
+          .set_name("pw::TestThread0")
+          .set_context(thread_contexts[0]);
+  return thread_0_options;
+}
+
+const Options& TestOptionsThread1() {
+  static constexpr embos::Options thread_1_options =
+      embos::Options()
+          .set_name("pw::TestThread1")
+          .set_context(thread_contexts[1]);
+  return thread_1_options;
+}
+
+void WaitUntilDetachedThreadsCleanedUp() {
+  // embOS does not permit us to invoke a callback after the TCB has been
+  // unregistered from the kernel, however it does provide an API to query this
+  // status.
+  for (auto& context : thread_contexts) {
+    while (OS_IsTask(&context.tcb())) {
+      this_thread::sleep_for(
+          chrono::SystemClock::for_at_least(std::chrono::milliseconds(1)));
+    }
+  }
+}
+
+}  // namespace pw::thread::test
diff --git a/pw_thread_embos/thread.cc b/pw_thread_embos/thread.cc
new file mode 100644
index 0000000..d8f95d5
--- /dev/null
+++ b/pw_thread_embos/thread.cc
@@ -0,0 +1,160 @@
+// 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/check.h"
+#include "pw_thread/id.h"
+#include "pw_thread_embos/config.h"
+#include "pw_thread_embos/context.h"
+#include "pw_thread_embos/options.h"
+
+using pw::thread::embos::Context;
+
+namespace pw::thread {
+
+void Context::ThreadEntryPoint(void* void_context_ptr) {
+  Context& context = *reinterpret_cast<Context*>(void_context_ptr);
+
+  // Invoke the user's thread function. This may never return.
+  context.user_thread_entry_function_(context.user_thread_entry_arg_);
+
+  // Use a task only critical section to guard against join() and detach().
+  OS_SuspendAllTasks();
+  if (context.detached()) {
+    // There is no threadsafe way to re-use detached threads. Callbacks
+    // registered through OS_AddOnTerminateHook CANNOT be used for this as they
+    // are invoked before the kernel is done using the task's TCB!
+    // However to enable unit test coverage we go ahead and clear this.
+    context.set_in_use(false);
+
+#if PW_THREAD_JOINING_ENABLED
+    // If the thread handle was detached before the thread finished execution,
+    // i.e. got here, then we are responsible for cleaning up the join event
+    // object.
+    OS_EVENT_Delete(&context.join_event_object());
+#endif  // PW_THREAD_JOINING_ENABLED
+
+    // Re-enable the scheduler before we delete this execution. Note this name
+    // is a bit misleading as reference counting is used.
+    OS_ResumeAllSuspendedTasks();
+    OS_TerminateTask(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();
+  OS_ResumeAllSuspendedTasks();
+
+#if PW_THREAD_JOINING_ENABLED
+  OS_EVENT_Set(&context.join_event_object());
+#endif  // PW_THREAD_JOINING_ENABLED
+
+  // Let the thread handle owner terminate this task when they detach or join.
+  OS_Suspend(nullptr);
+  PW_UNREACHABLE;
+}
+
+void Context::TerminateThread(Context& context) {
+  // Stop the other task first.
+  OS_TerminateTask(&context.tcb());
+
+  // 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.
+  OS_EVENT_Delete(&context.join_event_object());
+#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 embos::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(true);
+  native_type_->set_detached(false);
+  native_type_->set_thread_done(false);
+#if PW_THREAD_JOINING_ENABLED
+  OS_EVENT_CreateEx(&options.context()->join_event_object(),
+                    OS_EVENT_RESET_MODE_AUTO);
+#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);
+
+  OS_CreateTaskEx(&options.context()->tcb(),
+                  native_type_->name(),
+                  options.priority(),
+                  Context::ThreadEntryPoint,
+                  options.context()->stack().data(),
+                  static_cast<OS_UINT>(options.context()->stack().size_bytes()),
+                  options.time_slice_interval(),
+                  options.context());
+}
+
+void Thread::detach() {
+  PW_CHECK(joinable());
+
+  OS_Suspend(&native_type_->tcb());
+  native_type_->set_detached();
+  const bool thread_done = native_type_->thread_done();
+  OS_Resume(&native_type_->tcb());
+
+  if (thread_done) {
+    // The task finished (hit end of Context::ThreadEntryPoint) 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::ThreadEntryPoint.
+  }
+
+  // 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());
+
+  OS_EVENT_Wait(&native_type_->join_event_object());
+
+  // No need for a critical section here as the thread at this point is
+  // waiting to be deleted.
+  Context::TerminateThread(*native_type_);
+
+  // Update to no longer represent a thread of execution.
+  native_type_ = nullptr;
+}
+#endif  // PW_THREAD_JOINING_ENABLED
+
+}  // namespace pw::thread