diff --git a/drivers/gpu/drm/tyr/slot.rs b/drivers/gpu/drm/tyr/slot.rs new file mode 100644 index 000000000000..845846e07ee5 --- /dev/null +++ b/drivers/gpu/drm/tyr/slot.rs @@ -0,0 +1,405 @@ +// 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 +#![expect(dead_code)] + +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 + } +} diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs index 95cda7b0962f..7c9a8063b3b9 100644 --- a/drivers/gpu/drm/tyr/tyr.rs +++ b/drivers/gpu/drm/tyr/tyr.rs @@ -12,6 +12,7 @@ mod gem; mod gpu; mod regs; +mod slot; kernel::module_platform_driver! { type: TyrPlatformDriver,