pw_chrono_embos: adds SystemTimer backend

Adds a backend for pw::chrono::SystemTimer based on embOS's
software timer API.

Change-Id: I0d03c2b2b6474bcefb968a20897748f552ca1a7e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/50860
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_chrono_embos/BUILD b/pw_chrono_embos/BUILD
index 9248d51..b86d63d 100644
--- a/pw_chrono_embos/BUILD
+++ b/pw_chrono_embos/BUILD
@@ -54,3 +54,40 @@
         "//pw_chrono:system_clock_facade",
     ],
 )
+
+pw_cc_library(
+    name = "system_timer_headers",
+    hdrs = [
+        "public/pw_chrono_embos/system_timer_inline.h",
+        "public/pw_chrono_embos/system_timer_native.h",
+        "public_overrides/pw_chrono_backend/system_timer_inline.h",
+        "public_overrides/pw_chrono_backend/system_timer_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+        "//pw_function",
+        "//pw_chrono:system_timer_facade",
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+    ],
+)
+
+pw_cc_library(
+    name = "system_timer",
+    srcs = [
+        "system_timer.cc",
+    ],
+    deps = [
+        ":system_timer_headers",
+        "//pw_chrono:system_timer_facade",
+        ":system_clock",
+        "//pw_assert",
+        "//pw_interrupt:context",
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+    ],
+)
diff --git a/pw_chrono_embos/BUILD.gn b/pw_chrono_embos/BUILD.gn
index 13a8a0c..b4ad97c 100644
--- a/pw_chrono_embos/BUILD.gn
+++ b/pw_chrono_embos/BUILD.gn
@@ -68,6 +68,33 @@
   ]
 }
 
+# This target provides the backend for pw::chrono::SystemTimer.
+pw_source_set("system_timer") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_chrono_embos/system_timer_inline.h",
+    "public/pw_chrono_embos/system_timer_native.h",
+    "public_overrides/pw_chrono_backend/system_timer_inline.h",
+    "public_overrides/pw_chrono_backend/system_timer_native.h",
+  ]
+  public_deps = [
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_chrono:system_timer.facade",
+    "$dir_pw_function",
+    "$dir_pw_third_party/embos",
+  ]
+  deps = [
+    "$dir_pw_assert",
+    "$dir_pw_chrono_embos:system_clock",
+    "$dir_pw_interrupt:context",
+  ]
+  allow_circular_includes_from = [ "$dir_pw_chrono:system_timer.facade" ]
+  sources = [ "system_timer.cc" ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono_embos/docs.rst b/pw_chrono_embos/docs.rst
index c6be81b..4e45946 100644
--- a/pw_chrono_embos/docs.rst
+++ b/pw_chrono_embos/docs.rst
@@ -7,8 +7,7 @@
 implemented using embOS v4 for 32bit targets.
 
 .. warning::
-  This module is under construction, not ready for use, and the documentation
-  is incomplete.
+  This module is still under construction, the API is not yet stable.
 
 SystemClock backend
 -------------------
@@ -23,6 +22,13 @@
 native embOS ``OS_GetTime32()`` overflow. Note that this duration may
 vary if ``OS_SUPPORT_TICKLESS`` is used.
 
+SystemTimer backend
+-------------------
+The embOS based ``system_timer`` backend implements the
+``pw_chrono:system_timer`` facade by using embOS's software timer API.
+``pw::chrono::SystemTimer`` instances use ``OS_TIMER_EX`` &
+``OS_CreateTimerEx``, dynamic memory allocation is never used.
+
 Build targets
 -------------
 The GN build for ``pw_chrono_embos`` has one target: ``system_clock``.
diff --git a/pw_chrono_embos/public/pw_chrono_embos/system_timer_inline.h b/pw_chrono_embos/public/pw_chrono_embos/system_timer_inline.h
new file mode 100644
index 0000000..98a40b6
--- /dev/null
+++ b/pw_chrono_embos/public/pw_chrono_embos/system_timer_inline.h
@@ -0,0 +1,28 @@
+// 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_chrono/system_clock.h"
+#include "pw_chrono/system_timer.h"
+
+namespace pw::chrono {
+
+inline void SystemTimer::InvokeAfter(SystemClock::duration delay) {
+  InvokeAt(SystemClock::TimePointAfterAtLeast(delay));
+}
+
+inline void SystemTimer::Cancel() { OS_StopTimerEx(&native_type_.tcb); }
+
+}  // namespace pw::chrono
diff --git a/pw_chrono_embos/public/pw_chrono_embos/system_timer_native.h b/pw_chrono_embos/public/pw_chrono_embos/system_timer_native.h
new file mode 100644
index 0000000..423bb24
--- /dev/null
+++ b/pw_chrono_embos/public/pw_chrono_embos/system_timer_native.h
@@ -0,0 +1,29 @@
+// 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_chrono/system_clock.h"
+#include "pw_function/function.h"
+
+namespace pw::chrono::backend {
+
+struct NativeSystemTimer {
+  OS_TIMER_EX tcb;
+  SystemClock::time_point expiry_deadline;
+  Function<void(SystemClock::time_point expired_deadline)> user_callback;
+};
+using NativeSystemTimerHandle = NativeSystemTimer&;
+
+}  // namespace pw::chrono::backend
diff --git a/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_inline.h b/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_inline.h
new file mode 100644
index 0000000..aeb649f
--- /dev/null
+++ b/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_inline.h
@@ -0,0 +1,19 @@
+// 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.
+
+// This override header includes the main tokenized logging header and defines
+// the PW_LOG macro as the tokenized logging macro.
+#pragma once
+
+#include "pw_chrono_embos/system_timer_inline.h"
diff --git a/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_native.h b/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_native.h
new file mode 100644
index 0000000..13fb7c6
--- /dev/null
+++ b/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_native.h
@@ -0,0 +1,19 @@
+// 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.
+
+// This override header includes the main tokenized logging header and defines
+// the PW_LOG macro as the tokenized logging macro.
+#pragma once
+
+#include "pw_chrono_embos/system_timer_native.h"
diff --git a/pw_chrono_embos/system_timer.cc b/pw_chrono_embos/system_timer.cc
new file mode 100644
index 0000000..3f41674
--- /dev/null
+++ b/pw_chrono_embos/system_timer.cc
@@ -0,0 +1,113 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_chrono/system_timer.h"
+
+#include <mutex>
+
+#include "RTOS.h"
+#include "pw_assert/check.h"
+#include "pw_chrono_embos/system_clock_constants.h"
+#include "pw_interrupt/context.h"
+
+namespace pw::chrono {
+namespace {
+
+// Instead of adding targeted locks to each instance, simply use the global
+// recursive critical section lock. Note it has to be recursive because a timer
+// cannot be started with a time period of 0 and ergo the Invoke* API might
+// have to directly invoke the callback.
+class RecursiveCriticalSectionLock {
+ public:
+  void lock() {
+    OS_IncDI();            // Mask interrupts.
+    OS_SuspendAllTasks();  // Disable task switching.
+  }
+
+  void unlock() {
+    OS_ResumeAllSuspendedTasks();  // Restore task switching.
+    OS_DecRI();                    // Restore interrupts.
+  }
+};
+RecursiveCriticalSectionLock recursive_global_timer_lock;
+
+void HandleTimerCallback(void* void_native_system_timer) {
+  // NOTE: embOS invokes all timer callbacks from a single interrupt. Ergo we do
+  // not add a unnecessary grab of the recursive_global_timer_lock here.
+  PW_DCHECK(interrupt::InInterruptContext(),
+            "HandleTimerCallback must be invoked from an interrupt");
+  // TODO(ewout): can we potentially skip this as it's always in an interrupt
+  // handler and the control API is not interrupt safe?
+  std::lock_guard lock(recursive_global_timer_lock);
+
+  backend::NativeSystemTimer& native_type =
+      *static_cast<backend::NativeSystemTimer*>(void_native_system_timer);
+  const SystemClock::duration time_until_deadline =
+      native_type.expiry_deadline - SystemClock::now();
+  if (time_until_deadline <= SystemClock::duration::zero()) {
+    // We have met the deadline, execute the user's callback.
+    native_type.user_callback(native_type.expiry_deadline);
+    return;
+  }
+  const SystemClock::duration period =
+      std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
+  OS_SetTimerPeriodEx(&native_type.tcb, static_cast<OS_TIME>(period.count()));
+  OS_StartTimerEx(&native_type.tcb);
+}
+
+constexpr OS_TIME kInvalidPeriod = 0;
+
+}  // namespace
+
+SystemTimer::SystemTimer(ExpiryCallback callback)
+    : native_type_{.tcb{},
+                   .expiry_deadline = SystemClock::time_point(),
+                   .user_callback = std::move(callback)} {
+  OS_CreateTimerEx(
+      &native_type_.tcb, HandleTimerCallback, kInvalidPeriod, &native_type_);
+}
+
+SystemTimer::~SystemTimer() {
+  // Not threadsafe by design.
+  Cancel();
+  OS_DeleteTimerEx(&native_type_.tcb);
+}
+
+void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
+  std::lock_guard lock(recursive_global_timer_lock);
+
+  // Ensure the timer has been cancelled first.
+  Cancel();
+
+  native_type_.expiry_deadline = timestamp;
+  const SystemClock::duration time_until_deadline =
+      timestamp - SystemClock::now();
+
+  // Timers can only be created with a non-zero period, ergo we must immediately
+  // invoke the user's callback if it cannot be deferred by at least a partial
+  // tick.
+  if (time_until_deadline <= SystemClock::duration::zero()) {
+    native_type_.user_callback(timestamp);
+    return;
+  }
+
+  // Schedule the timer as far out as possible. Note that the timeout might be
+  // clamped and it may be rescheduled internally.
+  const SystemClock::duration period =
+      std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
+  OS_SetTimerPeriodEx(&native_type_.tcb, static_cast<OS_TIME>(period.count()));
+  OS_RetriggerTimerEx(&native_type_.tcb);
+}
+
+}  // namespace pw::chrono