> On 9 Jul 2026, at 18:36, Deborah Brouwer <[email protected]> 
> wrote:
> 
> From: Boris Brezillon <[email protected]>
> 
> Introduce a generic slot manager to dynamically allocate limited hardware
> slots to software "seats". It can be used for both address space (AS) and
> command stream group (CSG) slots.
> 
> The slot manager initially assigns seats to its free slots. It will
> continue to reuse the same slot for a seat, as long as another seat does
> not start to use the slot in the interim.
> 
> When contention arises because all of the slots are allocated, the slot
> manager will lazily evict and reuse slots that have become idle (if any).
> 
> The seat state is protected using the LockedBy pattern with the same lock
> that guards the SlotManager. This ensures the seat state stays consistent
> across slot operations.
> 
> Hardware specific behaviour is controlled through the SlotManager's
> specific manager type that implements the `SlotOperations` trait.
> 
> Signed-off-by: Boris Brezillon <[email protected]>
> Co-developed-by: Deborah Brouwer <[email protected]>
> Signed-off-by: Deborah Brouwer <[email protected]>
> ---
> drivers/gpu/drm/tyr/slot.rs | 385 ++++++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tyr/tyr.rs  |   1 +
> 2 files changed, 386 insertions(+)
> 
> diff --git a/drivers/gpu/drm/tyr/slot.rs b/drivers/gpu/drm/tyr/slot.rs
> new file mode 100644
> index 000000000000..3f58b23238ef
> --- /dev/null
> +++ b/drivers/gpu/drm/tyr/slot.rs
> @@ -0,0 +1,385 @@
> +// 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
> +#![allow(dead_code)]
> +
> +use core::{
> +    mem::take,
> +    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(super) fn slot(&self) -> Option<u8> {

Can we use on pub(crate) instead of pub(super)?

> +        match self {
> +            Self::Active(info) => Some(info.slot),
> +            _ => None,
> +        }
> +    }
> +}
> +
> +/// Information related to a slot.
> +struct SlotInfo<T> {
> +    /// Type specific data attached to a slot.
> +    slot_data: T,
> +
> +    /// Sequence number from when this slot was last activated.
> +    seqno: u64,
> +}
> +
> +/// Slot state.
> +#[derive(Default)]
> +enum SlotState<T> {
> +    /// Slot is free.
> +    #[default]
> +    Free,
> +
> +    /// Slot is active.
> +    Active(SlotInfo<T>),
> +
> +    /// Slot is idle.
> +    Idle(SlotInfo<T>),
> +}
> +
> +/// Trait describing the slot-related operations.
> +pub(crate) trait SlotOperations {
> +    /// Implementation-specific data associated with each slot.
> +    type SlotData;
> +
> +    /// 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, 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: [SlotState<T::SlotData>; MAX_SLOTS],
> +
> +    /// Sequence number incremented each time a Seat is successfully 
> activated
> +    use_seqno: u64,
> +}
> +
> +/// A `Seat` that can only be accessed when holding a `SlotManager` lock 
> guard.
> +type LockedSeat<T, const MAX_SLOTS: usize> = LockedBy<Seat, SlotManager<T, 
> MAX_SLOTS>>;
> +
> +impl<T: SlotOperations, 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 {
> +            pr_err!("Invalid slot count: 0");
> +            return Err(EINVAL);
> +        }
> +        if slot_count > MAX_SLOTS {
> +            pr_err!(
> +                "Slot count: {} cannot exceed MAX_SLOTS: {}",
> +                slot_count,
> +                MAX_SLOTS
> +            );
> +            return Err(EINVAL);
> +        }
> +        Ok(Self {
> +            manager,
> +            slot_count,
> +            slots: [const { SlotState::Free }; MAX_SLOTS],
> +            use_seqno: 1,
> +        })
> +    }
> +
> +    /// Records a slot as active for the given seat.
> +    fn record_active_slot(
> +        &mut self,
> +        slot_idx: usize,
> +        locked_seat: &LockedSeat<T, MAX_SLOTS>,
> +        slot_data: T::SlotData,
> +    ) -> Result {

This looks infallible?

> +        let cur_seqno = self.use_seqno;
> +
> +        *locked_seat.access_mut(self) = Seat::Active(SeatInfo {
> +            slot: slot_idx as u8,
> +            seqno: cur_seqno,
> +        });
> +
> +        self.slots[slot_idx] = SlotState::Active(SlotInfo {
> +            slot_data,
> +            seqno: cur_seqno,
> +        });
> +
> +        self.use_seqno += 1;
> +        Ok(())
> +    }
> +
> +    /// Activates a slot for the given seat.
> +    fn activate_slot(
> +        &mut self,
> +        slot_idx: usize,
> +        locked_seat: &LockedSeat<T, MAX_SLOTS>,
> +        slot_data: T::SlotData,
> +    ) -> Result {
> +        self.manager.activate(slot_idx, &slot_data)?;
> +        self.record_active_slot(slot_idx, locked_seat, slot_data)
> +    }
> +
> +    /// 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,
> +        locked_seat: &LockedSeat<T, MAX_SLOTS>,
> +        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 {
> +                SlotState::Free => {
> +                    return self.activate_slot(slot_idx, locked_seat, 
> slot_data);
> +                }
> +                SlotState::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;
> +                    }
> +                }
> +                SlotState::Active(_) => (),
> +            }
> +        }
> +
> +        match idle_slot_idx {
> +            Some(slot_idx) => {
> +                // Lazily evict idle slot just before it is reused.
> +                if let SlotState::Idle(slot_info) = &self.slots[slot_idx] {
> +                    self.manager.evict(slot_idx, &slot_info.slot_data)?;
> +                }
> +                self.activate_slot(slot_idx, locked_seat, slot_data)
> +            }
> +            None => {
> +                pr_err!(
> +                    "Slot allocation failed: all {} slots in use\n",
> +                    self.slot_count
> +                );
> +                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 = take(&mut self.slots[slot_idx]);

I think it’d be clearer to have mem::take here instead of take.

> +
> +        // If the slot was active, make it idle.
> +        if let SlotState::Active(slot_info) = slot {
> +            self.slots[slot_idx] = SlotState::Idle(slot_info);

Is it me or we don’t place the slot back in the array if the slot is
already idle? Looking at the caller, it seems like this can’t happen today,
but still, perhaps we should use a match here as future-proofing

I think that would fit better, specially given the fact that the code below
does use a match.

> +        }
> +
> +        // 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] {
> +            SlotState::Active(slot_info) | SlotState::Idle(slot_info) => {
> +                self.manager.evict(slot_idx, &slot_info.slot_data)?;
> +                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],
> +                SlotState::Active(slot_info) if slot_info.seqno == seat_seqno
> +            ))
> +        } else {
> +            matches!(
> +                &self.slots[slot_idx],
> +                SlotState::Idle(slot_info) if slot_info.seqno == seat_seqno
> +            )
> +        };
> +
> +        if !valid {
> +            *locked_seat.access_mut(self) = Seat::NoSeat;
> +        }
> +    }
> +
> +    /// Activate a resource on any available/reclaimable slot.
> +    pub(crate) fn activate(
> +        &mut self,
> +        locked_seat: &LockedSeat<T, MAX_SLOTS>,
> +        slot_data: T::SlotData,
> +    ) -> Result {
> +        self.check_seat(locked_seat);
> +        match locked_seat.access(self) {
> +            // If a seat still has a valid slot, just reuse the slot and 
> refresh the bookkeeping.
> +            Seat::Active(seat_info) | Seat::Idle(seat_info) => {
> +                self.record_active_slot(seat_info.slot as usize, 
> locked_seat, slot_data)
> +            }
> +            _ => self.allocate_slot(locked_seat, 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, const MAX_SLOTS: usize> Deref for SlotManager<T, 
> MAX_SLOTS> {
> +    type Target = T;
> +
> +    fn deref(&self) -> &Self::Target {
> +        &self.manager
> +    }
> +}
> +
> +impl<T: SlotOperations, const MAX_SLOTS: usize> DerefMut for SlotManager<T, 
> MAX_SLOTS> {
> +    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,
> 
> -- 
> 2.54.0
> 

Reply via email to