pw_sync: add ThreadNotification and TimedThreadNotification

Adds the ThreadNotification and TimedThreadNotification facades
to pw_sync.

Also provides a generic and not-optimized backend based on
pw::sync::BinarySemaphore which is immediately used for the host
target.

Change-Id: Iac067ae10f32f9633907c05365338eb575d35097
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/44020
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_sync/BUILD b/pw_sync/BUILD
index 6b63e1f..9b9f683 100644
--- a/pw_sync/BUILD
+++ b/pw_sync/BUILD
@@ -1,4 +1,4 @@
-# Copyright 2020 The Pigweed Authors
+# Copyright 2021 The Pigweed Authors
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
 # use this file except in compliance with the License. You may obtain a copy of
@@ -18,6 +18,10 @@
     "pw_cc_library",
     "pw_cc_test",
 )
+load(
+    "//pw_build:selects.bzl",
+    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
+)
 
 package(default_visibility = ["//visibility:public"])
 
@@ -212,6 +216,114 @@
     }),
 )
 
+pw_cc_facade(
+    name = "thread_notification_facade",
+    hdrs = [
+        "public/pw_sync/thread_notification.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "thread_notification",
+    deps = [
+        ":thread_notification_facade",
+        "@pigweed_config//:pw_sync_thread_notification_backend",
+    ],
+)
+
+pw_cc_library(
+    name = "thread_notification_backend_multiplexer",
+    visibility = ["@pigweed_config//:__pkg__"],
+    deps = select({
+        "//conditions:default": ["//pw_sync:binary_semaphore_thread_notification_backend"],
+    }),
+)
+
+pw_cc_facade(
+    name = "timed_thread_notification_facade",
+    hdrs = [
+        "public/pw_sync/timed_thread_notification.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":thread_notification_facade",
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_thread_notification",
+    deps = [
+        ":thread_notification",
+        ":timed_thread_notification_facade",
+        "@pigweed_config//:pw_sync_timed_thread_notification_backend",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_thread_notification_backend_multiplexer",
+    visibility = ["@pigweed_config//:__pkg__"],
+    deps = select({
+        "//conditions:default": ["//pw_sync:binary_semaphore_timed_thread_notification_backend"],
+    }),
+)
+
+pw_cc_library(
+    name = "binary_semaphore_thread_notification_backend_headers",
+    hdrs = [
+        "public/pw_sync/backends/binary_semaphore_thread_notification_inline.h",
+        "public/pw_sync/backends/binary_semaphore_thread_notification_native.h",
+        "public_overrides/pw_sync_backend/thread_notification_inline.h",
+        "public_overrides/pw_sync_backend/thread_notification_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+    ],
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+)
+
+pw_cc_library(
+    name = "binary_semaphore_thread_notification_backend",
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+    deps = [
+        ":binary_semaphore_thread_notification_headers",
+        ":binary_semaphore_facade",
+        ":thread_notification_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore_timed_thread_notification_backend_headers",
+    hdrs = [
+        "public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h",
+        "public_overrides/pw_sync_backend/timed_thread_notification_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+    deps = [
+        ":binary_semaphore_thread_notification_backend_headers",
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore_timed_thread_notification_backend",
+    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
+    deps = [
+        ":binary_semaphore_thread_notification_backend",
+        ":binary_semaphore_timed_thread_notification_backend_headers",
+        "//pw_sync:timed_thread_notification_facade",
+    ],
+)
+
 pw_cc_library(
     name = "yield_core",
     hdrs = [
@@ -267,6 +379,7 @@
     ],
     deps = [
         ":timed_mutex",
+        "//pw_chrono:system_clock",
         "//pw_preprocessor",
         "//pw_unit_test",
     ],
@@ -284,3 +397,28 @@
         "//pw_unit_test",
     ],
 )
+
+pw_cc_test(
+    name = "thread_notification_facade_test",
+    srcs = [
+        "thread_notification_facade_test.cc",
+        "thread_notification_facade_test_c.c",
+    ],
+    deps = [
+        ":thread_notification",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "timed_thread_notification_facade_test",
+    srcs = [
+        "timed_thread_notification_facade_test.cc",
+        "timed_thread_notification_facade_test_c.c",
+    ],
+    deps = [
+        ":timed_thread_notification",
+        "//pw_chrono:system_clock",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index db702a6..5b6fecf 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Pigweed Authors
+# Copyright 2021 The Pigweed Authors
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
 # use this file except in compliance with the License. You may obtain a copy of
@@ -25,6 +25,11 @@
   visibility = [ ":*" ]
 }
 
+config("backend_config") {
+  include_dirs = [ "public_overrides" ]
+  visibility = [ ":*" ]
+}
+
 pw_facade("binary_semaphore") {
   backend = pw_sync_BINARY_SEMAPHORE_BACKEND
   public_configs = [ ":public_include_path" ]
@@ -87,6 +92,59 @@
   sources = [ "interrupt_spin_lock.cc" ]
 }
 
+pw_facade("thread_notification") {
+  backend = pw_sync_THREAD_NOTIFICATION_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/thread_notification.h" ]
+}
+
+pw_facade("timed_thread_notification") {
+  backend = pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/timed_thread_notification.h" ]
+  public_deps = [
+    ":thread_notification",
+    "$dir_pw_chrono:system_clock",
+  ]
+}
+
+# This target provides the backend for pw::sync::ThreadNotification based on
+# pw::sync::BinarySemaphore.
+pw_source_set("binary_semaphore_thread_notification_backend") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync/backends/binary_semaphore_thread_notification_inline.h",
+    "public/pw_sync/backends/binary_semaphore_thread_notification_native.h",
+    "public_overrides/pw_sync_backend/thread_notification_inline.h",
+    "public_overrides/pw_sync_backend/thread_notification_native.h",
+  ]
+  public_deps = [
+    ":binary_semaphore",
+    ":thread_notification.facade",
+  ]
+}
+
+# This target provides the backend for pw::sync::TimedThreadNotification based
+# on pw::sync::BinarySemaphore.
+pw_source_set("binary_semaphore_timed_thread_notification_backend") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h",
+    "public_overrides/pw_sync_backend/timed_thread_notification_inline.h",
+  ]
+  public_deps = [
+    ":binary_semaphore_thread_notification_backend",
+    ":timed_thread_notification.facade",
+    "$dir_pw_chrono:system_clock",
+  ]
+}
+
 pw_source_set("yield_core") {
   public = [ "public/pw_sync/yield_core.h" ]
   public_configs = [ ":public_include_path" ]
@@ -99,6 +157,8 @@
     ":mutex_facade_test",
     ":timed_mutex_facade_test",
     ":interrupt_spin_lock_facade_test",
+    ":thread_notification_facade_test",
+    ":timed_thread_notification_facade_test",
   ]
 }
 
@@ -167,6 +227,24 @@
   ]
 }
 
+pw_test("thread_notification_facade_test") {
+  enable_if = pw_sync_THREAD_NOTIFICATION_BACKEND != ""
+  sources = [ "thread_notification_facade_test.cc" ]
+  deps = [
+    ":thread_notification",
+    pw_sync_THREAD_NOTIFICATION_BACKEND,
+  ]
+}
+
+pw_test("timed_thread_notification_facade_test") {
+  enable_if = pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND != ""
+  sources = [ "timed_thread_notification_facade_test.cc" ]
+  deps = [
+    ":timed_thread_notification",
+    pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND,
+  ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_sync/backend.gni b/pw_sync/backend.gni
index 8f6605a..9712a45 100644
--- a/pw_sync/backend.gni
+++ b/pw_sync/backend.gni
@@ -28,6 +28,12 @@
   # Backend for the pw_sync module's interrupt spin lock.
   pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = ""
 
+  # Backend for the pw_sync module's thread notification.
+  pw_sync_THREAD_NOTIFICATION_BACKEND = ""
+
+  # Backend for the pw_sync module's timed thread notification.
+  pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND = ""
+
   # Whether the GN asserts should be silenced in ensuring that a compatible
   # backend for pw_chrono_SYSTEM_CLOCK_BACKEND is chosen.
   # Set to true to disable the asserts.
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index b5835fb..86e1f0c 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -963,18 +963,320 @@
 build any signaling primitive based on other native signaling primitives, this
 may come with non-trivial added overhead in ROM, RAM, and execution efficiency.
 
-For this reason, Pigweed intends to provide some "simpler" signaling primitives
+For this reason, Pigweed intends to provide some simpler signaling primitives
 which exist to solve a narrow programming need but can be implemented as
 efficiently as possible for the platform that it is used on.
 
 This simpler but highly portable class of signaling primitives is intended to
 ensure that a portability efficiency tradeoff does not have to be made up front.
-For example we intend to provide a ``pw::sync::ThreadNotification`` facade which
-permits a singler consumer to block until an event occurs. This should be
+Today this is class of simpler signaling primitives is limited to the
+``pw::sync::ThreadNotification`` and ``pw::sync::TimedThreadNotification``.
+
+ThreadNotification
+==================
+The ThreadNotification is a synchronization primitive that can be used to
+permit a SINGLE thread to block and consume a latching, saturating
+notification from multiple notifiers.
+
+.. Warning::
+  This is a single consumer/waiter, multiple producer/notifier API!
+  The acquire APIs must only be invoked by a single consuming thread. As a
+  result, having multiple threads receiving notifications via the acquire API
+  is unsupported.
+
+This is effectively a subset of the ``pw::sync::BinarySemaphore`` API, except
+that only a single thread can be notified and block at a time.
+
+The single consumer aspect of the API permits the use of a smaller and/or
+faster native APIs such as direct thread signaling. This should be
 backed by the most efficient native primitive for a target, regardless of
 whether that is a semaphore, event flag group, condition variable, or something
 else.
 
+Generic BinarySemaphore-based Backend
+-------------------------------------
+This module provides a generic backend for ``pw::sync::ThreadNotification`` via
+``pw_sync:binary_semaphore_thread_notification`` which uses a
+``pw::sync::BinarySemaphore`` as the backing primitive. See
+:ref:`BinarySemaphore <module-pw_sync-binary-semaphore>` for backend
+availability.
+
+Optimized Backend
+-----------------
+.. list-table::
+
+  * - *Supported on*
+    - *Optimized backend module*
+  * - FreeRTOS
+    - Planned
+  * - ThreadX
+    - Planned
+  * - embOS
+    - Planned
+  * - STL
+    - Not planned, use ``pw_sync:binary_semaphore_thread_notification``
+  * - Baremetal
+    - Planned
+  * - Zephyr
+    - Planned
+  * - CMSIS-RTOS API v2 & RTX5
+    - Planned
+
+C++
+---
+.. cpp:class:: pw::sync::ThreadNotification
+
+  .. cpp:function:: void acquire()
+
+     Blocks indefinitely until the thread is notified, i.e. until the
+     notification latch can be cleared because it was set.
+
+     Clears the notification latch.
+
+     **IMPORTANT:** This should only be used by a single consumer thread.
+
+  .. cpp:function:: bool try_acquire()
+
+     Returns whether the thread has been notified, i.e. whether the notificion
+     latch was set and resets the latch regardless.
+
+     Clears the notification latch.
+
+     Returns true if the thread was notified, meaning the the internal latch was
+     reset successfully.
+
+     **IMPORTANT:** This should only be used by a single consumer thread.
+
+  .. cpp:function:: void release()
+
+     Notifies the thread in a saturating manner, setting the notification latch.
+
+     Raising the notification multiple time without it being acquired by the
+     consuming thread is equivalent to raising the notification once to the
+     thread. The notification is latched in case the thread was not waiting at
+     the time.
+
+     This is IRQ and thread safe.
+
+  .. list-table::
+
+    * - *Safe to use in context*
+      - *Thread*
+      - *Interrupt*
+      - *NMI*
+    * - ``ThreadNotification::ThreadNotification``
+      - ✔
+      -
+      -
+    * - ``ThreadNotification::~ThreadNotification``
+      - ✔
+      -
+      -
+    * - ``void ThreadNotification::acquire``
+      - ✔
+      -
+      -
+    * - ``bool ThreadNotification::try_acquire``
+      - ✔
+      -
+      -
+    * - ``void ThreadNotification::release``
+      - ✔
+      - ✔
+      -
+
+Examples in C++
+^^^^^^^^^^^^^^^
+.. code-block:: cpp
+
+  #include "pw_sync/thread_notification.h"
+  #include "pw_thread/thread_core.h"
+
+  class FooHandler() : public pw::thread::ThreadCore {
+   // Public API invoked by other threads and/or interrupts.
+   void NewFooAvailable() {
+     new_foo_notification_.release();
+   }
+
+   private:
+    pw::sync::ThreadNotification new_foo_notification_;
+
+    // Thread function.
+    void Run() override {
+      while (true) {
+        new_foo_notification_.acquire();
+        HandleFoo();
+      }
+    }
+
+    void HandleFoo();
+  }
+
+TimedThreadNotification
+=======================
+The TimedThreadNotification is an extension of the ThreadNotification which
+offers timeout and deadline based semantics.
+
+.. Warning::
+  This is a single consumer/waiter, multiple producer/notifier API!
+  The acquire APIs must only be invoked by a single consuming thread. As a
+  result, having multiple threads receiving notifications via the acquire API
+  is unsupported.
+
+Generic BinarySemaphore-based Backend
+-------------------------------------
+This module provides a generic backend for ``pw::sync::TimedThreadNotification``
+via ``pw_sync:binary_semaphore_timed_thread_notification`` which uses a
+``pw::sync::BinarySemaphore`` as the backing primitive. See
+:ref:`BinarySemaphore <module-pw_sync-binary-semaphore>` for backend
+availability.
+
+Optimized Backend
+-----------------
+.. list-table::
+
+  * - *Supported on*
+    - *Backend module*
+  * - FreeRTOS
+    - Planned
+  * - ThreadX
+    - Planned
+  * - embOS
+    - Planned
+  * - STL
+    - Not planned, use ``pw_sync:binary_semaphore_thread_notification``
+  * - Zephyr
+    - Planned
+  * - CMSIS-RTOS API v2 & RTX5
+    - Planned
+
+C++
+---
+.. cpp:class:: pw::sync::TimedThreadNotification
+
+  .. cpp:function:: void acquire()
+
+     Blocks indefinitely until the thread is notified, i.e. until the
+     notification latch can be cleared because it was set.
+
+     Clears the notification latch.
+
+     **IMPORTANT:** This should only be used by a single consumer thread.
+
+  .. cpp:function:: bool try_acquire()
+
+     Returns whether the thread has been notified, i.e. whether the notificion
+     latch was set and resets the latch regardless.
+
+     Clears the notification latch.
+
+     Returns true if the thread was notified, meaning the the internal latch was
+     reset successfully.
+
+     **IMPORTANT:** This should only be used by a single consumer thread.
+
+  .. cpp:function:: void release()
+
+     Notifies the thread in a saturating manner, setting the notification latch.
+
+     Raising the notification multiple time without it being acquired by the
+     consuming thread is equivalent to raising the notification once to the
+     thread. The notification is latched in case the thread was not waiting at
+     the time.
+
+     This is IRQ and thread safe.
+
+  .. cpp:function:: bool try_acquire_for(chrono::SystemClock::duration timeout)
+
+     Blocks until the specified timeout duration has elapsed or the thread
+     has been notified (i.e. notification latch can be cleared because it was
+     set), whichever comes first.
+
+     Clears the notification latch.
+
+     Returns true if the thread was notified, meaning the the internal latch was
+     reset successfully.
+
+     **IMPORTANT:** This should only be used by a single consumer thread.
+
+  .. cpp:function:: bool try_acquire_until(chrono::SystemClock::time_point deadline)
+
+     Blocks until the specified deadline time has been reached the thread has
+     been notified (i.e. notification latch can be cleared because it was set),
+     whichever comes first.
+
+     Clears the notification latch.
+
+     Returns true if the thread was notified, meaning the the internal latch was
+     reset successfully.
+
+     **IMPORTANT:** This should only be used by a single consumer thread.
+
+  .. list-table::
+
+    * - *Safe to use in context*
+      - *Thread*
+      - *Interrupt*
+      - *NMI*
+    * - ``ThreadNotification::ThreadNotification``
+      - ✔
+      -
+      -
+    * - ``ThreadNotification::~ThreadNotification``
+      - ✔
+      -
+      -
+    * - ``void ThreadNotification::acquire``
+      - ✔
+      -
+      -
+    * - ``bool ThreadNotification::try_acquire``
+      - ✔
+      -
+      -
+    * - ``bool ThreadNotification::try_acquire_for``
+      - ✔
+      -
+      -
+    * - ``bool ThreadNotification::try_acquire_until``
+      - ✔
+      -
+      -
+    * - ``void ThreadNotification::release``
+      - ✔
+      - ✔
+      -
+
+Examples in C++
+^^^^^^^^^^^^^^^
+.. code-block:: cpp
+
+  #include "pw_sync/timed_thread_notification.h"
+  #include "pw_thread/thread_core.h"
+
+  class FooHandler() : public pw::thread::ThreadCore {
+   // Public API invoked by other threads and/or interrupts.
+   void NewFooAvailable() {
+     new_foo_notification_.release();
+   }
+
+   private:
+    pw::sync::TimedThreadNotification new_foo_notification_;
+
+    // Thread function.
+    void Run() override {
+      while (true) {
+        if (new_foo_notification_.try_acquire_for(kNotificationTimeout)) {
+          HandleFoo();
+        }
+        DoOtherStuff();
+      }
+    }
+
+    void HandleFoo();
+    void DoOtherStuff();
+  }
+
 CountingSemaphore
 =================
 The CountingSemaphore is a synchronization primitive that can be used for
@@ -1012,6 +1314,8 @@
   * - CMSIS-RTOS API v2 & RTX5
     - Planned
 
+.. _module-pw_sync-binary-semaphore:
+
 BinarySemaphore
 ===============
 BinarySemaphore is a specialization of CountingSemaphore with an arbitrary token
@@ -1053,17 +1357,3 @@
 using our semaphore and mutex layers and we may consider providing this in the
 future. However for most of our resource constrained customers they will mostly
 likely be using semaphores more often than CVs.
-
-Coming Soon
-===========
-We are intending to provide facades for:
-
-* ``pw::sync::ThreadNotification``: A portable abstraction to allow a single
-  thread to receive notification of a single occurrence of a single event.
-
-* ``pw::sync::EventGroup`` A facade for a common primitive on RTOSes like
-  FreeRTOS, RTX5, ThreadX, and embOS which permit threads and interrupts to
-  signal up to 32 events. This permits others threads to be notified when either
-  any or some combination of these events have been signaled. This is frequently
-  used as an alternative to a set of binary semaphore(s). This is not supported
-  natively on Zephyr.
diff --git a/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_inline.h b/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_inline.h
new file mode 100644
index 0000000..c67a591
--- /dev/null
+++ b/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_inline.h
@@ -0,0 +1,37 @@
+// 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_sync/thread_notification.h"
+
+namespace pw::sync {
+
+inline ThreadNotification::ThreadNotification() : native_type_() {}
+
+inline ThreadNotification::~ThreadNotification() {}
+
+inline void ThreadNotification::acquire() { native_type_.acquire(); }
+
+inline bool ThreadNotification::try_acquire() {
+  return native_type_.try_acquire();
+}
+
+inline void ThreadNotification::release() { native_type_.release(); }
+
+inline ThreadNotification::native_handle_type
+ThreadNotification::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_native.h b/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_native.h
new file mode 100644
index 0000000..616b9af
--- /dev/null
+++ b/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_native.h
@@ -0,0 +1,23 @@
+// 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_sync/binary_semaphore.h"
+
+namespace pw::sync::backend {
+
+using NativeThreadNotification = BinarySemaphore;
+using NativeThreadNotificationHandle = BinarySemaphore&;
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync/public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h b/pw_sync/public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h
new file mode 100644
index 0000000..edc78b4
--- /dev/null
+++ b/pw_sync/public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h
@@ -0,0 +1,31 @@
+// 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_sync/timed_thread_notification.h"
+
+namespace pw::sync {
+
+inline bool TimedThreadNotification::try_acquire_for(
+    chrono::SystemClock::duration for_at_least) {
+  return native_handle().try_acquire_for(for_at_least);
+}
+
+inline bool TimedThreadNotification::try_acquire_until(
+    chrono::SystemClock::time_point until_at_least) {
+  return native_handle().try_acquire_until(until_at_least);
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/thread_notification.h b/pw_sync/public/pw_sync/thread_notification.h
new file mode 100644
index 0000000..ba8cf58
--- /dev/null
+++ b/pw_sync/public/pw_sync/thread_notification.h
@@ -0,0 +1,83 @@
+// 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_sync_backend/thread_notification_native.h"
+
+namespace pw::sync {
+
+// The ThreadNotification is a synchronization primitive that can be used to
+// permit a SINGLE thread to block and consume a latching, saturating
+// notification from multiple notifiers.
+//
+// IMPORTANT: This is a single consumer/waiter, multiple producer/notifier API!
+// The acquire APIs must only be invoked by a single consuming thread. As a
+// result, having multiple threads receiving notifications via the acquire API
+// is unsupported.
+//
+// This is effectively a subset of a binary semaphore API, except that only a
+// single thread can be notified and block at a time.
+//
+// The single consumer aspect of the API permits the use of a smaller and/or
+// faster native APIs such as direct thread signaling.
+class ThreadNotification {
+ public:
+  using native_handle_type = backend::NativeThreadNotificationHandle;
+
+  ThreadNotification();
+  ~ThreadNotification();
+  ThreadNotification(const ThreadNotification&) = delete;
+  ThreadNotification(ThreadNotification&&) = delete;
+  ThreadNotification& operator=(const ThreadNotification&) = delete;
+  ThreadNotification& operator=(ThreadNotification&&) = delete;
+
+  // Blocks indefinitely until the thread is notified, i.e. until the
+  // notification latch can be cleared because it was set.
+  //
+  // Clears the notification latch.
+  //
+  // IMPORTANT: This should only be used by a single consumer thread.
+  void acquire();
+
+  // Returns whether the thread has been notified, i.e. whether the notificion
+  // latch was set and resets the latch regardless.
+  //
+  // Clears the notification latch.
+  //
+  // Returns true if the thread was notified, meaning the the internal latch was
+  // reset successfully.
+  //
+  // IMPORTANT: This should only be used by a single consumer thread.
+  bool try_acquire();
+
+  // Notifies the thread in a saturating manner, setting the notification latch.
+  //
+  // Raising the notification multiple time without it being acquired by the
+  // consuming thread is equivalent to raising the notification once to the
+  // thread. The notification is latched in case the thread was not waiting at
+  // the time.
+  //
+  // This is IRQ and thread safe.
+  void release();
+
+  native_handle_type native_handle();
+
+ private:
+  // This may be a wrapper around a native type with additional members.
+  backend::NativeThreadNotification native_type_;
+};
+
+}  // namespace pw::sync
+
+#include "pw_sync_backend/thread_notification_inline.h"
diff --git a/pw_sync/public/pw_sync/timed_thread_notification.h b/pw_sync/public/pw_sync/timed_thread_notification.h
new file mode 100644
index 0000000..3e17db1
--- /dev/null
+++ b/pw_sync/public/pw_sync/timed_thread_notification.h
@@ -0,0 +1,71 @@
+// 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_sync/thread_notification.h"
+
+namespace pw::sync {
+
+// The TimedThreadNotification is a synchronization primitive that can be used
+// to permit a SINGLE thread to block and consume a latching, saturating
+// notification from  multiple notifiers.
+//
+// IMPORTANT: This is a single consumer/waiter, multiple producer/notifier API!
+// The acquire APIs must only be invoked by a single consuming thread. As a
+// result, having multiple threads receiving notifications via the acquire API
+// is unsupported.
+//
+// This is effectively a subset of a binary semaphore API, except that only a
+// single thread can be notified and block at a time.
+//
+// The single consumer aspect of the API permits the use of a smaller and/or
+// faster native APIs such as direct thread signaling.
+class TimedThreadNotification : public ThreadNotification {
+ public:
+  TimedThreadNotification() = default;
+  ~TimedThreadNotification() = default;
+  TimedThreadNotification(const TimedThreadNotification&) = delete;
+  TimedThreadNotification(TimedThreadNotification&&) = delete;
+  TimedThreadNotification& operator=(const TimedThreadNotification&) = delete;
+  TimedThreadNotification& operator=(TimedThreadNotification&&) = delete;
+
+  // Blocks until the specified timeout duration has elapsed or the thread
+  // has been notified (i.e. notification latch can be cleared because it was
+  // set), whichever comes first.
+  //
+  // Clears the notification latch.
+  //
+  // Returns true if the thread was notified, meaning the the internal latch was
+  // reset successfully.
+  //
+  // IMPORTANT: This should only be used by a single consumer thread.
+  bool try_acquire_for(chrono::SystemClock::duration timeout);
+
+  // Blocks until the specified deadline time has been reached the thread has
+  // been notified (i.e. notification latch can be cleared because it was set),
+  // whichever comes first.
+  //
+  // Clears the notification latch.
+  //
+  // Returns true if the thread was notified, meaning the the internal latch was
+  // reset successfully.
+  //
+  // IMPORTANT: This should only be used by a single consumer thread.
+  bool try_acquire_until(chrono::SystemClock::time_point deadline);
+};
+
+}  // namespace pw::sync
+
+#include "pw_sync_backend/timed_thread_notification_inline.h"
diff --git a/pw_sync/public_overrides/pw_sync_backend/thread_notification_inline.h b/pw_sync/public_overrides/pw_sync_backend/thread_notification_inline.h
new file mode 100644
index 0000000..0e3a4a8
--- /dev/null
+++ b/pw_sync/public_overrides/pw_sync_backend/thread_notification_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_sync/backends/binary_semaphore_thread_notification_inline.h"
diff --git a/pw_sync/public_overrides/pw_sync_backend/thread_notification_native.h b/pw_sync/public_overrides/pw_sync_backend/thread_notification_native.h
new file mode 100644
index 0000000..6830910
--- /dev/null
+++ b/pw_sync/public_overrides/pw_sync_backend/thread_notification_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_sync/backends/binary_semaphore_thread_notification_native.h"
diff --git a/pw_sync/public_overrides/pw_sync_backend/timed_thread_notification_inline.h b/pw_sync/public_overrides/pw_sync_backend/timed_thread_notification_inline.h
new file mode 100644
index 0000000..4297196
--- /dev/null
+++ b/pw_sync/public_overrides/pw_sync_backend/timed_thread_notification_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_sync/backends/binary_semaphore_timed_thread_notification_inline.h"
diff --git a/pw_sync/thread_notification_facade_test.cc b/pw_sync/thread_notification_facade_test.cc
new file mode 100644
index 0000000..c8f6eaa
--- /dev/null
+++ b/pw_sync/thread_notification_facade_test.cc
@@ -0,0 +1,54 @@
+// 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_sync/thread_notification.h"
+
+namespace pw::sync {
+namespace {
+
+TEST(ThreadNotification, EmptyInitialState) {
+  ThreadNotification notification;
+  EXPECT_FALSE(notification.try_acquire());
+}
+
+// TODO(pwbug/291): Add real concurrency tests.
+
+TEST(ThreadNotification, Release) {
+  ThreadNotification notification;
+  notification.release();
+  notification.release();
+  notification.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(notification.try_acquire());
+}
+
+ThreadNotification empty_initial_notification;
+TEST(ThreadNotification, EmptyInitialStateStatic) {
+  EXPECT_FALSE(empty_initial_notification.try_acquire());
+}
+
+ThreadNotification raise_notification;
+TEST(ThreadNotification, ReleaseStatic) {
+  raise_notification.release();
+  raise_notification.release();
+  raise_notification.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(raise_notification.try_acquire());
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/timed_thread_notification_facade_test.cc b/pw_sync/timed_thread_notification_facade_test.cc
new file mode 100644
index 0000000..7a4c522
--- /dev/null
+++ b/pw_sync/timed_thread_notification_facade_test.cc
@@ -0,0 +1,94 @@
+// 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_sync/timed_thread_notification.h"
+
+using pw::chrono::SystemClock;
+using namespace std::chrono_literals;
+
+namespace pw::sync {
+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 kRoundedArbitraryDuration =
+    SystemClock::for_at_least(42ms);
+
+TEST(TimedThreadNotification, EmptyInitialState) {
+  TimedThreadNotification notification;
+  EXPECT_FALSE(notification.try_acquire());
+}
+
+// TODO(pwbug/291): Add real concurrency tests.
+
+TEST(TimedThreadNotification, Release) {
+  TimedThreadNotification notification;
+  notification.release();
+  notification.release();
+  notification.acquire();
+  // Ensure it fails when not notified.
+  EXPECT_FALSE(notification.try_acquire());
+}
+
+TimedThreadNotification empty_initial_notification;
+TEST(TimedThreadNotification, EmptyInitialStateStatic) {
+  EXPECT_FALSE(empty_initial_notification.try_acquire());
+}
+
+TimedThreadNotification raise_notification;
+TEST(TimedThreadNotification, ReleaseStatic) {
+  raise_notification.release();
+  raise_notification.release();
+  raise_notification.acquire();
+  // Ensure it fails when not notified.
+  EXPECT_FALSE(raise_notification.try_acquire());
+}
+
+TEST(TimedThreadNotification, TryAcquireFor) {
+  TimedThreadNotification notification;
+  notification.release();
+
+  SystemClock::time_point before = SystemClock::now();
+  EXPECT_TRUE(notification.try_acquire_for(kRoundedArbitraryDuration));
+  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
+
+  // Ensure it blocks and fails when not notified.
+  before = SystemClock::now();
+  EXPECT_FALSE(notification.try_acquire_for(kRoundedArbitraryDuration));
+  time_elapsed = SystemClock::now() - before;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
+}
+
+TEST(TimedThreadNotification, tryAcquireUntil) {
+  TimedThreadNotification notification;
+  notification.release();
+
+  const SystemClock::time_point deadline =
+      SystemClock::now() + kRoundedArbitraryDuration;
+  EXPECT_TRUE(notification.try_acquire_until(deadline));
+  EXPECT_LT(SystemClock::now(), deadline);
+
+  // Ensure it blocks and fails when not notified.
+  EXPECT_FALSE(notification.try_acquire_until(deadline));
+  EXPECT_GE(SystemClock::now(), deadline);
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index 240f111..aa99a50 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -43,6 +43,10 @@
       "$dir_pw_sync_stl:counting_semaphore_backend"
   pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
   pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_stl:timed_mutex_backend"
+  pw_sync_THREAD_NOTIFICATION_BACKEND =
+      "$dir_pw_sync:binary_semaphore_thread_notification_backend"
+  pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND =
+      "$dir_pw_sync:binary_semaphore_timed_thread_notification_backend"
 
   # Configure backend for pw_sys_io facade.
   pw_sys_io_BACKEND = "$dir_pw_sys_io_stdio"