pw_chrono: adds the SystemTimer to schedule callbacks

Adds the pw::chrono::SystemTimer facade with STL backend to
schedule callbacks at a specified time in the future.

Change-Id: I2d9f3fe52d214c1ec0ddc152d1450a414339f59e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/49342
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
diff --git a/docs/build_system.rst b/docs/build_system.rst
index b8eb09f..8367ec1 100644
--- a/docs/build_system.rst
+++ b/docs/build_system.rst
@@ -817,39 +817,39 @@
    |                            (Actual backend)
    |                                               ^
    |                                               |
-   |                            @pigweed//pw_chrono:backend_multiplexer
+   |                            @pigweed//pw_chrono:system_clock_backend_multiplexer
    |                            Select backend based on OS:
    |                            [FreeRTOS (X), Embos ( ), STL ( ), Threadx ( )]
    |                                               ^
    |                                               |
-  @pigweed//pw_chrono  -------> @pigweed_config//:pw_chrono_backend
+  @pigweed//pw_chrono  -------> @pigweed_config//:pw_chrono_system_clock_backend
    ^                            (Injectable)
    |
   //:time_is_relative
 
 So when evaluating this setup Bazel checks the dependencies for '//pw_chrono'
-and finds that it depends on "@pigweed_config//:pw_chrono_backend" which looks
+and finds that it depends on "@pigweed_config//:pw_chrono_system_clock_backend" which looks
 like this;
 
 .. code:: py
 
   # pw_chrono config.
   label_flag(
-      name = "pw_chrono_backend",
-      build_setting_default = "@pigweed//pw_chrono:backend_multiplexer",
+      name = "pw_chrono_system_clock_backend",
+      build_setting_default = "@pigweed//pw_chrono:system_clock_backend_multiplexer",
   )
 
 Looking at the 'build_setting_default' we can see that by default it depends
-back on the target "@pigweed//pw_chrono:backend_multiplexer". If you only had one
-backend you could actually just change the 'build_setting_default' to point
-directly to your backend. However because we have four different backends we
-have to use the select semantics to choose the right one. In this case it looks
-like;
+back on the target "@pigweed//pw_chrono:system_clock_backend_multiplexer". If
+you only had one backend you could actually just change the
+'build_setting_default' to point directly to your backend. However because we
+have four different backends we have to use the select semantics to choose the
+right one. In this case it looks like;
 
 .. code:: py
 
   pw_cc_library(
-    name = "backend_multiplexer",
+    name = "system_clock_backend_multiplexer",
     visibility = ["@pigweed_config//:__pkg__"],
     deps = select({
         "@pigweed//pw_build/constraints/rtos:freertos":
@@ -875,7 +875,7 @@
 .. code:: sh
 
   bazel build //:time_is_relative \
-    --@pigweed_config//pw_chrono_backend=//pw_chrono_my_hardware_rtc:system_clock
+    --@pigweed_config//pw_chrono_system_clock_backend=//pw_chrono_my_hardware_rtc:system_clock
 
 This temporarily modifies the build graph to look something like this;
 
@@ -887,7 +887,7 @@
    |                      (Actual backend)
    |                                         ^
    |                                         |
-  @pigweed//pw_chrono  -> @pigweed_config//:pw_chrono_backend
+  @pigweed//pw_chrono  -> @pigweed_config//:pw_chrono_system_clock_backend
    ^                      (Injectable)
    |
   //:time_is_relative
@@ -907,10 +907,10 @@
 need two separate implementations for our pw_chrono facade. Let's say we choose
 to keep the primary flight computer using the hardware RTC and switch the backup
 computer over to use Pigweeds default FreeRTOS backend. In this case we might,
-want to do something similar to '@pigweed//pw_chrono:backend_multiplexer' and
-create selectable dependencies for the two different computers. Now because
-there are no default constraint_setting's that meet our requirements we are
-going to have to;
+want to do something similar to
+'@pigweed//pw_chrono:system_clock_backend_multiplexer' and create selectable
+dependencies for the two different computers. Now because there are no default
+constraint_setting's that meet our requirements we are going to have to;
 
 1. Create a constraint_setting and a set of constraint_value's for the flight
    computer. For example;
@@ -957,7 +957,7 @@
     load("//pw_build:pigweed.bzl", "pw_cc_library")
 
     pw_cc_library(
-      name = "backend_multiplexer",
+      name = "system_clock_backend_multiplexer",
       deps = select({
         "//platforms/flight_computer:primary": [
           "//pw_chrono_my_hardware_rtc:system_clock",
@@ -973,7 +973,8 @@
 
 4. Copy and paste across the target/default_config.BUILD across from the
    Pigweed repository and modifying the build_setting_default for the target
-   'pw_chrono_backend' to point to your new backend_multiplexer target. For example;
+   'pw_chrono_system_clock_backend' to point to your new system_clock_backend_multiplexer
+   target. For example;
 
    This;
 
@@ -981,8 +982,8 @@
 
     # @pigweed//target:default_config.BUILD
     label_flag(
-        name = "pw_chrono_backend",
-        build_setting_default = "@pigweed//pw_chrono:backend_multiplexer",
+        name = "pw_chrono_system_clock_backend",
+        build_setting_default = "@pigweed//pw_chrono:system_clock_backend_multiplexer",
     )
 
   Becomes this;
@@ -991,9 +992,9 @@
 
     # @your_workspace//target:your_config.BUILD
     label_flag(
-      name = "pw_chrono_backend",
+      name = "pw_chrono_system_clock_backend",
       build_setting_default =
-        "@your_workspace//pw_chrono:backend_multiplexer",
+        "@your_workspace//pw_chrono:system_clock_backend_multiplexer",
     )
 
 5. Switch your workspace 'pigweed_config' rule over to use your custom config.
@@ -1023,12 +1024,12 @@
    |                     (Actual backend)
    |                                        ^
    |                                        |
-   |                     @your_workspace//pw_chrono:backend_multiplexer
+   |                     @your_workspace//pw_chrono:system_clock_backend_multiplexer
    |                     Select backend based on OS:
    |                     [Primary (X), Backup ( ), Host only default ( )]
    |                                        ^
    |                                        |
-  @pigweed//pw_chrono -> @pigweed_config//:pw_chrono_backend
+  @pigweed//pw_chrono -> @pigweed_config//:pw_chrono_system_clock_backend
    ^                     (Injectable)
    |
   //:time_is_relative
diff --git a/pw_chrono/BUILD b/pw_chrono/BUILD
index d4d7860..eb1f183 100644
--- a/pw_chrono/BUILD
+++ b/pw_chrono/BUILD
@@ -51,12 +51,12 @@
     ],
     deps = [
         ":system_clock_facade",
-        "@pigweed_config//:pw_chrono_backend",
+        "@pigweed_config//:pw_chrono_system_clock_backend",
     ],
 )
 
 pw_cc_library(
-    name = "backend_multiplexer",
+    name = "system_clock_backend_multiplexer",
     visibility = ["@pigweed_config//:__pkg__"],
     deps = select({
         "//pw_build/constraints/rtos:freertos": ["//pw_chrono_freertos:system_clock"],
@@ -67,6 +67,34 @@
 )
 
 pw_cc_library(
+  name = "system_timer_facade",
+  hdrs = [
+      "public/pw_chrono/system_timer.h"
+  ],
+  includes = ["public"],
+  deps = [
+      ":system_clock",
+      "//pw_function",
+  ],
+)
+
+pw_cc_library(
+    name = "system_timer",
+    deps = [
+        ":system_timer_facade",
+        "@pigweed_config//:pw_chrono_system_timer_backend",
+    ],
+)
+
+pw_cc_library(
+    name = "system_timer_backend_multiplexer",
+    visibility = ["@pigweed_config//:__pkg__"],
+    deps = select({
+        "//conditions:default": ["//pw_chrono_stl:system_timer"],
+    }),
+)
+
+pw_cc_library(
     name = "simulated_system_clock",
     hdrs = [
         "public/pw_chrono/simulated_system_clock.h",
@@ -100,3 +128,16 @@
         "//pw_unit_test",
     ],
 )
+
+pw_cc_test(
+    name = "system_timer_facade_test",
+    srcs = [
+        "system_timer_facade_test.cc",
+    ],
+    deps = [
+        ":system_clock",
+        ":system_timer",
+        "//pw_sync:thread_notification",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_chrono/BUILD.gn b/pw_chrono/BUILD.gn
index 33176db..433ab90 100644
--- a/pw_chrono/BUILD.gn
+++ b/pw_chrono/BUILD.gn
@@ -43,6 +43,16 @@
   sources = [ "system_clock.cc" ]
 }
 
+pw_facade("system_timer") {
+  backend = pw_chrono_SYSTEM_TIMER_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_chrono/system_timer.h" ]
+  public_deps = [
+    ":system_clock",
+    "$dir_pw_function",
+  ]
+}
+
 # Dependency injectable implementation of pw::chrono::SystemClock::Interface.
 pw_source_set("simulated_system_clock") {
   public_configs = [ ":public_include_path" ]
@@ -57,6 +67,7 @@
   tests = [
     ":simulated_system_clock_test",
     ":system_clock_facade_test",
+    ":system_timer_facade_test",
   ]
 }
 
@@ -79,6 +90,16 @@
   ]
 }
 
+pw_test("system_timer_facade_test") {
+  enable_if = pw_chrono_SYSTEM_TIMER_BACKEND != ""
+  sources = [ "system_timer_facade_test.cc" ]
+  deps = [
+    ":system_timer",
+    "$dir_pw_sync:thread_notification",
+    pw_chrono_SYSTEM_TIMER_BACKEND,
+  ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono/backend.gni b/pw_chrono/backend.gni
index f5f80e8..526609f 100644
--- a/pw_chrono/backend.gni
+++ b/pw_chrono/backend.gni
@@ -15,4 +15,7 @@
 declare_args() {
   # Backend for the pw_chrono module's system_clock.
   pw_chrono_SYSTEM_CLOCK_BACKEND = ""
+
+  # Backend for the pw_chrono module's system_timer.
+  pw_chrono_SYSTEM_TIMER_BACKEND = ""
 }
diff --git a/pw_chrono/docs.rst b/pw_chrono/docs.rst
index 62cc03b..1c4815f 100644
--- a/pw_chrono/docs.rst
+++ b/pw_chrono/docs.rst
@@ -1,8 +1,8 @@
 .. _module-pw_chrono:
 
----------
+=========
 pw_chrono
----------
+=========
 Pigweed's chrono module provides facilities for applications to deal with time,
 leveraging many pieces of STL's the ``std::chrono`` library but with a focus
 on portability for constrained embedded devices and maintaining correctness.
@@ -22,6 +22,7 @@
   This module is still under construction, the API is not yet stable. Also the
   documentation is incomplete.
 
+------------------
 SystemClock facade
 ------------------
 The ``pw::chrono::SystemClock`` is meant to serve as the clock used for time
@@ -30,3 +31,130 @@
 points and durations. This means users do not have to worry about clock overflow
 risk as long as rational durations and time points as used, i.e. within a range
 of ±292 years.
+
+------------------
+SystemTimer facade
+------------------
+The SystemTimer facade enables deferring execution of a callback until a later
+time. For example, enabling low power mode after a period of inactivity.
+
+The base SystemTimer only supports a one-shot style timer with a callback.
+A periodic timer can be implemented by rescheduling the timer in the callback
+through ``InvokeAt(kDesiredPeriod + expired_deadline)``.
+
+When implementing a periodic layer on top, the user should be mindful of
+handling missed periodic callbacks. They could opt to invoke the callback
+multiple times with the expected ``expired_deadline`` values or instead saturate
+and invoke the callback only once with the latest ``expired_deadline``.
+
+The entire API is thread safe, however it is NOT always IRQ safe.
+
+The ExpiryCallback is either invoked from a high priority thread or an
+interrupt. Ergo ExpiryCallbacks should be treated as if they are executed by an
+interrupt, meaning:
+
+ * Processing inside of the callback should be kept to a minimum.
+
+ * Callbacks should never attempt to block.
+
+ * APIs which are not interrupt safe such as pw::sync::Mutex should not be used!
+
+C++
+---
+.. cpp:class:: pw::chrono::SystemTimer
+
+  .. cpp:function:: SystemTimer(ExpiryCallback callback)
+
+    Constructs the SystemTimer based on the user provided
+    ``pw::Function<void(SystemClock::time_point expired_deadline)>``. Note that
+    The ExpiryCallback is either invoked from a high priority thread or an
+    interrupt.
+
+    .. note::
+      For a given timer instance, its ExpiryCallback will not preempt itself.
+      This makes it appear like there is a single executor of a timer instance's
+      ExpiryCallback.
+
+  .. cpp:function:: void InvokeAfter(chrono::SystemClock::duration delay)
+
+    Invokes the expiry callback as soon as possible after at least the
+    specified duration.
+
+    Scheduling a callback cancels the existing callback (if pending).
+    If the callback is already being executed while you reschedule it, it will
+    finish callback execution to completion. You are responsible for any
+    critical section locks which may be needed for timer coordination.
+
+    This is thread safe, it may not be IRQ safe.
+
+  .. cpp:function:: void InvokeAt(chrono::SystemClock::time_point timestamp)
+
+    Invokes the expiry callback as soon as possible starting at the specified
+    time_point.
+
+    Scheduling a callback cancels the existing callback (if pending).
+    If the callback is already being executed while you reschedule it, it will
+    finish callback execution to completion. You are responsible for any
+    critical section locks which may be needed for timer coordination.
+
+    This is thread safe, it may not be IRQ safe.
+
+  .. cpp:function:: void Cancel()
+
+    Cancels the software timer expiry callback if pending.
+
+    Canceling a timer which isn't scheduled does nothing.
+
+    If the callback is already being executed while you cancel it, it will
+    finish callback execution to completion. You are responsible for any
+    synchronization which is needed for thread safety.
+
+    This is thread safe, it may not be IRQ safe.
+
+  .. list-table::
+
+    * - *Safe to use in context*
+      - *Thread*
+      - *Interrupt*
+      - *NMI*
+    * - ``SystemTimer::SystemTimer``
+      - ✔
+      -
+      -
+    * - ``SystemTimer::~SystemTimer``
+      - ✔
+      -
+      -
+    * - ``void SystemTimer::InvokeAfter``
+      - ✔
+      -
+      -
+    * - ``void SystemTimer::InvokeAt``
+      - ✔
+      -
+      -
+    * - ``void SystemTimer::Cancel``
+      - ✔
+      -
+      -
+
+Examples in C++
+^^^^^^^^^^^^^^^
+
+.. code-block:: cpp
+
+  #include "pw_chrono/system_clock.h"
+  #include "pw_chrono/system_timer.h"
+  #include "pw_log/log.h"
+
+  using namespace std::chrono_literals;
+
+  void DoFoo(pw::chrono::SystemClock::time_point expired_deadline) {
+    PW_LOG_INFO("Timer callback invoked!");
+  }
+
+  pw::chrono::SystemTimer foo_timer(DoFoo);
+
+  void DoFooLater() {
+    foo.InvokeAfter(42ms);  // DoFoo will be invoked after 42ms.
+  }
diff --git a/pw_chrono/public/pw_chrono/system_timer.h b/pw_chrono/public/pw_chrono/system_timer.h
new file mode 100644
index 0000000..7282788
--- /dev/null
+++ b/pw_chrono/public/pw_chrono/system_timer.h
@@ -0,0 +1,104 @@
+// 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_chrono/system_clock.h"
+#include "pw_chrono_backend/system_timer_native.h"
+#include "pw_function/function.h"
+
+namespace pw::chrono {
+
+// The SystemTimer allows an ExpiryCallback be executed at a set time in the
+// future.
+//
+// The base SystemTimer only supports a one-shot style timer with a callback.
+// A periodic timer can be implemented by rescheduling the timer in the callback
+// through InvokeAt(kDesiredPeriod + expired_deadline).
+//
+// When implementing a periodic layer on top, the user should be mindful of
+// handling missed periodic callbacks. They could opt to invoke the callback
+// multiple times with the expected expired_deadline values or instead
+// saturate and invoke the callback only once with the latest expired_deadline.
+//
+// The entire API is thread safe, however it is NOT always IRQ safe.
+class SystemTimer {
+ public:
+  using native_handle_type = backend::NativeSystemTimerHandle;
+
+  // The ExpiryCallback is either invoked from a high priority thread or an
+  // interrupt.
+  //
+  // For a given timer instance, its ExpiryCallback will not preempt itself.
+  // This makes it appear like there is a single executor of a timer instance's
+  // ExpiryCallback.
+  //
+  // Ergo ExpiryCallbacks should be treated as if they are executed by an
+  // interrupt, meaning:
+  // - Processing inside of the callback should be kept to a minimum.
+  // - Callbacks should never attempt to block.
+  // - APIs which are not interrupt safe such as pw::sync::Mutex should not be
+  //   used!
+  using ExpiryCallback =
+      Function<void(SystemClock::time_point expired_deadline)>;
+
+  SystemTimer(ExpiryCallback callback);
+  ~SystemTimer();  // Cancels the timer.
+  SystemTimer(const SystemTimer&) = delete;
+  SystemTimer(SystemTimer&&) = delete;
+  SystemTimer& operator=(const SystemTimer&) = delete;
+  SystemTimer& operator=(SystemTimer&&) = delete;
+
+  // Invokes the expiry callback as soon as possible after at least the
+  // specified duration.
+  //
+  // Scheduling a callback cancels the existing callback (if pending).
+  // If the callback is already being executed while you reschedule it, it will
+  // finish callback execution to completion. You are responsible for any
+  // critical section locks which may be needed for timer coordination.
+  //
+  // This is thread safe, it may not be IRQ safe.
+  void InvokeAfter(SystemClock::duration delay);
+
+  // Invokes the expiry callback as soon as possible starting at the specified
+  // time_point.
+  //
+  // Scheduling a callback cancels the existing callback (if pending).
+  // If the callback is already being executed while you reschedule it, it will
+  // finish callback execution to completion. You are responsible for any
+  // critical section locks which may be needed for timer coordination.
+  //
+  // This is thread safe, it may not be IRQ safe.
+  void InvokeAt(SystemClock::time_point timestamp);
+
+  // Cancels the software timer expiry callback if pending.
+  //
+  // Canceling a timer which isn't scheduled does nothing.
+  //
+  // If the callback is already being executed while you cancel it, it will
+  // finish callback execution to completion. You are responsible for any
+  // synchronization which is needed for thread safety.
+  //
+  // This is thread safe, it may not be IRQ safe.
+  void Cancel();
+
+  native_handle_type native_handle();
+
+ private:
+  // This may be a wrapper around a native type with additional members.
+  backend::NativeSystemTimer native_type_;
+};
+
+}  // namespace pw::chrono
+
+#include "pw_chrono_backend/system_timer_inline.h"
diff --git a/pw_chrono/system_timer_facade_test.cc b/pw_chrono/system_timer_facade_test.cc
new file mode 100644
index 0000000..88cfd26
--- /dev/null
+++ b/pw_chrono/system_timer_facade_test.cc
@@ -0,0 +1,249 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <chrono>
+
+#include "gtest/gtest.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_chrono/system_timer.h"
+#include "pw_sync/thread_notification.h"
+
+using namespace std::chrono_literals;
+
+namespace pw::chrono {
+namespace {
+
+// We can't control the SystemClock's period configuration, so just in case
+// duration cannot be accurately expressed in integer ticks, round the
+// duration up.
+constexpr SystemClock::duration kRoundedArbitraryShortDuration =
+    SystemClock::for_at_least(42ms);
+constexpr SystemClock::duration kRoundedArbitraryLongDuration =
+    SystemClock::for_at_least(1s);
+
+void ShouldNotBeInvoked(SystemClock::time_point) { FAIL(); }
+
+TEST(SystemTimer, CancelInactive) {
+  SystemTimer timer(ShouldNotBeInvoked);
+  timer.Cancel();
+}
+
+TEST(SystemTimer, CancelExplicitly) {
+  SystemTimer timer(ShouldNotBeInvoked);
+  timer.InvokeAfter(kRoundedArbitraryLongDuration);
+  timer.Cancel();
+}
+
+TEST(SystemTimer, CancelThroughDestruction) {
+  SystemTimer timer(ShouldNotBeInvoked);
+  timer.InvokeAfter(kRoundedArbitraryLongDuration);
+}
+
+TEST(SystemTimer, CancelThroughRescheduling) {
+  SystemTimer timer(ShouldNotBeInvoked);
+  timer.InvokeAfter(kRoundedArbitraryLongDuration);
+  // Cancel the first with this rescheduling.
+  timer.InvokeAfter(kRoundedArbitraryLongDuration);
+  timer.Cancel();
+}
+
+// Helper class to let test cases easily instantiate a timer with a handler
+// and its own context.
+class TimerWithHandler {
+ public:
+  TimerWithHandler()
+      : timer_([this](SystemClock::time_point expired_deadline) {
+          this->OnExpiryCallback(expired_deadline);
+        }) {}
+  virtual ~TimerWithHandler() = default;
+
+  // To be implemented by the test case.
+  virtual void OnExpiryCallback(SystemClock::time_point expired_deadline) = 0;
+
+  SystemTimer& timer() { return timer_; }
+
+ private:
+  SystemTimer timer_;
+};
+
+TEST(SystemTimer, StaticInvokeAt) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
+      EXPECT_GE(SystemClock::now(), expired_deadline);
+      EXPECT_EQ(expired_deadline, expected_deadline);
+      callback_ran_notification.release();
+    };
+
+    SystemClock::time_point expected_deadline;
+    sync::ThreadNotification callback_ran_notification;
+  };
+  static TimerWithContext uut;
+
+  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
+  uut.timer().InvokeAt(uut.expected_deadline);
+  uut.callback_ran_notification.acquire();
+
+  // Ensure you can re-use the timer.
+  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
+  uut.timer().InvokeAt(uut.expected_deadline);
+  uut.callback_ran_notification.acquire();
+}
+
+TEST(SystemTimer, InvokeAt) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
+      EXPECT_GE(SystemClock::now(), expired_deadline);
+      EXPECT_EQ(expired_deadline, expected_deadline);
+      callback_ran_notification.release();
+    };
+
+    SystemClock::time_point expected_deadline;
+    sync::ThreadNotification callback_ran_notification;
+  };
+  TimerWithContext uut;
+
+  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
+  uut.timer().InvokeAt(uut.expected_deadline);
+  uut.callback_ran_notification.acquire();
+
+  // Ensure you can re-use the timer.
+  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
+  uut.timer().InvokeAt(uut.expected_deadline);
+  uut.callback_ran_notification.acquire();
+}
+
+TEST(SystemTimer, InvokeAfter) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
+      EXPECT_GE(SystemClock::now(), expired_deadline);
+      EXPECT_GE(expired_deadline, expected_min_deadline);
+      callback_ran_notification.release();
+    };
+
+    SystemClock::time_point expected_min_deadline;
+    sync::ThreadNotification callback_ran_notification;
+  };
+  TimerWithContext uut;
+
+  uut.expected_min_deadline =
+      SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
+  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
+  uut.callback_ran_notification.acquire();
+
+  // Ensure you can re-use the timer.
+  uut.expected_min_deadline =
+      SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
+  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
+  uut.callback_ran_notification.acquire();
+}
+
+TEST(SystemTimer, CancelFromCallback) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point) override {
+      timer().Cancel();
+      callback_ran_notification.release();
+    };
+
+    sync::ThreadNotification callback_ran_notification;
+  };
+  TimerWithContext uut;
+
+  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
+  uut.callback_ran_notification.acquire();
+}
+
+TEST(SystemTimer, RescheduleAndCancelFromCallback) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point) override {
+      timer().InvokeAfter(kRoundedArbitraryShortDuration);
+      timer().Cancel();
+      callback_ran_notification.release();
+    };
+
+    sync::ThreadNotification callback_ran_notification;
+  };
+  TimerWithContext uut;
+
+  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
+  uut.callback_ran_notification.acquire();
+}
+
+TEST(SystemTimer, RescheduleFromCallback) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
+      EXPECT_GE(SystemClock::now(), expired_deadline);
+
+      EXPECT_EQ(expired_deadline, expected_deadline);
+      invocation_count++;
+      ASSERT_LE(invocation_count, kRequiredInvocations);
+      if (invocation_count < kRequiredInvocations) {
+        expected_deadline = expired_deadline + kPeriod;
+        timer().InvokeAt(expected_deadline);
+      } else {
+        callbacks_done_notification.release();
+      }
+    };
+
+    const uint8_t kRequiredInvocations = 5;
+    const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
+    uint8_t invocation_count = 0;
+    SystemClock::time_point expected_deadline;
+    sync::ThreadNotification callbacks_done_notification;
+  };
+  TimerWithContext uut;
+
+  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
+  uut.timer().InvokeAt(uut.expected_deadline);
+  uut.callbacks_done_notification.acquire();
+}
+
+TEST(SystemTimer, DoubleRescheduleFromCallback) {
+  class TimerWithContext : public TimerWithHandler {
+   public:
+    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
+      EXPECT_GE(SystemClock::now(), expired_deadline);
+
+      EXPECT_EQ(expired_deadline, expected_deadline);
+      invocation_count++;
+      ASSERT_LE(invocation_count, kExpectedInvocations);
+      if (invocation_count == 1) {
+        expected_deadline = expired_deadline + kPeriod;
+        timer().InvokeAt(expected_deadline);
+        timer().InvokeAt(expected_deadline);
+      } else {
+        callbacks_done_notification.release();
+      }
+    };
+
+    const uint8_t kExpectedInvocations = 2;
+    const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
+    uint8_t invocation_count = 0;
+    SystemClock::time_point expected_deadline;
+    sync::ThreadNotification callbacks_done_notification;
+  };
+  TimerWithContext uut;
+
+  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
+  uut.timer().InvokeAt(uut.expected_deadline);
+  uut.callbacks_done_notification.acquire();
+}
+
+}  // namespace
+}  // namespace pw::chrono
diff --git a/pw_chrono_stl/BUILD b/pw_chrono_stl/BUILD
index a015e0f..7637a49 100644
--- a/pw_chrono_stl/BUILD
+++ b/pw_chrono_stl/BUILD
@@ -50,3 +50,34 @@
         "//pw_chrono:system_clock_facade",
     ],
 )
+
+pw_cc_library(
+    name = "system_timer_headers",
+    hdrs = [
+        "public/pw_chrono_stl/system_timer_inline.h",
+        "public/pw_chrono_stl/system_timer_native.h",
+        "public_overrides/pw_chrono_backend/system_timer_inline.h",
+        "public_overrides/pw_chrono_backend/system_timer_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+        "//pw_function",
+        "//pw_chrono:system_timer_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "system_timer",
+    srcs = [
+        "system_timer.cc",
+    ],
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+    deps = [
+        ":system_timer_headers",
+        "//pw_chrono:system_timer_facade",
+    ],
+)
diff --git a/pw_chrono_stl/BUILD.gn b/pw_chrono_stl/BUILD.gn
index 948738d..9ecbd85 100644
--- a/pw_chrono_stl/BUILD.gn
+++ b/pw_chrono_stl/BUILD.gn
@@ -45,6 +45,27 @@
   ]
 }
 
+# This target provides the backend for pw::chrono::SystemTimer.
+pw_source_set("system_timer") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_chrono_stl/system_timer_inline.h",
+    "public/pw_chrono_stl/system_timer_native.h",
+    "public_overrides/pw_chrono_backend/system_timer_inline.h",
+    "public_overrides/pw_chrono_backend/system_timer_native.h",
+  ]
+  public_deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_chrono:system_timer.facade",
+    "$dir_pw_function",
+  ]
+  allow_circular_includes_from = [ "$dir_pw_chrono:system_timer.facade" ]
+  sources = [ "system_timer.cc" ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono_stl/docs.rst b/pw_chrono_stl/docs.rst
index 3ba23ba..b1aea39 100644
--- a/pw_chrono_stl/docs.rst
+++ b/pw_chrono_stl/docs.rst
@@ -7,15 +7,27 @@
 using STL's ``std::chrono`` library.
 
 .. warning::
-  This module is under construction, not ready for use, and the documentation
-  is incomplete.
+  This module is still under construction, the API is not yet stable.
 
 SystemClock backend
 -------------------
-The STL based ``system_clock`` backend implements the ``pw_chrono:system_clock``
-facade by using the ``std::chrono::steady_clock``. Note that the
-``std::chrono::system_clock`` cannot be used as this is not always a monotonic
-clock source.
+The STL based ``pw_chrono_stl:system_clock`` backend target implements the
+``pw_chrono:system_clock`` facade by using the ``std::chrono::steady_clock``.
+Note that the ``std::chrono::system_clock`` cannot be used as this is not always
+a monotonic clock source.
+
+See the documentation for ``pw_chrono`` for further details.
+
+SystemTimer backend
+-------------------
+The STL based ``pw_chrono_stl:system_timer`` backend target implements the
+``pw_chrono:system_timer`` facade by spawning a detached thread for every single
+``InvokeAt()`` and ``InvokeAfter()`` call. This thread simply sleeps until the
+desired ``expiration_deadline`` and invokes the user's ``ExpiryCallback`` if it
+wasn't cancelled.
+
+.. Warning::
+  Although fully functional, the current implementation is NOT efficient!
 
 See the documentation for ``pw_chrono`` for further details.
 
diff --git a/pw_chrono_stl/public/pw_chrono_stl/system_timer_inline.h b/pw_chrono_stl/public/pw_chrono_stl/system_timer_inline.h
new file mode 100644
index 0000000..4d40cda
--- /dev/null
+++ b/pw_chrono_stl/public/pw_chrono_stl/system_timer_inline.h
@@ -0,0 +1,39 @@
+// 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 <memory>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_chrono/system_timer.h"
+
+namespace pw::chrono {
+
+inline SystemTimer::SystemTimer(ExpiryCallback callback) : native_type_() {
+  native_type_.callback_context =
+      std::make_shared<backend::NativeSystemTimer::CallbackContext>(
+          std::move(callback));
+}
+
+inline SystemTimer::~SystemTimer() { Cancel(); }
+
+inline void SystemTimer::InvokeAfter(SystemClock::duration delay) {
+  InvokeAt(SystemClock::TimePointAfterAtLeast(delay));
+}
+
+inline SystemTimer::native_handle_type SystemTimer::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::chrono
diff --git a/pw_chrono_stl/public/pw_chrono_stl/system_timer_native.h b/pw_chrono_stl/public/pw_chrono_stl/system_timer_native.h
new file mode 100644
index 0000000..3bc3101
--- /dev/null
+++ b/pw_chrono_stl/public/pw_chrono_stl/system_timer_native.h
@@ -0,0 +1,53 @@
+// 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 <atomic>
+#include <memory>
+#include <mutex>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_function/function.h"
+
+namespace pw::chrono::backend {
+
+struct NativeSystemTimer {
+  // The mutex is only used to ensure the public API is threadsafe.
+  std::mutex api_mutex;
+
+  // Instead of using a more complex blocking timer cleanup, a shared_pointer is
+  // used so that the heap allocation is still valid for the detached threads
+  // even after the NativeSystemTimer has been destructed. Note this is shared
+  // with all detached threads.
+  struct CallbackContext {
+    CallbackContext(Function<void(SystemClock::time_point expired_deadline)> cb)
+        : callback(std::move(cb)) {}
+
+    const Function<void(SystemClock::time_point expired_deadline)> callback;
+    // This mutex is used to ensure only one expiry callback is executed at a
+    // time. This way there's no risk that a callback function attempting to
+    // reschedule the timer is immediately preempted by that callback.
+    //
+    // Note this is required by the facade API contract.
+    std::mutex callback_mutex;
+  };
+  std::shared_ptr<CallbackContext> callback_context;
+
+  // This is only shared with the last active timer if there is one.
+  std::shared_ptr<std::atomic<bool>> active_timer_enabled;
+};
+
+using NativeSystemTimerHandle = NativeSystemTimer&;
+
+}  // namespace pw::chrono::backend
diff --git a/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_inline.h b/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_inline.h
new file mode 100644
index 0000000..6fd8078
--- /dev/null
+++ b/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_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_chrono_stl/system_timer_inline.h"
diff --git a/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_native.h b/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_native.h
new file mode 100644
index 0000000..c1a3543
--- /dev/null
+++ b/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_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_chrono_stl/system_timer_native.h"
diff --git a/pw_chrono_stl/system_timer.cc b/pw_chrono_stl/system_timer.cc
new file mode 100644
index 0000000..112f5bd
--- /dev/null
+++ b/pw_chrono_stl/system_timer.cc
@@ -0,0 +1,65 @@
+// 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_chrono/system_timer.h"
+
+#include <thread>
+
+namespace pw::chrono {
+namespace {
+
+using CallbackContext = backend::NativeSystemTimer::CallbackContext;
+
+void SystemTimerThreadFunction(
+    std::shared_ptr<std::atomic<bool>> timer_enabled,
+    std::shared_ptr<CallbackContext> callback_context,
+    SystemClock::time_point expiry_deadline) {
+  // Sleep until the requested deadline.
+  std::this_thread::sleep_until(expiry_deadline);
+
+  // Only invoke the user's callback if this invocation has not been cancelled.
+  if (timer_enabled->load()) {
+    std::lock_guard lock(callback_context->callback_mutex);
+    (callback_context->callback)(expiry_deadline);
+  }
+}
+
+}  // namespace
+
+void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
+  std::lock_guard lock(native_type_.api_mutex);
+
+  // First, cancel any outstanding requests.
+  if (native_type_.active_timer_enabled) {
+    native_type_.active_timer_enabled->store(false);
+  }
+
+  // Second, active another detached timer thread with a new shared atomic bool.
+  native_type_.active_timer_enabled = std::make_shared<std::atomic<bool>>(true);
+  std::thread(SystemTimerThreadFunction,
+              native_type_.active_timer_enabled,
+              native_type_.callback_context,
+              timestamp)
+      .detach();
+}
+
+void SystemTimer::Cancel() {
+  std::lock_guard lock(native_type_.api_mutex);
+
+  if (native_type_.active_timer_enabled) {
+    native_type_.active_timer_enabled->store(false);
+  }
+}
+
+}  // namespace pw::chrono
diff --git a/targets/default_config.BUILD b/targets/default_config.BUILD
index 58fdba9..b5d8e79 100644
--- a/targets/default_config.BUILD
+++ b/targets/default_config.BUILD
@@ -25,8 +25,13 @@
 )
 
 label_flag(
-    name = "pw_chrono_backend",
-    build_setting_default = "@pigweed//pw_chrono:backend_multiplexer",
+    name = "pw_chrono_system_clock_backend",
+    build_setting_default = "@pigweed//pw_chrono:system_clock_backend_multiplexer",
+)
+
+label_flag(
+    name = "pw_chrono_system_timer_backend",
+    build_setting_default = "@pigweed//pw_chrono:system_timer_backend_multiplexer",
 )
 
 label_flag(
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index aa99a50..323984a 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -60,8 +60,9 @@
   # Tokenizer trace time.
   pw_trace_tokenizer_time = "$dir_pw_trace_tokenized:host_trace_time"
 
-  # Configure backend for pw_chrono's system_clock facade.
+  # Configure backend for pw_chrono's facades.
   pw_chrono_SYSTEM_CLOCK_BACKEND = "$dir_pw_chrono_stl:system_clock"
+  pw_chrono_SYSTEM_TIMER_BACKEND = "$dir_pw_chrono_stl:system_timer"
 
   # Configure backends for pw_thread's facades.
   pw_thread_ID_BACKEND = "$dir_pw_thread_stl:id"