spinlock: Add initial implementation
Change-Id: I687d54b15f47db651d3d0ce0475846110f8343c0
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/maize/+/256913
Pigweed-Auto-Submit: Erik Gilling <konkers@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Travis Geiselbrecht <travisg@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
diff --git a/arch/arm_cortex_m/BUILD.bazel b/arch/arm_cortex_m/BUILD.bazel
new file mode 100644
index 0000000..e14d276
--- /dev/null
+++ b/arch/arm_cortex_m/BUILD.bazel
@@ -0,0 +1,27 @@
+# Copyright 2025 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("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+rust_library(
+ name = "spinlock_backend_cortex_m",
+ srcs = ["spinlock_backend_cortex_m.rs"],
+ crate_name = "spinlock_backend",
+ deps = [
+ "//kernel/sync:spinlock_core",
+ "@rust_crates//:cortex-m",
+ ],
+)
diff --git a/arch/arm_cortex_m/spinlock_backend_cortex_m.rs b/arch/arm_cortex_m/spinlock_backend_cortex_m.rs
new file mode 100644
index 0000000..5c88aa6
--- /dev/null
+++ b/arch/arm_cortex_m/spinlock_backend_cortex_m.rs
@@ -0,0 +1,115 @@
+// Copyright 2025 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.
+#![no_std]
+
+use core::cell::UnsafeCell;
+use core::mem::ManuallyDrop;
+
+use cortex_m::interrupt;
+use cortex_m::register::primask::{self, Primask};
+use spinlock_core::BareSpinLockApi;
+
+struct InterruptGuard {
+ saved_primask: Primask,
+}
+
+impl InterruptGuard {
+ fn new() -> Self {
+ let saved_primask = primask::read();
+
+ // `cortex_m::interrupt::disable()` handles proper fencing.
+ interrupt::disable();
+
+ Self { saved_primask }
+ }
+}
+
+impl Drop for InterruptGuard {
+ #[inline]
+ fn drop(&mut self) {
+ if self.saved_primask.is_active() {
+ // `cortex_m::interrupt::enable()` handles proper fencing.
+ //
+ // Safety: cortex-m critical sections are not used in this code base.
+ unsafe { interrupt::enable() };
+ }
+ }
+}
+
+pub struct CortexMSpinLockGuard<'a> {
+ // A size optimization that allow the sentinel's drop() to directly
+ // call `InterruptGuard::do_drop()`.
+ guard: ManuallyDrop<InterruptGuard>,
+ lock: &'a BareSpinLock,
+}
+
+impl Drop for CortexMSpinLockGuard<'_> {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ self.lock.unlock();
+ ManuallyDrop::drop(&mut self.guard);
+ };
+ }
+}
+
+/// Non-SMP bare spinlock
+pub struct BareSpinLock {
+ // Lock state is needed to support `try_lock()` semantics. An `UnsafeCell`
+ // is used to hold the lock state as exclusive access is guaranteed by
+ // enabling and disabling interrupts.
+ is_locked: UnsafeCell<bool>,
+}
+
+impl BareSpinLock {
+ pub const fn new() -> Self {
+ Self {
+ is_locked: UnsafeCell::new(false),
+ }
+ }
+
+ // Must be called with interrupts disabled.
+ #[inline]
+ unsafe fn unlock(&self) {
+ *self.is_locked.get() = false;
+ }
+}
+
+impl Default for BareSpinLock {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl BareSpinLockApi for BareSpinLock {
+ type Guard<'a> = CortexMSpinLockGuard<'a>;
+
+ fn try_lock(&self) -> Option<Self::Guard<'_>> {
+ let guard = InterruptGuard::new();
+ // Safety: exclusive access to `is_locked` guaranteed because interrupts
+ // are off.
+ if unsafe { *self.is_locked.get() } {
+ return None;
+ }
+
+ unsafe {
+ *self.is_locked.get() = true;
+ }
+
+ Some(CortexMSpinLockGuard {
+ guard: ManuallyDrop::new(guard),
+ lock: self,
+ })
+ }
+}
diff --git a/kernel/sync/BUILD.bazel b/kernel/sync/BUILD.bazel
new file mode 100644
index 0000000..f07f0a1
--- /dev/null
+++ b/kernel/sync/BUILD.bazel
@@ -0,0 +1,56 @@
+# Copyright 2025 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("@pigweed//pw_build:compatibility.bzl", "incompatible_with_mcu")
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+package(default_visibility = ["//visibility:public"])
+
+rust_library(
+ name = "spinlock",
+ srcs = ["spinlock.rs"],
+ deps = [
+ ":spinlock_backend",
+ ":spinlock_core",
+ ],
+)
+
+rust_test(
+ name = "spinlock_test",
+ crate = ":spinlock",
+ use_libtest_harness = False,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//lib/unittest",
+ "//target:linker_script",
+ ],
+)
+
+rust_library(
+ name = "spinlock_core",
+ srcs = ["spinlock_core.rs"],
+)
+
+rust_library(
+ name = "spinlock_backend_atomic",
+ srcs = ["spinlock_backend_atomic.rs"],
+ crate_name = "spinlock_backend",
+ target_compatible_with = incompatible_with_mcu(),
+ deps = [":spinlock_core"],
+)
+
+label_flag(
+ name = "spinlock_backend",
+ build_setting_default = ":spinlock_backend_atomic",
+)
diff --git a/kernel/sync/spinlock.rs b/kernel/sync/spinlock.rs
new file mode 100644
index 0000000..7e30ae9
--- /dev/null
+++ b/kernel/sync/spinlock.rs
@@ -0,0 +1,106 @@
+// Copyright 2025 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.
+
+#![no_std]
+#![cfg_attr(test, no_main)]
+
+use core::cell::UnsafeCell;
+use core::ops::{Deref, DerefMut};
+
+pub use spinlock_backend::BareSpinLock;
+pub use spinlock_core::*;
+
+pub struct SpinLockGuard<'lock, T> {
+ lock: &'lock SpinLock<T>,
+ _inner_guard: <BareSpinLock as BareSpinLockApi>::Guard<'lock>,
+}
+
+impl<T> Deref for SpinLockGuard<'_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ unsafe { &*self.lock.data.get() }
+ }
+}
+
+impl<T> DerefMut for SpinLockGuard<'_, T> {
+ fn deref_mut(&mut self) -> &mut T {
+ unsafe { &mut *self.lock.data.get() }
+ }
+}
+
+pub struct SpinLock<T> {
+ data: UnsafeCell<T>,
+ inner: BareSpinLock,
+}
+
+impl<T> SpinLock<T> {
+ pub fn new(initial_value: T) -> Self {
+ Self {
+ data: UnsafeCell::new(initial_value),
+ inner: BareSpinLock::new(),
+ }
+ }
+
+ pub fn try_lock(&self) -> Option<SpinLockGuard<'_, T>> {
+ self.inner.try_lock().map(|guard| SpinLockGuard {
+ lock: self,
+ _inner_guard: guard,
+ })
+ }
+
+ pub fn lock(&self) -> SpinLockGuard<'_, T> {
+ let inner_guard = self.inner.lock();
+ SpinLockGuard {
+ lock: self,
+ _inner_guard: inner_guard,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use unittest::test;
+
+ #[test]
+ fn bare_try_lock_returns_correct_value() -> unittest::Result<()> {
+ let lock = BareSpinLock::new();
+
+ {
+ let _sentinel = lock.lock();
+ unittest::assert_true!(lock.try_lock().is_none());
+ }
+
+ unittest::assert_true!(lock.try_lock().is_some());
+
+ Ok(())
+ }
+
+ #[test]
+ fn try_lock_returns_correct_value() -> unittest::Result<()> {
+ let lock = SpinLock::new(false);
+
+ {
+ let mut guard = lock.lock();
+ *guard = true;
+ unittest::assert_true!(lock.try_lock().is_none());
+ }
+
+ let guard = lock.lock();
+ unittest::assert_true!(*guard);
+
+ Ok(())
+ }
+}
diff --git a/kernel/sync/spinlock_backend_atomic.rs b/kernel/sync/spinlock_backend_atomic.rs
new file mode 100644
index 0000000..c6c2862
--- /dev/null
+++ b/kernel/sync/spinlock_backend_atomic.rs
@@ -0,0 +1,62 @@
+// Copyright 2025 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.
+#![no_std]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+use spinlock_core::BareSpinLockApi;
+
+pub struct AtomicSpinLockGuard<'a> {
+ lock: &'a BareSpinLock,
+}
+
+impl Drop for AtomicSpinLockGuard<'_> {
+ fn drop(&mut self) {
+ self.lock.unlock();
+ }
+}
+
+pub struct BareSpinLock {
+ locked: AtomicBool,
+}
+
+impl BareSpinLock {
+ pub const fn new() -> Self {
+ Self {
+ locked: AtomicBool::new(false),
+ }
+ }
+
+ // Only to be called by AtomicSpinlockSentinel::drop().
+ fn unlock(&self) {
+ self.locked.store(false, Ordering::Release);
+ }
+}
+
+impl Default for BareSpinLock {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl BareSpinLockApi for BareSpinLock {
+ type Guard<'a> = AtomicSpinLockGuard<'a>;
+
+ fn try_lock(&self) -> Option<Self::Guard<'_>> {
+ self.locked
+ .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
+ .map(|_| AtomicSpinLockGuard { lock: self })
+ .ok()
+ }
+}
diff --git a/kernel/sync/spinlock_core.rs b/kernel/sync/spinlock_core.rs
new file mode 100644
index 0000000..941a831
--- /dev/null
+++ b/kernel/sync/spinlock_core.rs
@@ -0,0 +1,38 @@
+// Copyright 2025 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.
+#![no_std]
+
+pub trait BareSpinLockApi {
+ type Guard<'a>
+ where
+ Self: 'a;
+
+ // Rust does not support const function in traits. However it should be
+ // considered that the BareSpinlockApi includes:
+ //
+ // `const fn new() -> Self`
+
+ fn try_lock(&self) -> Option<Self::Guard<'_>>;
+
+ fn lock(&self) -> Self::Guard<'_> {
+ loop {
+ if let Some(sentinel) = self.try_lock() {
+ return sentinel;
+ }
+ }
+ }
+
+ // TODO - konkers: Add optimized path for functions that know they are in
+ // atomic context (i.e. interrupt handlers).
+}
diff --git a/lib/unittest/BUILD.bazel b/lib/unittest/BUILD.bazel
index 4183b61..3f15abf 100644
--- a/lib/unittest/BUILD.bazel
+++ b/lib/unittest/BUILD.bazel
@@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations under
# the License.
-load("@pigweed//pw_build:pw_linker_script.bzl", "pw_linker_script")
-load("@rules_rust//rust:defs.bzl", "rust_library", "rust_proc_macro", "rust_test")
load("@pigweed//pw_build:compatibility.bzl", "incompatible_with_mcu")
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_proc_macro", "rust_test")
package(default_visibility = ["//visibility:public"])
diff --git a/target/qemu/BUILD.bazel b/target/qemu/BUILD.bazel
index f44b533..ec2a5dc 100644
--- a/target/qemu/BUILD.bazel
+++ b/target/qemu/BUILD.bazel
@@ -26,8 +26,9 @@
"@rust_crates//:no_std",
],
flags = flags_from_dict({
- "//target:linker_script": "//target/qemu/linker_scripts:qemu_lm3s6965_linker_script",
"//lib/unittest:unittest_runner": "//lib/unittest:unittest_runner_cortex_m",
+ "//kernel/sync:spinlock_backend": "//arch/arm_cortex_m:spinlock_backend_cortex_m",
+ "//target:linker_script": "//target/qemu/linker_scripts:qemu_lm3s6965_linker_script",
"@pigweed//pw_log/rust:pw_log_backend": "//target/qemu/pw_log_backend_qemu:pw_log_backend",
}),
)
@@ -42,6 +43,7 @@
"@rust_crates//:no_std",
],
flags = flags_from_dict({
+ "//kernel/sync:spinlock_backend": "//arch/arm_cortex_m:spinlock_backend_cortex_m",
"//target:linker_script": "//target/qemu/linker_scripts:qemu_nrf51823_linker_script",
"@pigweed//pw_log/rust:pw_log_backend": "//target/qemu/pw_log_backend_qemu:pw_log_backend",
}),