embos: add Mutex support

Adds Mutex support for embOS v4.

Change-Id: Id5397095f0e5252486aeb48e1af7d9c5ef507036
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/35720
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 88e824e..56777c9 100644
--- a/pw_sync_embos/BUILD
+++ b/pw_sync_embos/BUILD
@@ -22,6 +22,38 @@
 licenses(["notice"])  # Apache License 2.0
 
 pw_cc_library(
+    name = "mutex_headers",
+    hdrs = [
+        "public/pw_sync_embos/mutex_inline.h",
+        "public/pw_sync_embos/mutex_native.h",
+        "public_overrides/pw_sync_backend/mutex_inline.h",
+        "public_overrides/pw_sync_backend/mutex_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 = "mutex",
+    srcs = [
+        "mutex.cc",
+    ],
+    deps = [
+        ":mutex_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
     name = "spin_lock_headers",
     hdrs = [
         "public/pw_sync_embos/spin_lock_inline.h",
diff --git a/pw_sync_embos/BUILD.gn b/pw_sync_embos/BUILD.gn
index cbb3d02..4fa9b3d 100644
--- a/pw_sync_embos/BUILD.gn
+++ b/pw_sync_embos/BUILD.gn
@@ -28,6 +28,34 @@
   visibility = [ ":*" ]
 }
 
+# This target provides the backend for pw::sync::Mutex.
+pw_source_set("mutex") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_embos/mutex_inline.h",
+    "public/pw_sync_embos/mutex_native.h",
+    "public_overrides/pw_sync_backend/mutex_inline.h",
+    "public_overrides/pw_sync_backend/mutex_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 = [ "mutex.cc" ]
+  deps = [ "$dir_pw_sync:mutex.facade" ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock",
+      "The embOS pw::sync::Mutex backend only works with the embOS " +
+          "pw::chrono::SystemClock backend.")
+}
+
 # This target provides the backend for pw::sync::SpinLock.
 pw_source_set("spin_lock") {
   public_configs = [
diff --git a/pw_sync_embos/mutex.cc b/pw_sync_embos/mutex.cc
new file mode 100644
index 0000000..8686039
--- /dev/null
+++ b/pw_sync_embos/mutex.cc
@@ -0,0 +1,49 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_sync/mutex.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 Mutex::try_lock_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) {
+    const int lock_count = OS_UseTimed(&native_type_, kMaxTimeout.count());
+    if (lock_count != 0) {
+      PW_CHECK_UINT_EQ(1, lock_count, "Recursive locking is not permitted");
+      return true;
+    }
+    for_at_least -= kMaxTimeout;
+  }
+  const int lock_count = OS_UseTimed(&native_type_, for_at_least.count());
+  PW_CHECK_UINT_LE(1, lock_count, "Recursive locking is not permitted");
+  return lock_count == 1;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/mutex_inline.h b/pw_sync_embos/public/pw_sync_embos/mutex_inline.h
new file mode 100644
index 0000000..a8d6862
--- /dev/null
+++ b/pw_sync_embos/public/pw_sync_embos/mutex_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/mutex.h"
+
+namespace pw::sync {
+
+inline Mutex::Mutex() : native_type_() { OS_CreateRSema(&native_type_); }
+
+inline Mutex::~Mutex() { OS_DeleteRSema(&native_type_); }
+
+inline void Mutex::lock() {
+  PW_ASSERT(!interrupt::InInterruptContext());
+  const int lock_count = OS_Use(&native_type_);
+  PW_ASSERT(lock_count == 1);  // Recursive locking is not permitted.
+}
+
+inline bool Mutex::try_lock() {
+  PW_ASSERT(!interrupt::InInterruptContext());
+  return OS_Request(&native_type_) != 0;
+}
+
+inline bool Mutex::try_lock_until(
+    chrono::SystemClock::time_point until_at_least) {
+  return try_lock_for(until_at_least - chrono::SystemClock::now());
+}
+
+inline void Mutex::unlock() {
+  PW_ASSERT(!interrupt::InInterruptContext());
+  OS_Unuse(&native_type_);
+}
+
+inline Mutex::native_handle_type Mutex::native_handle() { return native_type_; }
+
+}  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/mutex_native.h b/pw_sync_embos/public/pw_sync_embos/mutex_native.h
new file mode 100644
index 0000000..cf6846e
--- /dev/null
+++ b/pw_sync_embos/public/pw_sync_embos/mutex_native.h
@@ -0,0 +1,23 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "RTOS.h"
+
+namespace pw::sync::backend {
+
+using NativeMutex = OS_RSEMA;
+using NativeMutexHandle = NativeMutex&;
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_embos/public_overrides/pw_sync_backend/mutex_inline.h b/pw_sync_embos/public_overrides/pw_sync_backend/mutex_inline.h
new file mode 100644
index 0000000..a75f3b2
--- /dev/null
+++ b/pw_sync_embos/public_overrides/pw_sync_backend/mutex_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/mutex_inline.h"
diff --git a/pw_sync_embos/public_overrides/pw_sync_backend/mutex_native.h b/pw_sync_embos/public_overrides/pw_sync_backend/mutex_native.h
new file mode 100644
index 0000000..2710b26
--- /dev/null
+++ b/pw_sync_embos/public_overrides/pw_sync_backend/mutex_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/mutex_native.h"