blob: d194ead53f7105f92f2aad7cafb471800245f4bc [file] [edit]
// SPDX-License-Identifier: GPL-2.0 or MIT
//! Slot management abstraction for limited hardware resources.
//!
//! This module provides a generic [`SlotManager`] that assigns limited hardware
//! slots to logical "seats". A seat represents an entity (such as a virtual memory
//! (VM) address space) that needs access to a hardware slot.
//!
//! The [`SlotManager`] tracks slot allocation using sequence numbers (seqno) to detect
//! when a seat's binding has been invalidated. When a seat requests activation,
//! the manager will either reuse the seat's existing slot (if still valid),
//! allocate a free slot (if any are available), or evict the oldest idle slot if any
//! slots are idle.
//!
//! Hardware-specific behavior is customized by implementing the [`SlotOperations`]
//! trait, which allows callbacks when slots are activated or evicted.
//!
//! This is currently used for managing address space slots in the GPU, and it will
//! also be used to manage Command Stream Group (CSG) interface slots in the future.
//!
//! [SlotOperations]: crate::slot::SlotOperations
//! [SlotManager]: crate::slot::SlotManager
use core::{
mem,
ops::{
Deref,
DerefMut, //
}, //
};
use kernel::{
prelude::*,
sync::LockedBy, //
};
/// Seat information.
///
/// This can't be accessed directly by the element embedding a `Seat`,
/// but is used by the generic slot manager logic to control residency
/// of a certain object on a hardware slot.
pub(crate) struct SeatInfo {
/// Slot used by this seat.
///
/// This index is only valid if the slot pointed to by this index
/// has its `SlotInfo::seqno` match `SeatInfo::seqno`. Otherwise,
/// it means the object has been evicted from the hardware slot,
/// and a new slot needs to be acquired to make this object
/// resident again.
slot: u8,
/// Sequence number encoding the last time this seat was active.
/// We also use it to check if a slot is still bound to a seat.
seqno: u64,
}
/// Seat state.
///
/// This is meant to be embedded in the object that wants to acquire
/// hardware slots. It also starts in the `Seat::NoSeat` state, and
/// the slot manager will change the object value when an active/evict
/// request is issued.
#[derive(Default)]
pub(crate) enum Seat {
#[expect(clippy::enum_variant_names)]
/// Resource is not resident.
///
/// All objects start with a seat in the `Seat::NoSeat` state. The seat also
/// gets back to that state if the user requests eviction. It
/// can also end up in that state next time an operation is done
/// on a `Seat::Idle` seat and the slot manager finds out this
/// object has been evicted from the slot.
#[default]
NoSeat,
/// Resource is actively used and resident.
///
/// When a seat is in the `Seat::Active` state, it can't be evicted, and the
/// slot pointed to by `SeatInfo::slot` is guaranteed to be reserved
/// for this object as long as the seat stays active.
Active(SeatInfo),
/// Resource is idle and might or might not be resident.
///
/// When a seat is in the`Seat::Idle` state, we can't know for sure if the
/// object is resident or evicted until the next request we issue
/// to the slot manager. This tells the slot manager it can
/// reclaim the underlying slot if needed.
/// In order for the hardware to use this object again, the seat
/// needs to be turned into an `Seat::Active` state again
/// with a `SlotManager::activate()` call.
Idle(SeatInfo),
}
impl Seat {
/// Get the slot index this seat is pointing to.
///
/// If the seat is not `Seat::Active` we can't trust the
/// `SeatInfo`. In that case `None` is returned, otherwise
/// `Some(SeatInfo::slot)` is returned.
pub(crate) fn slot(&self) -> Option<u8> {
match self {
Self::Active(info) => Some(info.slot),
_ => None,
}
}
}
/// Information related to a slot.
struct SlotInfo<D> {
/// Type specific data attached to a slot.
slot_data: D,
/// Sequence number from when this slot was last activated.
seqno: u64,
}
/// Slot state.
#[derive(Default)]
enum Slot<D> {
/// Slot is free.
#[default]
Free,
/// Slot is active.
Active(SlotInfo<D>),
/// Slot is idle.
Idle(SlotInfo<D>),
}
pub(crate) type LockedSeat<T, const MAX_SLOTS: usize> = LockedBy<Seat, SlotManager<T, MAX_SLOTS>>;
/// Trait describing the slot-related operations.
pub(crate) trait SlotOperations<const MAX_SLOTS: usize>: Sized {
/// Implementation-specific data associated with each slot.
type SlotData;
/// Returns the seat belonging to this slot data.
fn seat(slot_data: &Self::SlotData) -> &LockedSeat<Self, MAX_SLOTS>;
/// Called when a slot is being activated for a seat.
fn activate(&mut self, _slot_idx: usize, _slot_data: &Self::SlotData) -> Result {
Ok(())
}
/// Called when a slot is being evicted and freed.
fn evict(&mut self, _slot_idx: usize, _slot_data: &Self::SlotData) -> Result {
Ok(())
}
}
/// A generic slot manager that provides access to a limited number of hardware slots.
pub(crate) struct SlotManager<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> {
/// A specific implementation of the generic slot manager.
manager: T,
/// Number of slots actually available.
slot_count: usize,
/// Slot array used to track the state of each slot.
slots: [Slot<T::SlotData>; MAX_SLOTS],
/// Sequence number incremented each time a Seat is successfully activated
use_seqno: u64,
}
impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> SlotManager<T, MAX_SLOTS> {
/// Creates a specific instance of a slot manager.
pub(crate) fn new(manager: T, slot_count: usize) -> Result<Self> {
if slot_count == 0 {
return Err(EINVAL);
}
if slot_count > MAX_SLOTS {
return Err(EINVAL);
}
// Since the slot index is stored in SeatInfo as a u8, the maximum number of slots is 256.
if slot_count > u8::MAX as usize + 1 {
return Err(EINVAL);
}
Ok(Self {
manager,
slot_count,
slots: [const { Slot::Free }; MAX_SLOTS],
use_seqno: 1,
})
}
/// Records a newly activated slot for the given seat.
/// The slot manager takes ownership of the hardware-specific slot data.
fn record_active_slot(&mut self, slot_idx: usize, slot_data: T::SlotData) {
let cur_seqno = self.use_seqno;
*T::seat(&slot_data).access_mut(self) = Seat::Active(SeatInfo {
slot: slot_idx as u8,
seqno: cur_seqno,
});
self.slots[slot_idx] = Slot::Active(SlotInfo {
slot_data,
seqno: cur_seqno,
});
self.use_seqno += 1;
}
/// Reactivates an active/idle slot for a given seat without reprogramming the hardware.
/// The SlotManager reuses the existing slot_data. This ensures that the hardware-specific
/// information is not changed between subsequent uses. It also ensures that resources
/// owned by the existing slot_data remain alive while the hardware is configured to use them.
fn reactivate_slot(&mut self, slot_idx: usize, slot_data: &T::SlotData) -> Result {
let cur_seqno = self.use_seqno;
let mut slot_info = match mem::take(&mut self.slots[slot_idx]) {
Slot::Active(slot_info) | Slot::Idle(slot_info) => slot_info,
Slot::Free => {
*T::seat(slot_data).access_mut(self) = Seat::NoSeat;
return Err(EINVAL);
}
};
*T::seat(slot_data).access_mut(self) = Seat::Active(SeatInfo {
slot: slot_idx as u8,
seqno: cur_seqno,
});
slot_info.seqno = cur_seqno;
self.slots[slot_idx] = Slot::Active(slot_info);
self.use_seqno += 1;
Ok(())
}
/// Activates a slot for the given seat.
fn activate_slot(&mut self, slot_idx: usize, slot_data: T::SlotData) -> Result {
self.manager.activate(slot_idx, &slot_data)?;
self.record_active_slot(slot_idx, slot_data);
Ok(())
}
/// Finds a slot for the given seat. A free slot is preferred, but if none
/// are available, the oldest idle slot is evicted and reused. Otherwise, if
/// there are no free or idle slots, return [`EBUSY`].
fn allocate_slot(&mut self, slot_data: T::SlotData) -> Result {
let slots = &self.slots[..self.slot_count];
let mut idle_slot_idx = None;
let mut idle_slot_seqno: u64 = 0;
for (slot_idx, slot) in slots.iter().enumerate() {
match slot {
Slot::Free => {
return self.activate_slot(slot_idx, slot_data);
}
Slot::Idle(slot_info) => {
if idle_slot_idx.is_none() || slot_info.seqno < idle_slot_seqno {
idle_slot_idx = Some(slot_idx);
idle_slot_seqno = slot_info.seqno;
}
}
Slot::Active(_) => (),
}
}
match idle_slot_idx {
Some(slot_idx) => {
// Lazily evict idle slot just before it is reused.
if let Slot::Idle(slot_info) = &self.slots[slot_idx] {
self.manager.evict(slot_idx, &slot_info.slot_data)?;
mem::take(&mut self.slots[slot_idx]);
}
self.activate_slot(slot_idx, slot_data)
}
None => Err(EBUSY),
}
}
/// Converts an active slot and its seat to idle state.
fn idle_slot(&mut self, slot_idx: usize, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
let slot = mem::take(&mut self.slots[slot_idx]);
self.slots[slot_idx] = match slot {
// If the slot was active, make it idle.
Slot::Active(slot_info) => Slot::Idle(slot_info),
// Preserve an already-idle slot.
Slot::Idle(slot_info) => Slot::Idle(slot_info),
// A free slot remains free.
Slot::Free => Slot::Free,
};
// If the seat was active, make it idle, or keep it idle if it was already idle.
*locked_seat.access_mut(self) = match locked_seat.access(self) {
Seat::Active(seat_info) | Seat::Idle(seat_info) => Seat::Idle(SeatInfo {
slot: seat_info.slot,
seqno: seat_info.seqno,
}),
Seat::NoSeat => Seat::NoSeat,
};
Ok(())
}
/// Evicts an active or idle slot: calls the eviction callback and marks the slot as free
/// and the seat as NoSeat.
fn evict_slot(&mut self, slot_idx: usize, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
match &self.slots[slot_idx] {
Slot::Active(slot_info) | Slot::Idle(slot_info) => {
// If hardware eviction fails (e.g. times out), the slot retains
// its SlotData so that any resources still referenced by the hardware
// will remain alive. This prevents use-after-free errors.
self.manager.evict(slot_idx, &slot_info.slot_data)?;
mem::take(&mut self.slots[slot_idx]);
}
_ => (),
}
*locked_seat.access_mut(self) = Seat::NoSeat;
Ok(())
}
/// Checks that the seat state matches the slot's state.
/// If they don't match, the seat is stale and is reset to `NoSeat`.
fn check_seat(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) {
let (slot_idx, seat_seqno, is_active) = match locked_seat.access(self) {
Seat::Active(seat_info) => (seat_info.slot as usize, seat_info.seqno, true),
Seat::Idle(seat_info) => (seat_info.slot as usize, seat_info.seqno, false),
_ => return,
};
let valid = if is_active {
!kernel::warn_on!(!matches!(
&self.slots[slot_idx],
Slot::Active(slot_info) if slot_info.seqno == seat_seqno
))
} else {
matches!(
&self.slots[slot_idx],
Slot::Idle(slot_info) if slot_info.seqno == seat_seqno
)
};
if !valid {
*locked_seat.access_mut(self) = Seat::NoSeat;
}
}
/// Activates a resource on any available/reclaimable slot.
pub(crate) fn activate(&mut self, slot_data: T::SlotData) -> Result {
self.check_seat(T::seat(&slot_data));
// Copy out only the slot index so the borrow of slot_data ends here.
let slot_idx = match T::seat(&slot_data).access(self) {
Seat::Active(seat_info) | Seat::Idle(seat_info) => Some(seat_info.slot as usize),
Seat::NoSeat => None,
};
match slot_idx {
Some(slot_idx) => self.reactivate_slot(slot_idx, &slot_data),
None => self.allocate_slot(slot_data),
}
}
/// Flag a resource as idle. This method will be used for user VM support.
#[expect(dead_code)]
pub(crate) fn idle(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
self.check_seat(locked_seat);
if let Seat::Active(seat_info) = locked_seat.access(self) {
self.idle_slot(seat_info.slot as usize, locked_seat)?;
}
Ok(())
}
/// Evict a resource from its slot.
pub(crate) fn evict(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
self.check_seat(locked_seat);
match locked_seat.access(self) {
Seat::Active(seat_info) | Seat::Idle(seat_info) => {
let slot_idx = seat_info.slot as usize;
self.evict_slot(slot_idx, locked_seat)?;
}
_ => (),
}
Ok(())
}
}
impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> Deref for SlotManager<T, MAX_SLOTS> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.manager
}
}
impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> DerefMut for SlotManager<T, MAX_SLOTS> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.manager
}
}