pw_sync_threadx: Add pw::sync::SpinLock backend

Adds initial ThreadX support through a SpinLock backend for
pw_sync. Note that this first backend does not support ThreadX
with SMP enabled, taking a shortcut to detect SpinLock recursion
instead.

Change-Id: I6cb4d1edfe78bc97fc632cfb353589e53f87f2c7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/25803
Commit-Queue: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 6f38d3c..4ad9ea4 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -96,6 +96,7 @@
     "$dir_pw_string:docs",
     "$dir_pw_sync:docs",
     "$dir_pw_sync_stl:docs",
+    "$dir_pw_sync_threadx:docs",
     "$dir_pw_sys_io:docs",
     "$dir_pw_sys_io_arduino:docs",
     "$dir_pw_sys_io_baremetal_stm32f429:docs",
diff --git a/modules.gni b/modules.gni
index 2576da3..b079b63 100644
--- a/modules.gni
+++ b/modules.gni
@@ -68,6 +68,7 @@
   dir_pw_string = get_path_info("pw_string", "abspath")
   dir_pw_sync = get_path_info("pw_sync", "abspath")
   dir_pw_sync_stl = get_path_info("pw_sync_stl", "abspath")
+  dir_pw_sync_threadx = get_path_info("pw_sync_threadx", "abspath")
   dir_pw_sys_io = get_path_info("pw_sys_io", "abspath")
   dir_pw_sys_io_baremetal_lm3s6965evb =
       get_path_info("pw_sys_io_baremetal_lm3s6965evb", "abspath")
diff --git a/pw_sync_threadx/BUILD b/pw_sync_threadx/BUILD
new file mode 100644
index 0000000..54dff01
--- /dev/null
+++ b/pw_sync_threadx/BUILD
@@ -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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "spin_lock_headers",
+    hdrs = [
+        "public/pw_sync_threadx/spin_lock_inline.h",
+        "public/pw_sync_threadx/spin_lock_native.h",
+        "public_overrides/pw_sync_backend/spin_lock_inline.h",
+        "public_overrides/pw_sync_backend/spin_lock_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO: This should depend on ThreadX but our third parties currently
+    # do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "spin_lock",
+    srcs = [
+        "spin_lock.cc",
+    ],
+    deps = [
+        ":spin_lock_headers",
+        "//pw_sync:spin_lock_facade",
+    ],
+)
diff --git a/pw_sync_threadx/BUILD.gn b/pw_sync_threadx/BUILD.gn
new file mode 100644
index 0000000..7f80bd5
--- /dev/null
+++ b/pw_sync_threadx/BUILD.gn
@@ -0,0 +1,53 @@
+# 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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+
+config("public_include_path") {
+  include_dirs = [ "public" ]
+  visibility = [ ":*" ]
+}
+
+config("backend_config") {
+  include_dirs = [ "public_overrides" ]
+  visibility = [ ":*" ]
+}
+
+# This target provides the backend for pw::sync::SpinLock, note that this
+# implementation does NOT support ThreadX w/ SMP.
+pw_source_set("spin_lock") {
+  public_configs = [
+    ":public_include_path",
+    ":backend_config",
+  ]
+  public = [
+    "public/pw_sync_threadx/spin_lock_inline.h",
+    "public/pw_sync_threadx/spin_lock_native.h",
+    "public_overrides/pw_sync_backend/spin_lock_inline.h",
+    "public_overrides/pw_sync_backend/spin_lock_native.h",
+  ]
+  public_deps = [ "$dir_pw_third_party/threadx" ]
+  sources = [ "spin_lock.cc" ]
+  deps = [
+    "$dir_pw_assert",
+    "$dir_pw_sync:spin_lock.facade",
+  ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_sync_threadx/docs.rst b/pw_sync_threadx/docs.rst
new file mode 100644
index 0000000..6699742
--- /dev/null
+++ b/pw_sync_threadx/docs.rst
@@ -0,0 +1,8 @@
+.. _module-pw_sync_threadx:
+
+---------------
+pw_sync_threadx
+---------------
+This is a set of backends for pw_sync based on the ThreadX RTOS. It is not ready
+for use, and is under construction.
+
diff --git a/pw_sync_threadx/public/pw_sync_threadx/spin_lock_inline.h b/pw_sync_threadx/public/pw_sync_threadx/spin_lock_inline.h
new file mode 100644
index 0000000..67be0dc
--- /dev/null
+++ b/pw_sync_threadx/public/pw_sync_threadx/spin_lock_inline.h
@@ -0,0 +1,28 @@
+// 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/spin_lock.h"
+#include "pw_sync/yield_core.h"
+
+namespace pw::sync {
+
+inline SpinLock::SpinLock()
+    : native_type_{.locked = false, .saved_interrupt_mask = 0} {}
+
+inline SpinLock::native_handle_type SpinLock::native_handle() {
+  return native_type_;
+}
+
+}  // namespace pw::sync
diff --git a/pw_sync_threadx/public/pw_sync_threadx/spin_lock_native.h b/pw_sync_threadx/public/pw_sync_threadx/spin_lock_native.h
new file mode 100644
index 0000000..7756abc
--- /dev/null
+++ b/pw_sync_threadx/public/pw_sync_threadx/spin_lock_native.h
@@ -0,0 +1,28 @@
+// 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 <atomic>
+
+#include "tx_api.h"
+
+namespace pw::sync::backend {
+
+struct NativeSpinLock {
+  std::atomic<bool> locked;  // Used to detect recursion.
+  UINT saved_interrupt_mask;
+};
+using NativeSpinLockHandle = NativeSpinLock&;
+
+}  // namespace pw::sync::backend
diff --git a/pw_sync_threadx/public_overrides/pw_sync_backend/spin_lock_inline.h b/pw_sync_threadx/public_overrides/pw_sync_backend/spin_lock_inline.h
new file mode 100644
index 0000000..6040c43
--- /dev/null
+++ b/pw_sync_threadx/public_overrides/pw_sync_backend/spin_lock_inline.h
@@ -0,0 +1,19 @@
+// 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.
+
+// 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_threadx/spin_lock_inline.h"
diff --git a/pw_sync_threadx/public_overrides/pw_sync_backend/spin_lock_native.h b/pw_sync_threadx/public_overrides/pw_sync_backend/spin_lock_native.h
new file mode 100644
index 0000000..dcf41fe
--- /dev/null
+++ b/pw_sync_threadx/public_overrides/pw_sync_backend/spin_lock_native.h
@@ -0,0 +1,19 @@
+// 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.
+
+// 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_threadx/spin_lock_native.h"
diff --git a/pw_sync_threadx/spin_lock.cc b/pw_sync_threadx/spin_lock.cc
new file mode 100644
index 0000000..6d8f277
--- /dev/null
+++ b/pw_sync_threadx/spin_lock.cc
@@ -0,0 +1,57 @@
+// 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/spin_lock.h"
+
+#include "pw_assert/assert.h"
+#include "tx_api.h"
+
+namespace pw::sync {
+
+void SpinLock::lock() {
+  // In order to be pw::sync::SpinLock compliant, mask the interrupts
+  // before attempting to grab the internal spin lock.
+  native_type_.saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
+
+  // This implementation is not set up to support SMP, meaning we cannot
+  // deadlock here due to the global interrupt lock, so we crash on recursion
+  // on a specific spinlock instead.
+  PW_CHECK(!native_type_.locked.load(std::memory_order_relaxed),
+           "Recursive SpinLock::lock() detected");
+
+  native_type_.locked.store(true, std::memory_order_relaxed);
+}
+
+bool SpinLock::try_lock() {
+  // In order to be pw::sync::SpinLock compliant, mask the interrupts
+  // before attempting to grab the internal spin lock.
+  UINT saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
+
+  if (native_type_.locked.load(std::memory_order_relaxed)) {
+    // Already locked, restore interrupts and bail out.
+    tx_interrupt_control(saved_interrupt_mask);
+    return false;
+  }
+
+  native_type_.saved_interrupt_mask = saved_interrupt_mask;
+  native_type_.locked.store(true, std::memory_order_relaxed);
+  return true;
+}
+
+void SpinLock::unlock() {
+  native_type_.locked.store(false, std::memory_order_relaxed);
+  tx_interrupt_control(native_type_.saved_interrupt_mask);
+}
+
+}  // namespace pw::sync
diff --git a/third_party/threadx/BUILD.gn b/third_party/threadx/BUILD.gn
new file mode 100644
index 0000000..e400f06
--- /dev/null
+++ b/third_party/threadx/BUILD.gn
@@ -0,0 +1,51 @@
+# 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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+declare_args() {
+  # If compiling backends with ThreadX, this variable is set to the path to the
+  # ThreadX include directory. When set, a pw_source_set for the threadx library
+  # is created at "$dir_pw_third_party/threadx".
+  dir_pw_third_party_threadx_include = ""
+
+  # The pw_source_set which provides the port specific includes and sources.
+  pw_third_party_threadx_PORT = ""
+}
+
+# This file defines a GN source_set for an external installation of ThreadX.
+# To use, checkout the threadx source into a directory, then set the build arg
+# dir_pw_third_party_threadx_include to point to that directory. The ThreadX
+# library will be available in GN at "$dir_pw_third_party/threadx".
+if (dir_pw_third_party_threadx_include != "") {
+  config("public_includes") {
+    include_dirs = [ "$dir_pw_third_party_threadx_include" ]
+    visibility = [ ":*" ]
+  }
+
+  pw_source_set("threadx") {
+    public_configs = [ ":public_includes" ]
+    allow_circular_includes_from = [ pw_third_party_threadx_PORT ]
+    public_deps = [ pw_third_party_threadx_PORT ]
+    public = [
+      "$dir_pw_third_party_threadx_include/tx_api.h",
+      "$dir_pw_third_party_threadx_include/tx_port.h",
+    ]
+  }
+} else {
+  group("threadx") {
+  }
+}