blob: 7d4724c52b2ff2968f3b4b67ea5529e208db53d9 [file]
// 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.
use core::arch::asm;
use core::cell::UnsafeCell;
use core::mem::ManuallyDrop;
use riscv::register::*;
pub struct InterruptGuard {
saved_interrupt_enable: bool,
}
impl InterruptGuard {
#[inline]
pub fn new() -> Self {
// TODO: combine these two into single instruction
let saved_interrupt_enable = mstatus::read().mie();
unsafe {
mstatus::clear_mie();
}
// A hardware fence rather than compiler_fence is required on RISC-V to
// prevent the CPU from reordering memory accesses (including volatile operations) across
// the CSR write. Acquire ordering ("fence r, rw") is sufficient because we only need to
// prevent subsequent memory accesses from being reordered before the interrupt disable.
unsafe {
asm!("fence r, rw", options(nostack, preserves_flags));
}
Self {
saved_interrupt_enable,
}
}
}
impl Drop for InterruptGuard {
#[inline]
fn drop(&mut self) {
// A hardware fence rather than compiler_fence is required on RISC-V to
// ensure all memory accesses (like releasing the spinlock) are visible before interrupts are
// re-enabled via the CSR write. Release ordering ("fence rw, w") is sufficient because we
// only need to ensure all prior memory writes are visible before the interrupt enable.
unsafe {
asm!("fence rw, w", options(nostack, preserves_flags));
}
if self.saved_interrupt_enable {
unsafe {
mstatus::set_mie();
}
}
}
}
pub struct RiscVSpinLockGuard<'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 RiscVSpinLockGuard<'_> {
#[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>,
}
// Safety: Access to `is_locked` is protected by disabling interrupts and
// proper barriers.
unsafe impl Send for BareSpinLock {}
unsafe impl Sync for BareSpinLock {}
impl BareSpinLock {
#[must_use]
pub const fn new() -> Self {
Self {
is_locked: UnsafeCell::new(false),
}
}
// Must be called with interrupts disabled.
#[inline]
unsafe fn unlock(&self) {
unsafe {
self.is_locked.get().write_volatile(false);
}
}
}
impl Default for BareSpinLock {
fn default() -> Self {
Self::new()
}
}
impl kernel::sync::spinlock::BareSpinLock for BareSpinLock {
type Guard<'a> = RiscVSpinLockGuard<'a>;
const NEW: BareSpinLock = Self::new();
#[inline(always)]
fn try_lock(&self) -> Option<Self::Guard<'_>> {
let guard = InterruptGuard::new();
// Safety: exclusive access to `is_locked` guaranteed because interrupts
// are off.
// TODO - pwbug/405145609: use volatile read and writes for state variable
if unsafe { self.is_locked.get().read_volatile() } {
return None;
}
unsafe {
self.is_locked.get().write_volatile(true);
}
Some(RiscVSpinLockGuard {
guard: ManuallyDrop::new(guard),
lock: self,
})
}
#[inline(always)]
fn lock(&self) -> Self::Guard<'_> {
let guard = InterruptGuard::new();
// Safety: exclusive access to `is_locked` guaranteed because interrupts
// are off.
// For the uniprocessor version of the spinlock, there is no need to spin.
if unsafe { self.is_locked.get().read_volatile() } {
pw_assert::panic!("recursively locked spinlock");
}
unsafe {
self.is_locked.get().write_volatile(true);
}
RiscVSpinLockGuard {
guard: ManuallyDrop::new(guard),
lock: self,
}
}
/// Unconditionally break the lock.
///
/// Do not call directly.
///
/// # Safety
/// See [`kernel::sync::spinlock::SpinLock::break_lock()`] for use and
/// safety information.
unsafe fn break_lock(&self) {
unsafe { self.is_locked.get().write_volatile(false) };
}
}