pw_sync: Adds semaphores & mutexes

Adds the BinarySemaphore, CountingSemaphore, and Mutex to the
pw_sync module along with an STL backend for each.

Change-Id: I54a3a64e702202a319ed2c9068bf37412d3fd240
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/24241
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_sync/BUILD b/pw_sync/BUILD
index ceb60c6..18d1962 100644
--- a/pw_sync/BUILD
+++ b/pw_sync/BUILD
@@ -23,9 +23,105 @@
 licenses(["notice"])  # Apache License 2.0
 
 # TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_SYNC_BINARY_SEMAPHORE_BACKEND = "//pw_sync_stl:binary_semaphore"
+PW_SYNC_COUNTING_SEMAPHORE_BACKEND = "//pw_sync_stl:counting_semaphore"
+PW_SYNC_MUTEX_BACKEND = "//pw_sync_stl:mutex"
 PW_SYNC_SPIN_LOCK_BACKEND = "//pw_sync_stl:spin_lock"
 
 pw_cc_library(
+    name = "binary_semaphore_facade",
+    hdrs = [
+        "public/pw_sync/binary_semaphore.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "binary_semaphore.cc"
+    ],
+    deps = [
+        PW_SYNC_BINARY_SEMAPHORE_BACKEND + "_headers",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    deps = [
+        ":binary_semaphore_facade",
+        PW_SYNC_BINARY_SEMAPHORE_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore_backend",
+    deps = [
+       PW_SYNC_BINARY_SEMAPHORE_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_facade",
+    hdrs = [
+        "public/pw_sync/counting_semaphore.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "counting_semaphore.cc"
+    ],
+    deps = [
+        PW_SYNC_COUNTING_SEMAPHORE_BACKEND + "_headers",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    deps = [
+        ":counting_semaphore_facade",
+        PW_SYNC_COUNTING_SEMAPHORE_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_backend",
+    deps = [
+       PW_SYNC_COUNTING_SEMAPHORE_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_facade",
+    hdrs = [
+        "public/pw_sync/mutex.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "mutex.cc"
+    ],
+    deps = [
+        PW_SYNC_MUTEX_BACKEND + "_headers",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_facade",
+        PW_SYNC_MUTEX_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_backend",
+    deps = [
+       PW_SYNC_MUTEX_BACKEND,
+    ],
+)
+
+pw_cc_library(
     name = "spin_lock_facade",
     hdrs = [
         "public/pw_sync/spin_lock.h",
@@ -64,6 +160,45 @@
 )
 
 pw_cc_test(
+    name = "binary_semaphore_facade_test",
+    srcs = [
+        "binary_semaphore_facade_test.cc",
+        "binary_semaphore_facade_test_c.c",
+    ],
+    deps = [
+        ":binary_semaphore",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "counting_semaphore_facade_test",
+    srcs = [
+        "counting_semaphore_facade_test.cc",
+        "counting_semaphore_facade_test_c.c",
+    ],
+    deps = [
+        ":counting_semaphore",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "mutex_facade_test",
+    srcs = [
+        "mutex_facade_test.cc",
+        "mutex_facade_test_c.c",
+    ],
+    deps = [
+        ":mutex",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
     name = "spin_lock_facade_test",
     srcs = [
         "spin_lock_facade_test.cc",
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index cf292b8..058c4f0 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -20,6 +20,15 @@
 import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
+  # Backend for the pw_sync module's binary semaphore.
+  pw_sync_BINARY_SEMAPHORE_BACKEND = ""
+
+  # Backend for the pw_sync module's counting semaphore.
+  pw_sync_COUNTING_SEMAPHORE_BACKEND = ""
+
+  # Backend for the pw_sync module's mutex.
+  pw_sync_MUTEX_BACKEND = ""
+
   # Backend for the pw_sync module's spin lock.
   pw_sync_SPIN_LOCK_BACKEND = ""
 }
@@ -29,6 +38,39 @@
   visibility = [ ":*" ]
 }
 
+pw_facade("binary_semaphore") {
+  backend = pw_sync_BINARY_SEMAPHORE_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/binary_semaphore.h" ]
+  public_deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_preprocessor",
+  ]
+  sources = [ "binary_semaphore.cc" ]
+}
+
+pw_facade("counting_semaphore") {
+  backend = pw_sync_COUNTING_SEMAPHORE_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/counting_semaphore.h" ]
+  public_deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_preprocessor",
+  ]
+  sources = [ "counting_semaphore.cc" ]
+}
+
+pw_facade("mutex") {
+  backend = pw_sync_MUTEX_BACKEND
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_sync/mutex.h" ]
+  public_deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_preprocessor",
+  ]
+  sources = [ "mutex.cc" ]
+}
+
 pw_facade("spin_lock") {
   backend = pw_sync_SPIN_LOCK_BACKEND
   public_configs = [ ":public_include_path" ]
@@ -37,15 +79,59 @@
   sources = [ "spin_lock.cc" ]
 }
 
-pw_test_group("tests") {
-  tests = [ ":spin_lock_facade_test" ]
-}
-
 pw_source_set("yield_core") {
   public = [ "public/pw_sync/yield_core.h" ]
   public_configs = [ ":public_include_path" ]
 }
 
+pw_test_group("tests") {
+  tests = [
+    ":binary_semaphore_facade_test",
+    ":counting_semaphore_facade_test",
+    ":mutex_facade_test",
+    ":spin_lock_facade_test",
+  ]
+}
+
+pw_test("binary_semaphore_facade_test") {
+  enable_if = pw_sync_BINARY_SEMAPHORE_BACKEND != ""
+  sources = [
+    "binary_semaphore_facade_test.cc",
+    "binary_semaphore_facade_test_c.c",
+  ]
+  deps = [
+    ":binary_semaphore",
+    "$dir_pw_preprocessor",
+    pw_sync_BINARY_SEMAPHORE_BACKEND,
+  ]
+}
+
+pw_test("counting_semaphore_facade_test") {
+  enable_if = pw_sync_COUNTING_SEMAPHORE_BACKEND != ""
+  sources = [
+    "counting_semaphore_facade_test.cc",
+    "counting_semaphore_facade_test_c.c",
+  ]
+  deps = [
+    ":counting_semaphore",
+    "$dir_pw_preprocessor",
+    pw_sync_COUNTING_SEMAPHORE_BACKEND,
+  ]
+}
+
+pw_test("mutex_facade_test") {
+  enable_if = pw_sync_MUTEX_BACKEND != ""
+  sources = [
+    "mutex_facade_test.cc",
+    "mutex_facade_test_c.c",
+  ]
+  deps = [
+    ":mutex",
+    "$dir_pw_preprocessor",
+    pw_sync_MUTEX_BACKEND,
+  ]
+}
+
 pw_test("spin_lock_facade_test") {
   enable_if = pw_sync_SPIN_LOCK_BACKEND != ""
   sources = [
diff --git a/pw_sync/binary_semaphore.cc b/pw_sync/binary_semaphore.cc
new file mode 100644
index 0000000..439504b
--- /dev/null
+++ b/pw_sync/binary_semaphore.cc
@@ -0,0 +1,49 @@
+// 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 "pw_sync/binary_semaphore.h"
+
+using pw::chrono::SystemClock;
+
+extern "C" void pw_sync_BinarySemaphore_Release(
+    pw_sync_BinarySemaphore* semaphore) {
+  semaphore->release();
+}
+
+extern "C" void pw_sync_BinarySemaphore_Acquire(
+    pw_sync_BinarySemaphore* semaphore) {
+  semaphore->acquire();
+}
+
+extern "C" bool pw_sync_BinarySemaphore_TryAcquire(
+    pw_sync_BinarySemaphore* semaphore) {
+  return semaphore->try_acquire();
+}
+
+extern "C" bool pw_sync_BinarySemaphore_TryAcquireFor(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least) {
+  return semaphore->try_acquire_for(SystemClock::duration(for_at_least));
+}
+
+extern "C" bool pw_sync_BinarySemaphore_TryAcquireUntil(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  return semaphore->try_acquire_until(SystemClock::time_point(
+      SystemClock::duration(until_at_least.ticks_since_epoch)));
+}
+
+extern "C" ptrdiff_t pw_sync_BinarySemaphore_Max(void) {
+  return pw::sync::BinarySemaphore::max();
+}
diff --git a/pw_sync/binary_semaphore_facade_test.cc b/pw_sync/binary_semaphore_facade_test.cc
new file mode 100644
index 0000000..7c2273c
--- /dev/null
+++ b/pw_sync/binary_semaphore_facade_test.cc
@@ -0,0 +1,170 @@
+// 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/binary_semaphore.h"
+
+using pw::chrono::SystemClock;
+
+namespace pw::sync {
+namespace {
+
+extern "C" {
+
+// Functions defined in binary_semaphore_facade_test_c.c which call the API
+// from C.
+void pw_sync_BinarySemaphore_CallRelease(pw_sync_BinarySemaphore* semaphore);
+void pw_sync_BinarySemaphore_CallAcquire(pw_sync_BinarySemaphore* semaphore);
+bool pw_sync_BinarySemaphore_CallTryAcquire(pw_sync_BinarySemaphore* semaphore);
+bool pw_sync_BinarySemaphore_CallTryAcquireFor(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least);
+bool pw_sync_BinarySemaphore_CallTryAcquireUntil(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least);
+ptrdiff_t pw_sync_BinarySemaphore_CallMax(void);
+
+}  // extern "C"
+
+static constexpr auto kArbitraryDuration = std::chrono::milliseconds(42);
+// We can't control the SystemClock's period configuration, so just in case
+// duration cannot be accurately expressed in integer ticks, round the
+// duration w/ duration_cast.
+static constexpr auto kRoundedArbitraryDuration =
+    std::chrono::duration_cast<SystemClock::duration>(kArbitraryDuration);
+static constexpr pw_chrono_SystemClock_TickCount kRoundedArbitraryDurationInC =
+    kRoundedArbitraryDuration.count();
+
+TEST(BinarySemaphore, EmptyInitialState) {
+  BinarySemaphore semaphore;
+  EXPECT_FALSE(semaphore.try_acquire());
+}
+
+// TODO(pwbug/291): Add real concurrency tests once we have pw::thread.
+
+TEST(BinarySemaphore, Release) {
+  BinarySemaphore semaphore;
+  semaphore.release();
+  semaphore.release();
+  semaphore.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(semaphore.try_acquire());
+}
+
+BinarySemaphore empty_initial_semaphore;
+TEST(BinarySemaphore, EmptyInitialStateStatic) {
+  EXPECT_FALSE(empty_initial_semaphore.try_acquire());
+}
+
+BinarySemaphore release_semaphore;
+TEST(BinarySemaphore, ReleaseStatic) {
+  release_semaphore.release();
+  release_semaphore.release();
+  release_semaphore.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(release_semaphore.try_acquire());
+}
+
+TEST(BinarySemaphore, TryAcquireFor) {
+  BinarySemaphore semaphore;
+  semaphore.release();
+
+  SystemClock::time_point before = SystemClock::now();
+  EXPECT_TRUE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
+  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
+
+  // Ensure it blocks and fails when empty.
+  before = SystemClock::now();
+  EXPECT_FALSE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
+  time_elapsed = SystemClock::now() - before;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
+}
+
+TEST(BinarySemaphore, TryAcquireUntil) {
+  BinarySemaphore semaphore;
+  semaphore.release();
+
+  const SystemClock::time_point deadline =
+      SystemClock::now() + kRoundedArbitraryDuration;
+  EXPECT_TRUE(semaphore.try_acquire_until(deadline));
+  EXPECT_LT(SystemClock::now(), deadline);
+
+  // Ensure it blocks and fails when empty.
+  EXPECT_FALSE(semaphore.try_acquire_until(deadline));
+  EXPECT_GE(SystemClock::now(), deadline);
+}
+
+TEST(BinarySemaphore, EmptyInitialStateInC) {
+  BinarySemaphore semaphore;
+  EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquire(&semaphore));
+}
+
+TEST(BinarySemaphore, ReleaseInC) {
+  BinarySemaphore semaphore;
+  pw_sync_BinarySemaphore_CallRelease(&semaphore);
+  pw_sync_BinarySemaphore_CallRelease(&semaphore);
+  pw_sync_BinarySemaphore_CallAcquire(&semaphore);
+  // Ensure it fails when empty.
+  EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquire(&semaphore));
+}
+
+TEST(BinarySemaphore, TryAcquireForInC) {
+  BinarySemaphore semaphore;
+  pw_sync_BinarySemaphore_CallRelease(&semaphore);
+
+  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
+  ASSERT_TRUE(pw_sync_BinarySemaphore_CallTryAcquireFor(
+      &semaphore, kRoundedArbitraryDurationInC));
+  pw_chrono_SystemClock_TickCount time_elapsed =
+      pw_chrono_SystemClock_Now().ticks_since_epoch - before.ticks_since_epoch;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDurationInC);
+
+  // Ensure it blocks and fails when empty.
+  before = pw_chrono_SystemClock_Now();
+  EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquireFor(
+      &semaphore, kRoundedArbitraryDurationInC));
+  time_elapsed =
+      pw_chrono_SystemClock_Now().ticks_since_epoch - before.ticks_since_epoch;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDurationInC);
+}
+
+TEST(BinarySemaphore, TryAcquireUntilInC) {
+  BinarySemaphore semaphore;
+  pw_sync_BinarySemaphore_CallRelease(&semaphore);
+
+  pw_chrono_SystemClock_TimePoint deadline;
+  deadline.ticks_since_epoch = pw_chrono_SystemClock_Now().ticks_since_epoch +
+                               kRoundedArbitraryDurationInC;
+  ASSERT_TRUE(
+      pw_sync_BinarySemaphore_CallTryAcquireUntil(&semaphore, deadline));
+  EXPECT_LT(pw_chrono_SystemClock_Now().ticks_since_epoch,
+            deadline.ticks_since_epoch);
+
+  // Ensure it blocks and fails when empty.
+  EXPECT_FALSE(
+      pw_sync_BinarySemaphore_CallTryAcquireUntil(&semaphore, deadline));
+  EXPECT_GE(pw_chrono_SystemClock_Now().ticks_since_epoch,
+            deadline.ticks_since_epoch);
+}
+
+TEST(BinarySemaphore, MaxInC) {
+  EXPECT_EQ(BinarySemaphore::max(), pw_sync_BinarySemaphore_Max());
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/binary_semaphore_facade_test_c.c b/pw_sync/binary_semaphore_facade_test_c.c
new file mode 100644
index 0000000..5145301
--- /dev/null
+++ b/pw_sync/binary_semaphore_facade_test_c.c
@@ -0,0 +1,49 @@
+// 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.
+
+// These tests call the pw_sync module counting_semaphore API from C. The return
+// values are checked in the main C++ tests.
+
+#include <stdbool.h>
+
+#include "pw_sync/binary_semaphore.h"
+
+void pw_sync_BinarySemaphore_CallRelease(pw_sync_BinarySemaphore* semaphore) {
+  pw_sync_BinarySemaphore_Release(semaphore);
+}
+
+void pw_sync_BinarySemaphore_CallAcquire(pw_sync_BinarySemaphore* semaphore) {
+  pw_sync_BinarySemaphore_Acquire(semaphore);
+}
+
+bool pw_sync_BinarySemaphore_CallTryAcquire(
+    pw_sync_BinarySemaphore* semaphore) {
+  return pw_sync_BinarySemaphore_TryAcquire(semaphore);
+}
+
+bool pw_sync_BinarySemaphore_CallTryAcquireFor(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least) {
+  return pw_sync_BinarySemaphore_TryAcquireFor(semaphore, for_at_least);
+}
+
+bool pw_sync_BinarySemaphore_CallTryAcquireUntil(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  return pw_sync_BinarySemaphore_TryAcquireUntil(semaphore, until_at_least);
+}
+
+ptrdiff_t pw_sync_BinarySemaphore_CallMax(void) {
+  return pw_sync_BinarySemaphore_Max();
+}
diff --git a/pw_sync/counting_semaphore.cc b/pw_sync/counting_semaphore.cc
new file mode 100644
index 0000000..9915e5e
--- /dev/null
+++ b/pw_sync/counting_semaphore.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 "pw_sync/counting_semaphore.h"
+
+using pw::chrono::SystemClock;
+
+extern "C" void pw_sync_CountingSemaphore_Release(
+    pw_sync_CountingSemaphore* semaphore) {
+  semaphore->release();
+}
+
+extern "C" void pw_sync_CountingSemaphore_ReleaseNum(
+    pw_sync_CountingSemaphore* semaphore, ptrdiff_t update) {
+  semaphore->release(update);
+}
+
+extern "C" void pw_sync_CountingSemaphore_Acquire(
+    pw_sync_CountingSemaphore* semaphore) {
+  semaphore->acquire();
+}
+
+extern "C" bool pw_sync_CountingSemaphore_TryAcquire(
+    pw_sync_CountingSemaphore* semaphore) {
+  return semaphore->try_acquire();
+}
+
+extern "C" bool pw_sync_CountingSemaphore_TryAcquireFor(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least) {
+  return semaphore->try_acquire_for(SystemClock::duration(for_at_least));
+}
+
+extern "C" bool pw_sync_CountingSemaphore_TryAcquireUntil(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  return semaphore->try_acquire_until(SystemClock::time_point(
+      SystemClock::duration(until_at_least.ticks_since_epoch)));
+}
+
+extern "C" ptrdiff_t pw_sync_CountingSemaphore_Max(void) {
+  return pw::sync::CountingSemaphore::max();
+}
diff --git a/pw_sync/counting_semaphore_facade_test.cc b/pw_sync/counting_semaphore_facade_test.cc
new file mode 100644
index 0000000..3d5b331
--- /dev/null
+++ b/pw_sync/counting_semaphore_facade_test.cc
@@ -0,0 +1,200 @@
+// 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/counting_semaphore.h"
+
+using pw::chrono::SystemClock;
+
+namespace pw::sync {
+namespace {
+
+extern "C" {
+
+// Functions defined in counting_semaphore_facade_test_c.c which call the API
+// from C.
+void pw_sync_CountingSemaphore_CallRelease(
+    pw_sync_CountingSemaphore* semaphore);
+void pw_sync_CountingSemaphore_CallReleaseNum(
+    pw_sync_CountingSemaphore* semaphore, ptrdiff_t update);
+void pw_sync_CountingSemaphore_CallAcquire(
+    pw_sync_CountingSemaphore* semaphore);
+bool pw_sync_CountingSemaphore_CallTryAcquire(
+    pw_sync_CountingSemaphore* semaphore);
+bool pw_sync_CountingSemaphore_CallTryAcquireFor(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least);
+bool pw_sync_CountingSemaphore_CallTryAcquireUntil(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least);
+ptrdiff_t pw_sync_CountingSemaphore_CallMax(void);
+
+}  // extern "C"
+
+static constexpr auto kArbitraryDuration = std::chrono::milliseconds(42);
+// We can't control the SystemClock's period configuration, so just in case
+// duration cannot be accurately expressed in integer ticks, round the
+// duration w/ duration_cast.
+static constexpr auto kRoundedArbitraryDuration =
+    std::chrono::duration_cast<SystemClock::duration>(kArbitraryDuration);
+static constexpr pw_chrono_SystemClock_TickCount kRoundedArbitraryDurationInC =
+    kRoundedArbitraryDuration.count();
+
+TEST(CountingSemaphore, EmptyInitialState) {
+  CountingSemaphore semaphore;
+  EXPECT_FALSE(semaphore.try_acquire());
+}
+
+// TODO(pwbug/291): Add real concurrency tests once we have pw::thread.
+
+TEST(CountingSemaphore, SingleRelease) {
+  CountingSemaphore semaphore;
+  semaphore.release();
+  semaphore.release();
+  semaphore.acquire();
+  semaphore.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(semaphore.try_acquire());
+}
+
+CountingSemaphore empty_initial_semaphore;
+TEST(CountingSemaphore, EmptyInitialStateStatic) {
+  EXPECT_FALSE(empty_initial_semaphore.try_acquire());
+}
+
+CountingSemaphore release_semaphore;
+TEST(CountingSemaphore, ReleaseStatic) {
+  release_semaphore.release();
+  release_semaphore.release();
+  release_semaphore.acquire();
+  release_semaphore.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(release_semaphore.try_acquire());
+}
+
+TEST(CountingSemaphore, MultiRelease) {
+  CountingSemaphore semaphore;
+  semaphore.release(2);
+  semaphore.release(1);
+  semaphore.acquire();
+  semaphore.acquire();
+  semaphore.acquire();
+  // Ensure it fails when empty.
+  EXPECT_FALSE(semaphore.try_acquire());
+}
+
+TEST(CountingSemaphore, TryAcquireFor) {
+  CountingSemaphore semaphore;
+  semaphore.release();
+
+  SystemClock::time_point before = SystemClock::now();
+  EXPECT_TRUE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
+  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
+
+  // Ensure it blocks and fails when empty.
+  before = SystemClock::now();
+  EXPECT_FALSE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
+  time_elapsed = SystemClock::now() - before;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
+}
+
+TEST(CountingSemaphore, TryAcquireUntil) {
+  CountingSemaphore semaphore;
+  semaphore.release();
+
+  const SystemClock::time_point deadline =
+      SystemClock::now() + kRoundedArbitraryDuration;
+  EXPECT_TRUE(semaphore.try_acquire_until(deadline));
+  EXPECT_LT(SystemClock::now(), deadline);
+
+  // Ensure it blocks and fails when empty.
+  EXPECT_FALSE(semaphore.try_acquire_until(deadline));
+  EXPECT_GE(SystemClock::now(), deadline);
+}
+
+TEST(CountingSemaphore, EmptyInitialStateInC) {
+  CountingSemaphore semaphore;
+  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquire(&semaphore));
+}
+
+TEST(CountingSemaphore, SingeReleaseInC) {
+  CountingSemaphore semaphore;
+  pw_sync_CountingSemaphore_CallRelease(&semaphore);
+  pw_sync_CountingSemaphore_CallRelease(&semaphore);
+  pw_sync_CountingSemaphore_CallAcquire(&semaphore);
+  pw_sync_CountingSemaphore_CallAcquire(&semaphore);
+  // Ensure it fails when empty.
+  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquire(&semaphore));
+}
+
+TEST(CountingSemaphore, MultiReleaseInC) {
+  CountingSemaphore semaphore;
+  pw_sync_CountingSemaphore_CallReleaseNum(&semaphore, 2);
+  pw_sync_CountingSemaphore_CallReleaseNum(&semaphore, 1);
+  pw_sync_CountingSemaphore_CallAcquire(&semaphore);
+  pw_sync_CountingSemaphore_CallAcquire(&semaphore);
+  pw_sync_CountingSemaphore_CallAcquire(&semaphore);
+  // Ensure it fails when empty.
+  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquire(&semaphore));
+}
+
+TEST(CountingSemaphore, TryAcquireForInC) {
+  CountingSemaphore semaphore;
+  pw_sync_CountingSemaphore_CallRelease(&semaphore);
+
+  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
+  ASSERT_TRUE(pw_sync_CountingSemaphore_CallTryAcquireFor(
+      &semaphore, kRoundedArbitraryDurationInC));
+  pw_chrono_SystemClock_TickCount time_elapsed =
+      pw_chrono_SystemClock_Now().ticks_since_epoch - before.ticks_since_epoch;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDurationInC);
+
+  // Ensure it blocks and fails when empty.
+  before = pw_chrono_SystemClock_Now();
+  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquireFor(
+      &semaphore, kRoundedArbitraryDurationInC));
+  time_elapsed =
+      pw_chrono_SystemClock_Now().ticks_since_epoch - before.ticks_since_epoch;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDurationInC);
+}
+
+TEST(CountingSemaphore, TryAcquireUntilInC) {
+  CountingSemaphore semaphore;
+  pw_sync_CountingSemaphore_CallRelease(&semaphore);
+
+  pw_chrono_SystemClock_TimePoint deadline;
+  deadline.ticks_since_epoch = pw_chrono_SystemClock_Now().ticks_since_epoch +
+                               kRoundedArbitraryDurationInC;
+  ASSERT_TRUE(
+      pw_sync_CountingSemaphore_CallTryAcquireUntil(&semaphore, deadline));
+  EXPECT_LT(pw_chrono_SystemClock_Now().ticks_since_epoch,
+            deadline.ticks_since_epoch);
+
+  // Ensure it blocks and fails when empty.
+  EXPECT_FALSE(
+      pw_sync_CountingSemaphore_CallTryAcquireUntil(&semaphore, deadline));
+  EXPECT_GE(pw_chrono_SystemClock_Now().ticks_since_epoch,
+            deadline.ticks_since_epoch);
+}
+
+TEST(CountingSemaphore, MaxInC) {
+  EXPECT_EQ(CountingSemaphore::max(), pw_sync_CountingSemaphore_Max());
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/counting_semaphore_facade_test_c.c b/pw_sync/counting_semaphore_facade_test_c.c
new file mode 100644
index 0000000..592df9b
--- /dev/null
+++ b/pw_sync/counting_semaphore_facade_test_c.c
@@ -0,0 +1,56 @@
+// 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.
+
+// These tests call the pw_sync module counting_semaphore API from C. The return
+// values are checked in the main C++ tests.
+
+#include <stdbool.h>
+
+#include "pw_sync/counting_semaphore.h"
+
+void pw_sync_CountingSemaphore_CallRelease(
+    pw_sync_CountingSemaphore* semaphore) {
+  pw_sync_CountingSemaphore_Release(semaphore);
+}
+
+void pw_sync_CountingSemaphore_CallReleaseNum(
+    pw_sync_CountingSemaphore* semaphore, ptrdiff_t update) {
+  pw_sync_CountingSemaphore_ReleaseNum(semaphore, update);
+}
+
+void pw_sync_CountingSemaphore_CallAcquire(
+    pw_sync_CountingSemaphore* semaphore) {
+  pw_sync_CountingSemaphore_Acquire(semaphore);
+}
+
+bool pw_sync_CountingSemaphore_CallTryAcquire(
+    pw_sync_CountingSemaphore* semaphore) {
+  return pw_sync_CountingSemaphore_TryAcquire(semaphore);
+}
+
+bool pw_sync_CountingSemaphore_CallTryAcquireFor(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least) {
+  return pw_sync_CountingSemaphore_TryAcquireFor(semaphore, for_at_least);
+}
+
+bool pw_sync_CountingSemaphore_CallTryAcquireUntil(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  return pw_sync_CountingSemaphore_TryAcquireUntil(semaphore, until_at_least);
+}
+
+ptrdiff_t pw_sync_CountingSemaphore_CallMax(void) {
+  return pw_sync_CountingSemaphore_Max();
+}
diff --git a/pw_sync/mutex.cc b/pw_sync/mutex.cc
new file mode 100644
index 0000000..85c3c92
--- /dev/null
+++ b/pw_sync/mutex.cc
@@ -0,0 +1,35 @@
+// 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 "pw_sync/mutex.h"
+
+using pw::chrono::SystemClock;
+
+extern "C" void pw_sync_Mutex_Lock(pw_sync_Mutex* mutex) { mutex->lock(); }
+
+extern "C" bool pw_sync_Mutex_TryLock(pw_sync_Mutex* mutex) {
+  return mutex->try_lock();
+}
+
+extern "C" bool pw_sync_Mutex_TryLockFor(
+    pw_sync_Mutex* mutex, pw_chrono_SystemClock_TickCount for_at_least) {
+  return mutex->try_lock_for(SystemClock::duration(for_at_least));
+}
+
+extern "C" bool pw_sync_Mutex_TryLockUntil(
+    pw_sync_Mutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least) {
+  return mutex->try_lock_until(SystemClock::time_point(
+      SystemClock::duration(until_at_least.ticks_since_epoch)));
+}
+extern "C" void pw_sync_Mutex_Unlock(pw_sync_Mutex* mutex) { mutex->unlock(); }
diff --git a/pw_sync/mutex_facade_test.cc b/pw_sync/mutex_facade_test.cc
new file mode 100644
index 0000000..046682a
--- /dev/null
+++ b/pw_sync/mutex_facade_test.cc
@@ -0,0 +1,160 @@
+// 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/mutex.h"
+
+using pw::chrono::SystemClock;
+
+namespace pw::sync {
+namespace {
+
+extern "C" {
+
+// Functions defined in mutex_facade_test_c.c which call the API from C.
+void pw_sync_Mutex_CallLock(pw_sync_Mutex* mutex);
+bool pw_sync_Mutex_CallTryLock(pw_sync_Mutex* mutex);
+bool pw_sync_Mutex_CallTryLockFor(pw_sync_Mutex* mutex,
+                                  pw_chrono_SystemClock_TickCount for_at_least);
+bool pw_sync_Mutex_CallTryLockUntil(
+    pw_sync_Mutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least);
+void pw_sync_Mutex_CallUnlock(pw_sync_Mutex* mutex);
+
+}  // extern "C"
+
+static constexpr auto kArbitraryDuration = std::chrono::milliseconds(42);
+// We can't control the SystemClock's period configuration, so just in case
+// duration cannot be accurately expressed in integer ticks, round the
+// duration w/ duration_cast.
+static constexpr auto kRoundedArbitraryDuration =
+    std::chrono::duration_cast<SystemClock::duration>(kArbitraryDuration);
+static constexpr pw_chrono_SystemClock_TickCount kRoundedArbitraryDurationInC =
+    kRoundedArbitraryDuration.count();
+
+// TODO(pwbug/291): Add real concurrency tests once we have pw::thread.
+
+TEST(Mutex, LockUnlock) {
+  pw::sync::Mutex mutex;
+  mutex.lock();
+  // Ensure it fails to lock when already held.
+  EXPECT_FALSE(mutex.try_lock());
+  mutex.unlock();
+}
+
+Mutex static_mutex;
+TEST(Mutex, LockUnlockStatic) {
+  static_mutex.lock();
+  // Ensure it fails to lock when already held.
+  EXPECT_FALSE(static_mutex.try_lock());
+  static_mutex.unlock();
+}
+
+TEST(Mutex, TryLockUnlock) {
+  pw::sync::Mutex mutex;
+  ASSERT_TRUE(mutex.try_lock());
+  // Ensure it fails to lock when already held.
+  EXPECT_FALSE(mutex.try_lock());
+  mutex.unlock();
+}
+
+TEST(Mutex, TryLockUnlockFor) {
+  pw::sync::Mutex mutex;
+
+  SystemClock::time_point before = SystemClock::now();
+  ASSERT_TRUE(mutex.try_lock_for(kRoundedArbitraryDuration));
+  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
+
+  // Ensure it blocks and fails to lock when already held.
+  before = SystemClock::now();
+  EXPECT_FALSE(mutex.try_lock_for(kRoundedArbitraryDuration));
+  time_elapsed = SystemClock::now() - before;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
+
+  mutex.unlock();
+}
+
+TEST(Mutex, TryLockUnlockUntil) {
+  pw::sync::Mutex mutex;
+
+  const SystemClock::time_point deadline =
+      SystemClock::now() + kRoundedArbitraryDuration;
+  ASSERT_TRUE(mutex.try_lock_until(deadline));
+  EXPECT_LT(SystemClock::now(), deadline);
+
+  // Ensure it blocks and fails to lock when already held.
+  EXPECT_FALSE(
+      mutex.try_lock_until(SystemClock::now() + kRoundedArbitraryDuration));
+  EXPECT_GE(SystemClock::now(), deadline);
+
+  mutex.unlock();
+}
+
+TEST(Mutex, LockUnlockInC) {
+  pw::sync::Mutex mutex;
+  pw_sync_Mutex_CallLock(&mutex);
+  pw_sync_Mutex_CallUnlock(&mutex);
+}
+
+TEST(Mutex, TryLockUnlockInC) {
+  pw::sync::Mutex mutex;
+  ASSERT_TRUE(pw_sync_Mutex_CallTryLock(&mutex));
+  // Ensure it fails to lock when already held.
+  EXPECT_FALSE(pw_sync_Mutex_CallTryLock(&mutex));
+  pw_sync_Mutex_CallUnlock(&mutex);
+}
+
+TEST(Mutex, TryLockUnlockForInC) {
+  pw::sync::Mutex mutex;
+
+  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
+  ASSERT_TRUE(
+      pw_sync_Mutex_CallTryLockFor(&mutex, kRoundedArbitraryDurationInC));
+  pw_chrono_SystemClock_TickCount time_elapsed =
+      pw_chrono_SystemClock_Now().ticks_since_epoch - before.ticks_since_epoch;
+  EXPECT_LT(time_elapsed, kRoundedArbitraryDurationInC);
+
+  // Ensure it blocks and fails to lock when already held.
+  before = pw_chrono_SystemClock_Now();
+  EXPECT_FALSE(
+      pw_sync_Mutex_CallTryLockFor(&mutex, kRoundedArbitraryDurationInC));
+  time_elapsed =
+      pw_chrono_SystemClock_Now().ticks_since_epoch - before.ticks_since_epoch;
+  EXPECT_GE(time_elapsed, kRoundedArbitraryDurationInC);
+
+  pw_sync_Mutex_CallUnlock(&mutex);
+}
+
+TEST(Mutex, TryLockUnlockUntilInC) {
+  pw::sync::Mutex mutex;
+  pw_chrono_SystemClock_TimePoint deadline;
+  deadline.ticks_since_epoch = pw_chrono_SystemClock_Now().ticks_since_epoch +
+                               kRoundedArbitraryDurationInC;
+  ASSERT_TRUE(pw_sync_Mutex_CallTryLockUntil(&mutex, deadline));
+  EXPECT_LT(pw_chrono_SystemClock_Now().ticks_since_epoch,
+            deadline.ticks_since_epoch);
+
+  // Ensure it blocks and fails to lock when already held.
+  EXPECT_FALSE(pw_sync_Mutex_CallTryLockUntil(&mutex, deadline));
+  EXPECT_GE(pw_chrono_SystemClock_Now().ticks_since_epoch,
+            deadline.ticks_since_epoch);
+
+  mutex.unlock();
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/mutex_facade_test_c.c b/pw_sync/mutex_facade_test_c.c
new file mode 100644
index 0000000..9004912
--- /dev/null
+++ b/pw_sync/mutex_facade_test_c.c
@@ -0,0 +1,40 @@
+// 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.
+
+// These tests call the pw_sync module mutex API from C. The return values are
+// checked in the main C++ tests.
+
+#include <stdbool.h>
+
+#include "pw_sync/mutex.h"
+
+void pw_sync_Mutex_CallLock(pw_sync_Mutex* mutex) { pw_sync_Mutex_Lock(mutex); }
+
+bool pw_sync_Mutex_CallTryLock(pw_sync_Mutex* mutex) {
+  return pw_sync_Mutex_TryLock(mutex);
+}
+
+bool pw_sync_Mutex_CallTryLockFor(
+    pw_sync_Mutex* mutex, pw_chrono_SystemClock_TickCount for_at_least) {
+  return pw_sync_Mutex_TryLockFor(mutex, for_at_least);
+}
+
+bool pw_sync_Mutex_CallTryLockUntil(
+    pw_sync_Mutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least) {
+  return pw_sync_Mutex_TryLockUntil(mutex, until_at_least);
+}
+
+void pw_sync_Mutex_CallUnlock(pw_sync_Mutex* mutex) {
+  pw_sync_Mutex_Unlock(mutex);
+}
diff --git a/pw_sync/public/pw_sync/binary_semaphore.h b/pw_sync/public/pw_sync/binary_semaphore.h
new file mode 100644
index 0000000..53925d3
--- /dev/null
+++ b/pw_sync/public/pw_sync/binary_semaphore.h
@@ -0,0 +1,116 @@
+// 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.
+#pragma once
+
+#include <stddef.h>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_preprocessor/util.h"
+
+#ifdef __cplusplus
+
+#include "pw_sync_backend/binary_semaphore_native.h"
+
+namespace pw::sync {
+
+// BinarySemaphore is a specialization of CountingSemaphore with arbitrary
+// token limit of 1. Note that that max() is >= 1, meaning it may be
+// released up to max() times but only acquired once for those N releases.
+// Implementations of BinarySemaphore are typically more efficient than the
+// default implementation of CountingSemaphore. The entire API is thread safe
+// but only a subset is IRQ safe.
+//
+// WARNING: In order to support global statically constructed BinarySemaphores,
+// the backend MUST ensure that any initialization required in your environment
+// prior to the creation and/or initialization of the native semaphore
+// (e.g. kernel initialization), is done before or during the invocation of the
+// global static C++ constructors.
+class BinarySemaphore {
+ public:
+  using native_handle_type = backend::NativeBinarySemaphoreHandle;
+
+  BinarySemaphore();
+  ~BinarySemaphore();
+  BinarySemaphore(const BinarySemaphore&) = delete;
+  BinarySemaphore(BinarySemaphore&&) = delete;
+  BinarySemaphore& operator=(const BinarySemaphore&) = delete;
+  BinarySemaphore& operator=(BinarySemaphore&&) = delete;
+
+  // Atomically increments the internal counter by 1 up to max_count.
+  // Any thread(s) waiting for the counter to be greater than 0,
+  // such as due to being blocked in acquire, will subsequently be unblocked.
+  // This is IRQ safe.
+  //
+  // PRECONDITIONS:
+  //   1 <= max() - counter
+  void release();
+
+  // Decrements the internal counter to 0 or blocks indefinitely until it can.
+  // This is thread safe.
+
+  //   update <= max() - counter
+  void acquire();
+
+  // Attempts to decrement by the internal counter to 0 without blocking.
+  // Returns true if the internal counter was reset successfully.
+  // This is IRQ safe.
+  bool try_acquire();
+
+  // Attempts to decrement the internal counter to 0 where, if needed, blocking
+  // for at least the specified duration.
+  // Returns true if the internal counter was decremented successfully.
+  // This is thread safe.
+  bool try_acquire_for(chrono::SystemClock::duration for_at_least);
+
+  // Attempts to decrement the internal counter to 0 where, if needed, blocking
+  // until at least the specified time point.
+  // Returns true if the internal counter was decremented successfully.
+  // This is thread safe.
+  bool try_acquire_until(chrono::SystemClock::time_point until_at_least);
+
+  static constexpr ptrdiff_t max() { return backend::kBinarySemaphoreMaxValue; }
+
+  native_handle_type native_handle();
+
+ private:
+  // This may be a wrapper around a native type with additional members.
+  backend::NativeBinarySemaphore native_type_;
+};
+
+}  // namespace pw::sync
+
+#include "pw_sync_backend/binary_semaphore_inline.h"
+
+using pw_sync_BinarySemaphore = pw::sync::BinarySemaphore;
+
+#else  // !defined(__cplusplus)
+
+typedef struct pw_sync_BinarySemaphore pw_sync_BinarySemaphore;
+
+#endif  // __cplusplus
+
+PW_EXTERN_C_START
+
+void pw_sync_BinarySemaphore_Release(pw_sync_BinarySemaphore* semaphore);
+void pw_sync_BinarySemaphore_Acquire(pw_sync_BinarySemaphore* semaphore);
+bool pw_sync_BinarySemaphore_TryAcquire(pw_sync_BinarySemaphore* semaphore);
+bool pw_sync_BinarySemaphore_TryAcquireFor(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least);
+bool pw_sync_BinarySemaphore_TryAcquireUntil(
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least);
+ptrdiff_t pw_sync_BinarySemaphore_Max(void);
+
+PW_EXTERN_C_END
diff --git a/pw_sync/public/pw_sync/counting_semaphore.h b/pw_sync/public/pw_sync/counting_semaphore.h
new file mode 100644
index 0000000..18dee02
--- /dev/null
+++ b/pw_sync/public/pw_sync/counting_semaphore.h
@@ -0,0 +1,120 @@
+// 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.
+#pragma once
+
+#include <stddef.h>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_preprocessor/util.h"
+
+#ifdef __cplusplus
+
+#include "pw_sync_backend/counting_semaphore_native.h"
+
+namespace pw::sync {
+
+// The CountingSemaphore is a synchronization primitive that can be used for
+// counting events and/or resource management where receiver(s) can block on
+// acquire until notifier(s) signal by invoking release.
+// Note that unlike Mutexes, priority inheritance is not used by semaphores
+// meaning semaphores are subject to unbounded priority inversions.
+// Pigweed does not recommend semaphores for mutual exclusion. The entire API is
+// thread safe but only a subset is IRQ safe.
+//
+// WARNING: In order to support global statically constructed
+// CountingSemaphores, the backend MUST ensure that any initialization required
+// in your environment prior to the creation and/or initialization of the native
+// semaphore (e.g. kernel initialization), is done before or during the
+// invocation of the global static C++ constructors.
+class CountingSemaphore {
+ public:
+  using native_handle_type = backend::NativeCountingSemaphoreHandle;
+
+  CountingSemaphore();
+  ~CountingSemaphore();
+  CountingSemaphore(const CountingSemaphore&) = delete;
+  CountingSemaphore(CountingSemaphore&&) = delete;
+  CountingSemaphore& operator=(const CountingSemaphore&) = delete;
+  CountingSemaphore& operator=(CountingSemaphore&&) = delete;
+
+  // Atomically increments the internal counter by the value of update.
+  // Any thread(s) waiting for the counter to be greater than 0, i.e.
+  // blocked in acquire, will subsequently be unblocked.
+  // This is IRQ safe.
+  //
+  // PRECONDITIONS:
+  //   update >= 0
+  //   update <= max() - counter
+  void release(ptrdiff_t update = 1);
+
+  // Decrements the internal counter by 1 or blocks indefinitely until it can.
+  // This is thread safe.
+  void acquire();
+
+  // Attempts to decrement by the internal counter by 1 without blocking.
+  // Returns true if the internal counter was decremented successfully.
+  // This is IRQ safe.
+  bool try_acquire();
+
+  // Attempts to decrement the internal counter by 1 where, if needed, blocking
+  // for at least the specified duration.
+  // Returns true if the internal counter was decremented successfully.
+  // This is thread safe.
+  bool try_acquire_for(chrono::SystemClock::duration for_at_least);
+
+  // Attempts to decrement the internal counter by 1 where, if needed, blocking
+  // until at least the specified time point.
+  // Returns true if the internal counter was decremented successfully.
+  // This is thread safe.
+  bool try_acquire_until(chrono::SystemClock::time_point until_at_least);
+
+  static constexpr ptrdiff_t max() {
+    return backend::kCountingSemaphoreMaxValue;
+  }
+
+  native_handle_type native_handle();
+
+ private:
+  // This may be a wrapper around a native type with additional members.
+  backend::NativeCountingSemaphore native_type_;
+};
+
+}  // namespace pw::sync
+
+#include "pw_sync_backend/counting_semaphore_inline.h"
+
+using pw_sync_CountingSemaphore = pw::sync::CountingSemaphore;
+
+#else  // !defined(__cplusplus)
+
+typedef struct pw_sync_CountingSemaphore pw_sync_CountingSemaphore;
+
+#endif  // __cplusplus
+
+PW_EXTERN_C_START
+
+void pw_sync_CountingSemaphore_Release(pw_sync_CountingSemaphore* semaphore);
+void pw_sync_CountingSemaphore_ReleaseNum(pw_sync_CountingSemaphore* semaphore,
+                                          ptrdiff_t update);
+void pw_sync_CountingSemaphore_Acquire(pw_sync_CountingSemaphore* semaphore);
+bool pw_sync_CountingSemaphore_TryAcquire(pw_sync_CountingSemaphore* semaphore);
+bool pw_sync_CountingSemaphore_TryAcquireFor(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TickCount for_at_least);
+bool pw_sync_CountingSemaphore_TryAcquireUntil(
+    pw_sync_CountingSemaphore* semaphore,
+    pw_chrono_SystemClock_TimePoint until_at_least);
+ptrdiff_t pw_sync_CountingSemaphore_Max(void);
+
+PW_EXTERN_C_END
diff --git a/pw_sync/public/pw_sync/mutex.h b/pw_sync/public/pw_sync/mutex.h
new file mode 100644
index 0000000..aef10e3
--- /dev/null
+++ b/pw_sync/public/pw_sync/mutex.h
@@ -0,0 +1,98 @@
+// 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.
+#pragma once
+
+#include <stdbool.h>
+
+#include "pw_chrono/system_clock.h"
+#include "pw_preprocessor/util.h"
+
+#ifdef __cplusplus
+
+#include "pw_sync_backend/mutex_native.h"
+
+namespace pw::sync {
+
+// The Mutex is a synchronization primitive that can be used to protect
+// shared data from being simultaneously accessed by multiple threads.
+// It offers exclusive, non-recursive ownership semantics where priority
+// inheritance is used to solve the classic priority-inversion problem.
+// This is thread safe, but NOT IRQ safe.
+//
+// WARNING: In order to support global statically constructed Mutex, the backend
+// MUST ensure that any initialization required in your environment prior to the
+// creation and/or initialization of the native semaphore (e.g. kernel
+// initialization), is done before or during the invocation of the global static
+// C++ constructors.
+class Mutex {
+ public:
+  using native_handle_type = backend::NativeMutexHandle;
+
+  Mutex();
+  ~Mutex();
+  Mutex(const Mutex&) = delete;
+  Mutex(Mutex&&) = delete;
+  Mutex& operator=(const Mutex&) = delete;
+  Mutex& operator=(Mutex&&) = delete;
+
+  // Locks the mutex, blocking indefinitely. Failures are fatal.
+  void lock();
+
+  // Attempts to lock the mutex in a non-blocking manner.
+  // Returns true if the mutex was successfully acquired.
+  bool try_lock();
+
+  // Attempts to lock the mutex where, if needed, blocking for at least the
+  // specified duration.
+  // Returns true if the mutex was successfully acquired.
+  bool try_lock_for(chrono::SystemClock::duration for_at_least);
+
+  // Attempts to lock the mutex where, if needed, blocking until at least the
+  // specified time_point.
+  // Returns true if the mutex was successfully acquired.
+  bool try_lock_until(chrono::SystemClock::time_point until_at_least);
+
+  // Unlocks the mutex. Failures are fatal.
+  void unlock();
+
+  native_handle_type native_handle();
+
+ private:
+  // This may be a wrapper around a native type with additional members.
+  backend::NativeMutex native_type_;
+};
+
+}  // namespace pw::sync
+
+#include "pw_sync_backend/mutex_inline.h"
+
+using pw_sync_Mutex = pw::sync::Mutex;
+
+#else  // !defined(__cplusplus)
+
+typedef struct pw_sync_Mutex pw_sync_Mutex;
+
+#endif  // __cplusplus
+
+PW_EXTERN_C_START
+
+void pw_sync_Mutex_Lock(pw_sync_Mutex* mutex);
+bool pw_sync_Mutex_TryLock(pw_sync_Mutex* mutex);
+bool pw_sync_Mutex_TryLockFor(pw_sync_Mutex* mutex,
+                              pw_chrono_SystemClock_TickCount for_at_least);
+bool pw_sync_Mutex_TryLockUntil(pw_sync_Mutex* mutex,
+                                pw_chrono_SystemClock_TimePoint until_at_least);
+void pw_sync_Mutex_Unlock(pw_sync_Mutex* mutex);
+
+PW_EXTERN_C_END
diff --git a/pw_sync/spin_lock_facade_test.cc b/pw_sync/spin_lock_facade_test.cc
index c115bd9..a373e79 100644
--- a/pw_sync/spin_lock_facade_test.cc
+++ b/pw_sync/spin_lock_facade_test.cc
@@ -33,6 +33,8 @@
   spin_lock.unlock();
 }
 
+// TODO(pwbug/291): Add real concurrency tests once we have pw::thread.
+
 SpinLock static_spin_lock;
 TEST(SpinLock, LockUnlockStatic) {
   static_spin_lock.lock();
diff --git a/pw_sync_stl/BUILD b/pw_sync_stl/BUILD
index 13b6d64..97dc246 100644
--- a/pw_sync_stl/BUILD
+++ b/pw_sync_stl/BUILD
@@ -22,6 +22,90 @@
 licenses(["notice"])  # Apache License 2.0
 
 pw_cc_library(
+    name = "binary_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_stl/binary_semaphore_inline.h",
+        "public/pw_sync_stl/binary_semaphore_native.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    srcs = [
+        "binary_semaphore.cc",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+        "//pw_chrono:system_clock",
+        "//pw_sync:binary_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_stl/counting_semaphore_inline.h",
+        "public/pw_sync_stl/counting_semaphore_native.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    srcs = [
+        "counting_semaphore.cc",
+    ],
+    deps = [
+        ":counting_semaphore_headers",
+        "//pw_chrono:system_clock",
+        "//pw_sync:counting_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_headers",
+    hdrs = [
+        "public/pw_sync_stl/mutex_inline.h",
+        "public/pw_sync_stl/mutex_native.h",
+        "public_overrides/pw_sync_backend/mutex_inline.h",
+        "public_overrides/pw_sync_backend/mutex_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_headers",
+        "//pw_chrono:system_clock",
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
     name = "spin_lock_headers",
     hdrs = [
         "public/pw_sync_stl/spin_lock_inline.h",
diff --git a/pw_sync_stl/BUILD.gn b/pw_sync_stl/BUILD.gn
index eacd5dd..f3fdebb 100644
--- a/pw_sync_stl/BUILD.gn
+++ b/pw_sync_stl/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/target_types.gni")
+import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 
 config("public_include_path") {
@@ -27,6 +28,79 @@
   visibility = [ ":*" ]
 }
 
+# This target provides the backend for pw::sync::BinarySemaphore.
+pw_source_set("binary_semaphore_backend") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_stl/binary_semaphore_inline.h",
+    "public/pw_sync_stl/binary_semaphore_native.h",
+    "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+    "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+  ]
+  sources = [ "binary_semaphore.cc" ]
+  deps = [
+    "$dir_pw_assert",
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_sync:binary_semaphore.facade",
+  ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::sync::BinarySemaphore backend only works with the " +
+          "STL pw::chrono::SystemClock backend.")
+}
+
+# This target provides the backend for pw::sync::CountingSemaphore.
+pw_source_set("counting_semaphore_backend") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_stl/counting_semaphore_inline.h",
+    "public/pw_sync_stl/counting_semaphore_native.h",
+    "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+    "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+  ]
+  sources = [ "counting_semaphore.cc" ]
+  deps = [
+    "$dir_pw_assert",
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_sync:counting_semaphore.facade",
+  ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::sync::CountingSemaphore backend only works with the " +
+          "STL pw::chrono::SystemClock backend.")
+}
+
+# This target provides the backend for pw::sync::Mutex.
+pw_source_set("mutex_backend") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_stl/mutex_inline.h",
+    "public/pw_sync_stl/mutex_native.h",
+    "public_overrides/pw_sync_backend/mutex_inline.h",
+    "public_overrides/pw_sync_backend/mutex_native.h",
+  ]
+  deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_sync:mutex.facade",
+  ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::sync::Mutex backend only works with the STL " +
+          "pw::chrono::SystemClock backend.")
+}
+
 # This target provides the backend for pw::sync::SpinLock.
 pw_source_set("spin_lock_backend") {
   public_configs = [
diff --git a/pw_sync_stl/binary_semaphore.cc b/pw_sync_stl/binary_semaphore.cc
new file mode 100644
index 0000000..f1f3fb4
--- /dev/null
+++ b/pw_sync_stl/binary_semaphore.cc
@@ -0,0 +1,56 @@
+// 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 "pw_sync/binary_semaphore.h"
+
+#include "pw_assert/assert.h"
+
+using pw::chrono::SystemClock;
+
+namespace pw::sync {
+
+void BinarySemaphore::release() {
+  std::lock_guard lock(native_type_.mutex);
+  PW_DCHECK_UINT_LT(native_type_.count, BinarySemaphore::max());
+  ++native_type_.count;
+  native_type_.condition.notify_one();
+}
+
+void BinarySemaphore::acquire() {
+  std::unique_lock lock(native_type_.mutex);
+  native_type_.condition.wait(lock, [&] { return native_type_.count != 0; });
+  native_type_.count = 0;
+}
+
+bool BinarySemaphore::try_acquire() {
+  std::lock_guard lock(native_type_.mutex);
+  if (native_type_.count != 0) {
+    native_type_.count = 0;
+    return true;
+  }
+  return false;
+}
+
+bool BinarySemaphore::try_acquire_until(
+    SystemClock::time_point until_at_least) {
+  std::unique_lock lock(native_type_.mutex);
+  if (native_type_.condition.wait_until(
+          lock, until_at_least, [&] { return native_type_.count != 0; })) {
+    native_type_.count = 0;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_stl/counting_semaphore.cc b/pw_sync_stl/counting_semaphore.cc
new file mode 100644
index 0000000..781aa77
--- /dev/null
+++ b/pw_sync_stl/counting_semaphore.cc
@@ -0,0 +1,59 @@
+// 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 "pw_sync/counting_semaphore.h"
+
+#include "pw_assert/assert.h"
+
+using pw::chrono::SystemClock;
+
+namespace pw::sync {
+
+void CountingSemaphore::release(ptrdiff_t update) {
+  PW_DCHECK_UINT_GE(update, 0);
+  {
+    std::lock_guard lock(native_type_.mutex);
+    PW_DCHECK_UINT_LE(update, CountingSemaphore::max() - native_type_.count);
+    native_type_.count += update;
+    native_type_.condition.notify_one();
+  }
+}
+
+void CountingSemaphore::acquire() {
+  std::unique_lock lock(native_type_.mutex);
+  native_type_.condition.wait(lock, [&] { return native_type_.count != 0; });
+  --native_type_.count;
+}
+
+bool CountingSemaphore::try_acquire() {
+  std::lock_guard lock(native_type_.mutex);
+  if (native_type_.count != 0) {
+    --native_type_.count;
+    return true;
+  }
+  return false;
+}
+
+bool CountingSemaphore::try_acquire_until(
+    SystemClock::time_point until_at_least) {
+  std::unique_lock lock(native_type_.mutex);
+  if (native_type_.condition.wait_until(
+          lock, until_at_least, [&] { return native_type_.count != 0; })) {
+    --native_type_.count;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h b/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h
new file mode 100644
index 0000000..ca5a1e3
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h
@@ -0,0 +1,41 @@
+// 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.
+#pragma once
+
+#include "pw_chrono/system_clock.h"
+#include "pw_sync/binary_semaphore.h"
+
+namespace pw::sync {
+
+inline BinarySemaphore::BinarySemaphore()
+    : native_type_{.mutex = {}, .condition = {}, .count = 0} {}
+
+inline BinarySemaphore::~BinarySemaphore() {}
+
+inline bool BinarySemaphore::try_acquire_for(
+    chrono::SystemClock::duration for_at_least) {
+  // Due to spurious condition variable wakeups we prefer not to use wait_for()
+  // as we may grossly extend the effective deadline after a spruious wakeup.
+  // Ergo we instead use the derived deadline which can be re-used many times
+  // without shifting the effective deadline. For more details on spurious
+  // wakeups and CVs on Windows and POSIX see:
+  //   https://en.wikipedia.org/wiki/Spurious_wakeup
+  return try_acquire_until(chrono::SystemClock::now() + for_at_least);
+}
+
+inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/binary_semaphore_native.h b/pw_sync_stl/public/pw_sync_stl/binary_semaphore_native.h
new file mode 100644
index 0000000..f19a2cc4
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/binary_semaphore_native.h
@@ -0,0 +1,32 @@
+// 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.
+#pragma once
+
+#include <condition_variable>
+#include <limits>
+#include <mutex>
+
+namespace pw::sync::backend {
+
+struct NativeBinarySemaphore {
+  std::mutex mutex;
+  std::condition_variable_any condition;
+  ptrdiff_t count;
+};
+using NativeBinarySemaphoreHandle = NativeBinarySemaphore&;
+
+inline constexpr ptrdiff_t kBinarySemaphoreMaxValue =
+    std::numeric_limits<ptrdiff_t>::max();
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h b/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h
new file mode 100644
index 0000000..f29df4b
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h
@@ -0,0 +1,39 @@
+// 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.
+#pragma once
+
+#include "pw_chrono/system_clock.h"
+#include "pw_sync/counting_semaphore.h"
+
+using pw::chrono::SystemClock;
+
+namespace pw::sync {
+
+inline CountingSemaphore::CountingSemaphore()
+    : native_type_{.mutex = {}, .condition = {}, .count = 0} {}
+
+inline CountingSemaphore::~CountingSemaphore() {}
+
+inline bool CountingSemaphore::try_acquire_for(
+    chrono::SystemClock::duration for_at_least) {
+  // Due to spurious condition variable wakeups this cannot use wait_for().
+  return try_acquire_until(chrono::SystemClock::now() + for_at_least);
+}
+
+inline CountingSemaphore::native_handle_type
+CountingSemaphore::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/counting_semaphore_native.h b/pw_sync_stl/public/pw_sync_stl/counting_semaphore_native.h
new file mode 100644
index 0000000..8b06307
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/counting_semaphore_native.h
@@ -0,0 +1,32 @@
+// 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.
+#pragma once
+
+#include <condition_variable>
+#include <limits>
+#include <mutex>
+
+namespace pw::sync::backend {
+
+struct NativeCountingSemaphore {
+  std::mutex mutex;
+  std::condition_variable_any condition;
+  ptrdiff_t count;
+};
+using NativeCountingSemaphoreHandle = NativeCountingSemaphore&;
+
+inline constexpr ptrdiff_t kCountingSemaphoreMaxValue =
+    std::numeric_limits<ptrdiff_t>::max();
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_stl/public/pw_sync_stl/mutex_inline.h b/pw_sync_stl/public/pw_sync_stl/mutex_inline.h
new file mode 100644
index 0000000..b4459eb
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/mutex_inline.h
@@ -0,0 +1,42 @@
+// 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.
+#pragma once
+
+#include "pw_chrono/system_clock.h"
+#include "pw_sync/mutex.h"
+
+namespace pw::sync {
+
+inline Mutex::Mutex() : native_type_() {}
+
+inline Mutex::~Mutex() {}
+
+inline void Mutex::lock() { native_type_.lock(); }
+
+inline bool Mutex::try_lock() { return native_type_.try_lock(); }
+
+inline bool Mutex::try_lock_for(chrono::SystemClock::duration for_at_least) {
+  return native_type_.try_lock_for(for_at_least);
+}
+
+inline bool Mutex::try_lock_until(
+    chrono::SystemClock::time_point until_at_least) {
+  return native_type_.try_lock_until(until_at_least);
+}
+
+inline void Mutex::unlock() { native_type_.unlock(); }
+
+inline Mutex::native_handle_type Mutex::native_handle() { return native_type_; }
+
+}  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/mutex_native.h b/pw_sync_stl/public/pw_sync_stl/mutex_native.h
new file mode 100644
index 0000000..fb04d56
--- /dev/null
+++ b/pw_sync_stl/public/pw_sync_stl/mutex_native.h
@@ -0,0 +1,23 @@
+// 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.
+#pragma once
+
+#include <mutex>
+
+namespace pw::sync::backend {
+
+using NativeMutex = std::timed_mutex;
+using NativeMutexHandle = std::timed_mutex&;
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/binary_semaphore_inline.h b/pw_sync_stl/public_overrides/pw_sync_backend/binary_semaphore_inline.h
new file mode 100644
index 0000000..96d102c
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/binary_semaphore_inline.h
@@ -0,0 +1,16 @@
+// 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.
+#pragma once
+
+#include "pw_sync_stl/binary_semaphore_inline.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/binary_semaphore_native.h b/pw_sync_stl/public_overrides/pw_sync_backend/binary_semaphore_native.h
new file mode 100644
index 0000000..f855f73
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/binary_semaphore_native.h
@@ -0,0 +1,16 @@
+// 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.
+#pragma once
+
+#include "pw_sync_stl/binary_semaphore_native.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/counting_semaphore_inline.h b/pw_sync_stl/public_overrides/pw_sync_backend/counting_semaphore_inline.h
new file mode 100644
index 0000000..99ad224
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/counting_semaphore_inline.h
@@ -0,0 +1,16 @@
+// 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.
+#pragma once
+
+#include "pw_sync_stl/counting_semaphore_inline.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/counting_semaphore_native.h b/pw_sync_stl/public_overrides/pw_sync_backend/counting_semaphore_native.h
new file mode 100644
index 0000000..041856b
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/counting_semaphore_native.h
@@ -0,0 +1,16 @@
+// 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.
+#pragma once
+
+#include "pw_sync_stl/counting_semaphore_native.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/mutex_inline.h b/pw_sync_stl/public_overrides/pw_sync_backend/mutex_inline.h
new file mode 100644
index 0000000..5c48c56
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/mutex_inline.h
@@ -0,0 +1,16 @@
+// 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.
+#pragma once
+
+#include "pw_sync_stl/mutex_inline.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/mutex_native.h b/pw_sync_stl/public_overrides/pw_sync_backend/mutex_native.h
new file mode 100644
index 0000000..d37bd48
--- /dev/null
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/mutex_native.h
@@ -0,0 +1,16 @@
+// 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.
+#pragma once
+
+#include "pw_sync_stl/mutex_native.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_inline.h b/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_inline.h
index 0307f88..57c17c2 100644
--- a/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_inline.h
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_inline.h
@@ -11,9 +11,6 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
 #pragma once
 
 #include "pw_sync_stl/spin_lock_inline.h"
diff --git a/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_native.h b/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_native.h
index 9d1922b..41ff621 100644
--- a/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_native.h
+++ b/pw_sync_stl/public_overrides/pw_sync_backend/spin_lock_native.h
@@ -11,9 +11,6 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
 #pragma once
 
 #include "pw_sync_stl/spin_lock_native.h"
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index c757915..cd89d8f 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -69,11 +69,22 @@
   pw_unit_test_AUTOMATIC_RUNNER = get_path_info("run_test.bat", "abspath")
 }
 
+# TODO(amontanez): figure out why std::mutex doesn't work on Windows.
+# These current target configurations do not work on windows.
+_win_incompatible_config = {
+  pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_stl:binary_semaphore_backend"
+  pw_sync_COUNTING_SEMAPHORE_BACKEND =
+      "$dir_pw_sync_stl:counting_semaphore_backend"
+  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
+}
+
 _os_specific_config = {
   if (host_os == "linux") {
     forward_variables_from(_linux_config, "*")
+    forward_variables_from(_win_incompatible_config, "*")
   } else if (host_os == "mac") {
     forward_variables_from(_mac_config, "*")
+    forward_variables_from(_win_incompatible_config, "*")
   } else if (host_os == "win") {
     forward_variables_from(_win_config, "*")
   }