syd 3.52.0

rock-solid application kernel
Documentation
// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2025 Google LLC.

//! Rust API for an ID pool backed by a [`BitmapVec`].

use crate::alloc::{AllocError, Flags};
use crate::bitmap::BitmapVec;

/// Represents a dynamic ID pool backed by a [`BitmapVec`].
///
/// Clients acquire and release IDs from unset bits in a bitmap.
///
/// The capacity of the ID pool may be adjusted by users as
/// needed. The API supports the scenario where users need precise control
/// over the time of allocation of a new backing bitmap, which may require
/// release of spinlock.
/// Due to concurrent updates, all operations are re-verified to determine
/// if the grow or shrink is sill valid.
///
/// # Examples
///
/// Basic usage
///
/// ```
/// use kernel::alloc::AllocError;
/// use kernel::id_pool::{IdPool, UnusedId};
///
/// let mut pool = IdPool::with_capacity(64, GFP_KERNEL)?;
/// for i in 0..64 {
///     assert_eq!(i, pool.find_unused_id(i).ok_or(ENOSPC)?.acquire());
/// }
///
/// pool.release_id(23);
/// assert_eq!(23, pool.find_unused_id(0).ok_or(ENOSPC)?.acquire());
///
/// assert!(pool.find_unused_id(0).is_none());  // time to realloc.
/// let resizer = pool.grow_request().ok_or(ENOSPC)?.realloc(GFP_KERNEL)?;
/// pool.grow(resizer);
///
/// assert_eq!(pool.find_unused_id(0).ok_or(ENOSPC)?.acquire(), 64);
/// # Ok::<(), Error>(())
/// ```
///
/// Releasing spinlock to grow the pool
///
/// ```no_run
/// use kernel::alloc::{AllocError, flags::GFP_KERNEL};
/// use kernel::sync::{new_spinlock, SpinLock};
/// use kernel::id_pool::IdPool;
///
/// fn get_id_maybe_realloc(guarded_pool: &SpinLock<IdPool>) -> Result<usize, AllocError> {
///     let mut pool = guarded_pool.lock();
///     loop {
///         match pool.find_unused_id(0) {
///             Some(index) => return Ok(index.acquire()),
///             None => {
///                 let alloc_request = pool.grow_request();
///                 drop(pool);
///                 let resizer = alloc_request.ok_or(AllocError)?.realloc(GFP_KERNEL)?;
///                 pool = guarded_pool.lock();
///                 pool.grow(resizer)
///             }
///         }
///     }
/// }
/// ```
pub struct IdPool {
    map: BitmapVec,
}

/// Indicates that an [`IdPool`] should change to a new target size.
pub struct ReallocRequest {
    num_ids: usize,
}

/// Contains a [`BitmapVec`] of a size suitable for reallocating [`IdPool`].
pub struct PoolResizer {
    new: BitmapVec,
}

impl ReallocRequest {
    /// Allocates a new backing [`BitmapVec`] for [`IdPool`].
    ///
    /// This method only prepares reallocation and does not complete it.
    /// Reallocation will complete after passing the [`PoolResizer`] to the
    /// [`IdPool::grow`] or [`IdPool::shrink`] operation, which will check
    /// that reallocation still makes sense.
    pub fn realloc(&self, flags: Flags) -> Result<PoolResizer, AllocError> {
        let new = BitmapVec::new(self.num_ids, flags)?;
        Ok(PoolResizer { new })
    }
}

impl IdPool {
    /// Constructs a new [`IdPool`].
    ///
    /// The pool will have a capacity of [`MAX_INLINE_LEN`].
    ///
    /// [`MAX_INLINE_LEN`]: BitmapVec::MAX_INLINE_LEN
    #[inline]
    pub fn new() -> Self {
        Self {
            map: BitmapVec::new_inline(),
        }
    }

    /// Constructs a new [`IdPool`] with space for a specific number of bits.
    ///
    /// A capacity below [`MAX_INLINE_LEN`] is adjusted to [`MAX_INLINE_LEN`].
    ///
    /// [`MAX_INLINE_LEN`]: BitmapVec::MAX_INLINE_LEN
    #[inline]
    pub fn with_capacity(num_ids: usize, flags: Flags) -> Result<Self, AllocError> {
        let num_ids = usize::max(num_ids, BitmapVec::MAX_INLINE_LEN);
        let map = BitmapVec::new(num_ids, flags)?;
        Ok(Self { map })
    }

    /// Returns how many IDs this pool can currently have.
    #[inline]
    pub fn capacity(&self) -> usize {
        self.map.len()
    }

    /// Returns a [`ReallocRequest`] if the [`IdPool`] can be shrunk, [`None`] otherwise.
    ///
    /// The capacity of an [`IdPool`] cannot be shrunk below [`MAX_INLINE_LEN`].
    ///
    /// [`MAX_INLINE_LEN`]: BitmapVec::MAX_INLINE_LEN
    ///
    /// # Examples
    ///
    /// ```
    /// use kernel::{
    ///     alloc::AllocError,
    ///     bitmap::BitmapVec,
    ///     id_pool::{
    ///         IdPool,
    ///         ReallocRequest,
    ///     },
    /// };
    ///
    /// let mut pool = IdPool::with_capacity(1024, GFP_KERNEL)?;
    /// let alloc_request = pool.shrink_request().ok_or(AllocError)?;
    /// let resizer = alloc_request.realloc(GFP_KERNEL)?;
    /// pool.shrink(resizer);
    /// assert_eq!(pool.capacity(), BitmapVec::MAX_INLINE_LEN);
    /// # Ok::<(), AllocError>(())
    /// ```
    #[inline]
    pub fn shrink_request(&self) -> Option<ReallocRequest> {
        let cap = self.capacity();
        // Shrinking below `MAX_INLINE_LEN` is never possible.
        if cap <= BitmapVec::MAX_INLINE_LEN {
            return None;
        }
        // Determine if the bitmap can shrink based on the position of
        // its last set bit. If the bit is within the first quarter of
        // the bitmap then shrinking is possible. In this case, the
        // bitmap should shrink to half its current size.
        let Some(bit) = self.map.last_bit() else {
            return Some(ReallocRequest {
                num_ids: BitmapVec::MAX_INLINE_LEN,
            });
        };
        if bit >= (cap / 4) {
            return None;
        }
        let num_ids = usize::max(BitmapVec::MAX_INLINE_LEN, cap / 2);
        Some(ReallocRequest { num_ids })
    }

    /// Shrinks pool by using a new [`BitmapVec`], if still possible.
    #[inline]
    pub fn shrink(&mut self, mut resizer: PoolResizer) {
        // Between request to shrink that led to allocation of `resizer` and now,
        // bits may have changed.
        // Verify that shrinking is still possible. In case shrinking to
        // the size of `resizer` is no longer possible, do nothing,
        // drop `resizer` and move on.
        let Some(updated) = self.shrink_request() else {
            return;
        };
        if updated.num_ids > resizer.new.len() {
            return;
        }

        resizer.new.copy_and_extend(&self.map);
        self.map = resizer.new;
    }

    /// Returns a [`ReallocRequest`] for growing this [`IdPool`], if possible.
    ///
    /// The capacity of an [`IdPool`] cannot be grown above [`MAX_LEN`].
    ///
    /// [`MAX_LEN`]: BitmapVec::MAX_LEN
    #[inline]
    pub fn grow_request(&self) -> Option<ReallocRequest> {
        let num_ids = self.capacity() * 2;
        if num_ids > BitmapVec::MAX_LEN {
            return None;
        }
        Some(ReallocRequest { num_ids })
    }

    /// Grows pool by using a new [`BitmapVec`], if still necessary.
    ///
    /// The `resizer` arguments has to be obtained by calling [`Self::grow_request`]
    /// on this object and performing a [`ReallocRequest::realloc`].
    #[inline]
    pub fn grow(&mut self, mut resizer: PoolResizer) {
        // Between request to grow that led to allocation of `resizer` and now,
        // another thread may have already grown the capacity.
        // In this case, do nothing, drop `resizer` and move on.
        if resizer.new.len() <= self.capacity() {
            return;
        }

        resizer.new.copy_and_extend(&self.map);
        self.map = resizer.new;
    }

    /// Finds an unused ID in the bitmap.
    ///
    /// Upon success, returns its index. Otherwise, returns [`None`]
    /// to indicate that a [`Self::grow_request`] is needed.
    #[inline]
    #[must_use]
    pub fn find_unused_id(&mut self, offset: usize) -> Option<UnusedId<'_>> {
        // INVARIANT: `next_zero_bit()` returns None or an integer less than `map.len()`
        Some(UnusedId {
            id: self.map.next_zero_bit(offset)?,
            pool: self,
        })
    }

    /// Releases an ID.
    #[inline]
    pub fn release_id(&mut self, id: usize) {
        self.map.clear_bit(id);
    }
}

/// Represents an unused id in an [`IdPool`].
///
/// # Invariants
///
/// The value of `id` is less than `pool.map.len()`.
pub struct UnusedId<'pool> {
    id: usize,
    pool: &'pool mut IdPool,
}

impl<'pool> UnusedId<'pool> {
    /// Get the unused id as an usize.
    ///
    /// Be aware that the id has not yet been acquired in the pool. The
    /// [`acquire`] method must be called to prevent others from taking the id.
    ///
    /// [`acquire`]: UnusedId::acquire()
    #[inline]
    #[must_use]
    pub fn as_usize(&self) -> usize {
        self.id
    }

    /// Get the unused id as an u32.
    ///
    /// Be aware that the id has not yet been acquired in the pool. The
    /// [`acquire`] method must be called to prevent others from taking the id.
    ///
    /// [`acquire`]: UnusedId::acquire()
    #[inline]
    #[must_use]
    pub fn as_u32(&self) -> u32 {
        // CAST: By the type invariants:
        // `self.id < pool.map.len() <= BitmapVec::MAX_LEN = i32::MAX`.
        self.id as u32
    }

    /// Acquire the unused id.
    #[inline]
    pub fn acquire(self) -> usize {
        self.pool.map.set_bit(self.id);
        self.id
    }
}

impl Default for IdPool {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}