// 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 { match self { Self::Active(info) => Some(info.slot), _ => None, } } } /// Information related to a slot. struct SlotInfo { /// 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 { /// Slot is free. #[default] Free, /// Slot is active. Active(SlotInfo), /// Slot is idle. Idle(SlotInfo), } pub(crate) type LockedSeat = LockedBy>; /// Trait describing the slot-related operations. pub(crate) trait SlotOperations: 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; /// 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, 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; MAX_SLOTS], /// Sequence number incremented each time a Seat is successfully activated use_seqno: u64, } impl, const MAX_SLOTS: usize> SlotManager { /// Creates a specific instance of a slot manager. pub(crate) fn new(manager: T, slot_count: usize) -> Result { 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) -> 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) -> 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) { 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) -> 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) -> 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, const MAX_SLOTS: usize> Deref for SlotManager { type Target = T; fn deref(&self) -> &Self::Target { &self.manager } } impl, const MAX_SLOTS: usize> DerefMut for SlotManager { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.manager } }