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