starry-kernel 0.10.1

A Linux-compatible OS kernel built on ArceOS unikernel
Documentation
//! StarryOS synchronization facade.
//!
//! Kernel code imports locks only through this module so the type name and
//! acquisition operation preserve sleep, preemption, and IRQ semantics.

use alloc::vec::Vec;

pub(crate) use ax_fs_ng::os::sync::SleepMutex as FsMutex;
pub(crate) use ax_runtime::task::sync::{
    InterruptibleMutexExt, LockdepMutexExt, Mutex, MutexGuard, PreemptGuard,
    PreemptIrqSaveGuard as NoPreemptIrqSave, RawIrqSaveMutex, RawSpinLock, RawSpinLockGuard, RawSpinRwLock,
};

/// An IRQ-save spin mutex for state reachable from interrupt context.
#[repr(transparent)]
pub(crate) struct IrqMutex<T: ?Sized>(ax_runtime::task::sync::RawSpinLock<T>);

pub(crate) type IrqMutexGuard<'a, T> = ax_runtime::task::sync::RawSpinLockIrqSaveGuard<'a, T>;

impl<T> IrqMutex<T> {
    #[track_caller]
    pub(crate) const fn new(value: T) -> Self {
        Self(ax_runtime::task::sync::RawSpinLock::new(value))
    }

    #[track_caller]
    pub(crate) fn lock(&self) -> IrqMutexGuard<'_, T> {
        self.0.lock_irqsave()
    }

    /// Acquires this IRQ-save mutex using a lockdep subclass.
    #[track_caller]
    pub(crate) fn lock_nested(&self, subclass: u32) -> IrqMutexGuard<'_, T> {
        self.0.lock_irqsave_nested(subclass)
    }

    #[track_caller]
    pub(crate) fn try_lock(&self) -> Option<IrqMutexGuard<'_, T>> {
        self.0.try_lock_irqsave()
    }
}

impl<T: Default> Default for IrqMutex<T> {
    fn default() -> Self {
        Self::new(T::default())
    }
}

/// Ensures that an IRQ-protected vector can accept `additional` elements
/// without invoking the allocator while its guard is held.
///
/// A replacement backing store is allocated before publication.  The short
/// IRQ-save section only rechecks capacity, moves already-owned elements, and
/// swaps the two vectors.  If another producer grows the queue between the
/// snapshot and publication, the operation retries instead of relying on a
/// stale capacity observation.  The displaced allocation is destroyed only
/// after the IRQ guard has been released.
pub(crate) fn try_reserve_irq_vec<T>(
    queue: &IrqMutex<Vec<T>>,
    additional: usize,
) -> Result<(), ()> {
    if additional == 0 {
        return Ok(());
    }

    let mut replacement = Vec::new();
    loop {
        let reserve_target = {
            let entries = queue.lock();
            if entries.capacity().saturating_sub(entries.len()) >= additional {
                return Ok(());
            }
            let required = entries.len().checked_add(additional).ok_or(())?;
            required.max(entries.capacity().saturating_mul(2)).max(4)
        };

        // `replacement` is empty until the successful publication below, so
        // reserving `reserve_target` guarantees enough total capacity. Grow
        // geometrically so repeated insertions do not turn this safe fallback
        // into quadratic element movement.
        replacement
            .try_reserve_exact(reserve_target)
            .map_err(|_| ())?;

        let mut entries = queue.lock();
        if entries.capacity().saturating_sub(entries.len()) >= additional {
            return Ok(());
        }
        let Some(required) = entries.len().checked_add(additional) else {
            return Err(());
        };
        if replacement.capacity() < required {
            // A concurrent producer consumed more capacity.  Drop the guard
            // before growing the detached replacement and retry.
            continue;
        }
        replacement.extend(entries.drain(..));
        core::mem::swap(&mut *entries, &mut replacement);
        drop(entries);
        // `replacement` now owns the old, empty backing store.  Its Drop is
        // deliberately outside the IRQ-save critical section.
        return Ok(());
    }
}

/// Appends one owned value without allocating or destroying it under the IRQ
/// guard.  A racing producer may consume a prepared slot, so insertion
/// rechecks and repeats the lock-external reservation until it owns capacity.
pub(crate) fn try_push_irq_vec<T>(queue: &IrqMutex<Vec<T>>, value: T) -> Result<(), T> {
    loop {
        if try_reserve_irq_vec(queue, 1).is_err() {
            return Err(value);
        }
        let mut entries = queue.lock();
        if entries.len() == entries.capacity() {
            continue;
        }
        entries.push(value);
        return Ok(());
    }
}

impl<T: core::fmt::Debug> core::fmt::Debug for IrqMutex<T> {
    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.0.fmt(formatter)
    }
}

pub(crate) type NoPreemptMutex<T> = RawSpinLock<T>;
pub(crate) type RwLock<T> = RawSpinRwLock<T>;