pw_sync_embos: add Semaphore support

Adds BinarySemaphore and CountingSemaphore support for embOS v4.

Change-Id: Iae3bbc9f6e9fe977e7906a44e1b14ecdfa5f5e0a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/35883
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_sync_embos/BUILD b/pw_sync_embos/BUILD
index 56777c9..98e4739 100644
--- a/pw_sync_embos/BUILD
+++ b/pw_sync_embos/BUILD
@@ -22,6 +22,71 @@
 licenses(["notice"])  # Apache License 2.0
 
 pw_cc_library(
+    name = "binary_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_embos/binary_semaphore_inline.h",
+        "public/pw_sync_embos/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 = [
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_embos:system_clock_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    srcs = [
+        "binary_semaphore.cc",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:binary_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_embos/counting_semaphore_inline.h",
+        "public/pw_sync_embos/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 = [
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_embos:system_clock_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    srcs = [
+        "counting_semaphore.cc",
+    ],
+    deps = [
+        ":counting_semaphore_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:counting_semaphore_facade",
+    ],
+)
+
+
+pw_cc_library(
     name = "mutex_headers",
     hdrs = [
         "public/pw_sync_embos/mutex_inline.h",
diff --git a/pw_sync_embos/BUILD.gn b/pw_sync_embos/BUILD.gn
index 4fa9b3d..abc95d7 100644
--- a/pw_sync_embos/BUILD.gn
+++ b/pw_sync_embos/BUILD.gn
@@ -28,6 +28,62 @@
   visibility = [ ":*" ]
 }
 
+# This target provides the backend for pw::sync::BinarySemaphore.
+pw_source_set("binary_semaphore") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_embos/binary_semaphore_inline.h",
+    "public/pw_sync_embos/binary_semaphore_native.h",
+    "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+    "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+  ]
+  public_deps = [
+    "$dir_pw_assert",
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_chrono_embos:system_clock",
+    "$dir_pw_interrupt:context",
+    "$dir_pw_third_party/embos",
+  ]
+  sources = [ "binary_semaphore.cc" ]
+  deps = [ "$dir_pw_sync:binary_semaphore.facade" ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock",
+      "The embOS pw::sync::BinarySemaphore backend only works with the " +
+          "embOS pw::chrono::SystemClock backend.")
+}
+
+# This target provides the backend for pw::sync::CountingSemaphore.
+pw_source_set("counting_semaphore") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_embos/counting_semaphore_inline.h",
+    "public/pw_sync_embos/counting_semaphore_native.h",
+    "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+    "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+  ]
+  public_deps = [
+    "$dir_pw_assert",
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_chrono_embos:system_clock",
+    "$dir_pw_interrupt:context",
+    "$dir_pw_third_party/embos",
+  ]
+  sources = [ "counting_semaphore.cc" ]
+  deps = [ "$dir_pw_sync:counting_semaphore.facade" ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock",
+      "The embOS pw::sync::CountingSemaphore backend only works with " +
+          "the embOS pw::chrono::SystemClock backend.")
+}
+
 # This target provides the backend for pw::sync::Mutex.
 pw_source_set("mutex") {
   public_configs = [
diff --git a/pw_sync_embos/binary_semaphore.cc b/pw_sync_embos/binary_semaphore.cc
new file mode 100644
index 0000000..379dbd4
--- /dev/null
+++ b/pw_sync_embos/binary_semaphore.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_sync/binary_semaphore.h"
+
+#include <algorithm>
+
+#include "RTOS.h"
+#include "pw_assert/assert.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_chrono_embos/system_clock_constants.h"
+#include "pw_interrupt/context.h"
+
+using pw::chrono::SystemClock;
+using pw::chrono::embos::kMaxTimeout;
+
+namespace pw::sync {
+
+bool BinarySemaphore::try_acquire_for(SystemClock::duration for_at_least) {
+  PW_DCHECK(!interrupt::InInterruptContext());
+
+  // Clamp negative durations to be 0 which maps to non-blocking.
+  for_at_least = std::max(for_at_least, SystemClock::duration::zero());
+
+  while (for_at_least > kMaxTimeout) {
+    if (OS_WaitCSemaTimed(&native_type_, kMaxTimeout.count())) {
+      return true;
+    }
+    for_at_least -= kMaxTimeout;
+  }
+  return OS_WaitCSemaTimed(&native_type_, for_at_least.count());
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_embos/counting_semaphore.cc b/pw_sync_embos/counting_semaphore.cc
new file mode 100644
index 0000000..e46a36b
--- /dev/null
+++ b/pw_sync_embos/counting_semaphore.cc
@@ -0,0 +1,59 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_sync/counting_semaphore.h"
+
+#include <algorithm>
+
+#include "RTOS.h"
+#include "pw_assert/assert.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_chrono_embos/system_clock_constants.h"
+#include "pw_interrupt/context.h"
+
+using pw::chrono::SystemClock;
+using pw::chrono::embos::kMaxTimeout;
+
+namespace pw::sync {
+
+void CountingSemaphore::release(ptrdiff_t update) {
+  for (; update > 0; --update) {
+    // There is no API to atomically detect overflow, however debug builds of
+    // embOS call OS_Error() internally when overflow is detected for the native
+    // token representation. Rather than enter a critical section both due to
+    // cost and potential direct use of the native handle, a lazy check is used
+    // for debug builds which may not trigger on the initial overflow.
+    PW_DCHECK_UINT_LE(OS_GetCSemaValue(&native_type_),
+                      CountingSemaphore::max(),
+                      "Overflowed counting semaphore.");
+    OS_SignalCSema(&native_type_);
+  }
+}
+
+bool CountingSemaphore::try_acquire_for(SystemClock::duration for_at_least) {
+  PW_DCHECK(!interrupt::InInterruptContext());
+
+  // Clamp negative durations to be 0 which maps to non-blocking.
+  for_at_least = std::max(for_at_least, SystemClock::duration::zero());
+
+  while (for_at_least > kMaxTimeout) {
+    if (OS_WaitCSemaTimed(&native_type_, kMaxTimeout.count())) {
+      return true;
+    }
+    for_at_least -= kMaxTimeout;
+  }
+  return OS_WaitCSemaTimed(&native_type_, for_at_least.count());
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h b/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h
new file mode 100644
index 0000000..3996851
--- /dev/null
+++ b/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "RTOS.h"
+#include "pw_assert/light.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_chrono_embos/system_clock_constants.h"
+#include "pw_interrupt/context.h"
+#include "pw_sync/binary_semaphore.h"
+
+namespace pw::sync {
+
+inline BinarySemaphore::BinarySemaphore() : native_type_() {
+  OS_CreateCSema(&native_type_, 0);
+}
+
+inline BinarySemaphore::~BinarySemaphore() { OS_DeleteCSema(&native_type_); }
+
+inline void BinarySemaphore::release() { OS_SignalCSemaMax(&native_type_, 1); }
+
+inline void BinarySemaphore::acquire() {
+  PW_ASSERT(!interrupt::InInterruptContext());
+  OS_WaitCSema(&native_type_);
+}
+
+inline bool BinarySemaphore::try_acquire() noexcept {
+  return OS_CSemaRequest(&native_type_) != 0;
+}
+
+inline bool BinarySemaphore::try_acquire_until(
+    chrono::SystemClock::time_point until_at_least) {
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
+}
+
+inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/binary_semaphore_native.h b/pw_sync_embos/public/pw_sync_embos/binary_semaphore_native.h
new file mode 100644
index 0000000..c92afa9
--- /dev/null
+++ b/pw_sync_embos/public/pw_sync_embos/binary_semaphore_native.h
@@ -0,0 +1,30 @@
+// 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 <limits>
+
+#include "RTOS.h"
+
+namespace pw::sync::backend {
+
+using NativeBinarySemaphore = OS_CSEMA;
+using NativeBinarySemaphoreHandle = NativeBinarySemaphore&;
+
+inline constexpr ptrdiff_t kBinarySemaphoreMaxValue =
+    std::numeric_limits<ptrdiff_t>::max() < std::numeric_limits<OS_UINT>::max()
+        ? std::numeric_limits<ptrdiff_t>::max()
+        : std::numeric_limits<OS_UINT>::max();
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h b/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h
new file mode 100644
index 0000000..d0b1a8c
--- /dev/null
+++ b/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h
@@ -0,0 +1,52 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "RTOS.h"
+#include "pw_assert/light.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_chrono_embos/system_clock_constants.h"
+#include "pw_interrupt/context.h"
+#include "pw_sync/counting_semaphore.h"
+
+namespace pw::sync {
+
+inline CountingSemaphore::CountingSemaphore() : native_type_() {
+  OS_CreateCSema(&native_type_, 0);
+}
+
+inline CountingSemaphore::~CountingSemaphore() {
+  OS_DeleteCSema(&native_type_);
+}
+
+inline void CountingSemaphore::acquire() {
+  PW_ASSERT(!interrupt::InInterruptContext());
+  OS_WaitCSema(&native_type_);
+}
+
+inline bool CountingSemaphore::try_acquire() noexcept {
+  return OS_CSemaRequest(&native_type_) != 0;
+}
+
+inline bool CountingSemaphore::try_acquire_until(
+    chrono::SystemClock::time_point until_at_least) {
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
+}
+
+inline CountingSemaphore::native_handle_type
+CountingSemaphore::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/counting_semaphore_native.h b/pw_sync_embos/public/pw_sync_embos/counting_semaphore_native.h
new file mode 100644
index 0000000..56959b6
--- /dev/null
+++ b/pw_sync_embos/public/pw_sync_embos/counting_semaphore_native.h
@@ -0,0 +1,30 @@
+// 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 <limits>
+
+#include "RTOS.h"
+
+namespace pw::sync::backend {
+
+using NativeCountingSemaphore = OS_CSEMA;
+using NativeCountingSemaphoreHandle = NativeCountingSemaphore&;
+
+inline constexpr ptrdiff_t kCountingSemaphoreMaxValue =
+    std::numeric_limits<ptrdiff_t>::max() < std::numeric_limits<OS_UINT>::max()
+        ? std::numeric_limits<ptrdiff_t>::max()
+        : std::numeric_limits<OS_UINT>::max();
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_embos/public_overrides/pw_sync_backend/binary_semaphore_inline.h b/pw_sync_embos/public_overrides/pw_sync_backend/binary_semaphore_inline.h
new file mode 100644
index 0000000..0179e82
--- /dev/null
+++ b/pw_sync_embos/public_overrides/pw_sync_backend/binary_semaphore_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_embos/binary_semaphore_inline.h"
diff --git a/pw_sync_embos/public_overrides/pw_sync_backend/binary_semaphore_native.h b/pw_sync_embos/public_overrides/pw_sync_backend/binary_semaphore_native.h
new file mode 100644
index 0000000..e056b63
--- /dev/null
+++ b/pw_sync_embos/public_overrides/pw_sync_backend/binary_semaphore_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_embos/binary_semaphore_native.h"
diff --git a/pw_sync_embos/public_overrides/pw_sync_backend/counting_semaphore_inline.h b/pw_sync_embos/public_overrides/pw_sync_backend/counting_semaphore_inline.h
new file mode 100644
index 0000000..aa4ffa6
--- /dev/null
+++ b/pw_sync_embos/public_overrides/pw_sync_backend/counting_semaphore_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_embos/counting_semaphore_inline.h"
diff --git a/pw_sync_embos/public_overrides/pw_sync_backend/counting_semaphore_native.h b/pw_sync_embos/public_overrides/pw_sync_backend/counting_semaphore_native.h
new file mode 100644
index 0000000..9b98e24
--- /dev/null
+++ b/pw_sync_embos/public_overrides/pw_sync_backend/counting_semaphore_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_embos/counting_semaphore_native.h"