On Thu, Feb 12, 2026 at 11:11:13AM +0100, Boris Brezillon wrote:
> On Wed, 11 Feb 2026 17:37:08 -0800
> 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 then
> > continues to reuse the same slot for a seat, as long as another seat
> > did 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 can be customized using the slot manager's
> > `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 | 359 ++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/tyr/tyr.rs  |   1 +
> >  2 files changed, 360 insertions(+)
> >  create mode 100644 drivers/gpu/drm/tyr/slot.rs
> > 
> > diff --git a/drivers/gpu/drm/tyr/slot.rs b/drivers/gpu/drm/tyr/slot.rs
> > new file mode 100644
> > index 000000000000..37bf8800a225
> > --- /dev/null
> > +++ b/drivers/gpu/drm/tyr/slot.rs
> > @@ -0,0 +1,359 @@
> > +// 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 vm 
> > address
> > +//! space) that needs access to a hardware slot.
> > +//!
> > +//! The [`SlotManager`] tracks slot allocation using sequence numbers 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 primarily used for managing address space slots in the GPU, 
> > where
> > +//! the number of hardware address space slots is limited.
> 
> I'd probably mention that we intend to use it for other stuff (Csg
> slots), hence the generalization done here.

Ack.

> 
> > +//!
> > +//! [SlotOperations]: crate::slot::SlotOperations
> > +//! [SlotManager]: crate::slot::SlotManager
> 
> Thanks a lot for adding some docs to my barebone initial implementation
> and fixing the stuff I got wrong along the way. :D
> 
> > +#![allow(dead_code)]
> > +
> > +use core::{
> > +    mem::take,
> > +    ops::{
> > +        Deref,
> > +        DerefMut, //
> > +    }, //
> > +};
> > +
> > +use kernel::{
> > +    prelude::*,
> > +    sync::LockedBy, //
> > +};
> > +
> 
> Don't know what the doc rules are in rust, but for this sort of generic
> layer, maybe we should provide extensive docs around objects, fields
> and public functions. I see that most struct fields are documented, but
> not the struct themselves. the enum doesn't seem to be documented, and
> some of the public functions are not either. And that's all my fault,
> because I gave you this raw piece of code without much doc (you added a
> lot already). Just saying that, maybe now that things have settled
> down, is a good time to add proper doc where it's missing.

Ok i've got extensive documentation for this now and the whole rest of this
series and will include it in v2.

> 
> 
> /// 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 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 to is issued. 
> > +#[derive(Default)]
> > +pub(crate) enum Seat {
> > +    #[expect(clippy::enum_variant_names)]
>        /// Resource is not resident.
>        ///
>        /// All objects start with a seat in that 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 an `Seat::Idle` seat and the slot managers finds out this
>        /// object has been evicted from the slot.
> > +    #[default]
> > +    NoSeat,
> 
>        /// Resource is actively used and resident.
>        ///
>        /// When a seat is in that state, it can't be evicted, and the
>        /// slot pointed by `SlotInfo::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 that 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> {
> > +        match self {
> > +            Self::Active(info) => Some(info.slot),
> > +            _ => None,
> > +        }
> > +    }
> > +}
> > +
> 
> /// Trait describing the slot-related operations.
> > +pub(crate) trait SlotOperations {
> > +    type SlotData;
> > +
> > +    /// Called when a slot is being activated for a seat.
> > +    ///
> > +    /// This callback allows hardware-specific actions to be performed 
> > when a slot
> > +    /// becomes active, such as updating hardware registers or 
> > invalidating caches.
> > +    fn activate(&mut self, _slot_idx: usize, _slot_data: &Self::SlotData) 
> > -> Result {
> > +        Ok(())
> > +    }
> > +
> > +    /// Called when a slot is being evicted and freed.
> > +    ///
> > +    /// This callback allows hardware-specific cleanup when a slot is being
> > +    /// completely freed, either explicitly or when an idle slot is being
> > +    /// reused for a different seat. Any hardware state should be 
> > invalidated.
> > +    fn evict(&mut self, _slot_idx: usize, _slot_data: &Self::SlotData) -> 
> > Result {
> > +        Ok(())
> > +    }
> > +}
> > +
> 
> /// Data attached 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 Slot<T> {
>     /// Slot is free.
>     ///
>     /// All slots start in this state when the slot manager is created.
> > +    #[default]
> > +    Free,
> 
>     /// Slot is active.
>     ///
>     /// When is that state, the slot is guaranteed to stay active
>     /// for as long as the resource bound to it has its seat in the
>     /// `Seat::Active` state. No new resource can be bound to it.
> > +    Active(SlotInfo<T>),
> 
>     /// Slot is idle.
>     ///
>     /// Happens when the underlying resource has been flagged
>     /// `Seat::Idle`. When in that state, the slot manager is allowed
>     /// to evict the resource and re-assign the slot to someone else.
>     /// This process involves updating the `SlotInfo::seqno` which
>     /// will be checked against the `SeatInfo::seqno` in case the idle
>     /// resource wants to become active again.
> > +    Idle(SlotInfo<T>),
> > +}
> > +
> 
> /// Generic slot manager object.
> ///
> /// It abstracts away all the churn around activeness/idleness tracking
> /// and let the implementer of the SlotOperations trait focus on how to
> /// make a resource active or evict it.
> > +pub(crate) struct SlotManager<T: SlotOperations, const MAX_SLOTS: usize> {
> > +    /// Manager specific data
> > +    manager: T,
> > +
> > +    /// Number of slots actually available
> > +    slot_count: usize,
> > +
> > +    /// Slots
> > +    slots: [Slot<T::SlotData>; MAX_SLOTS],
> > +
> > +    /// Sequence number incremented each time a Seat is successfully 
> > activated
> > +    use_seqno: u64,
> > +}
> > +
> > +// A `Seat` protected by the same lock that is used to wrap the 
> > `SlotManager`.
> 
> Should this be
> 
> /// A `Seat` ....
> 
> ?

I'll change it over to a document comment since it's an important point.

> 
> > +type LockedSeat<T, const MAX_SLOTS: usize> = LockedBy<Seat, SlotManager<T, 
> > MAX_SLOTS>>;
> > +
> > +impl<T: SlotOperations, const MAX_SLOTS: usize> Unpin for SlotManager<T, 
> > MAX_SLOTS> {}
> 
> Do we really need to explicitly flag this type Unpin? I thought this
> was the default if the struct is not pinned (and it's not AFAICT).
> 

At some point we needed this to expressly impl Unpin, but now we don't,
so I've removed it in v2 and will otherwise rely on the default
behaviour for Unpin.

> > +
> > +impl<T: SlotOperations, const MAX_SLOTS: usize> SlotManager<T, MAX_SLOTS> {
>     /// Creates a new 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);
> > +        }
> > +        Ok(Self {
> > +            manager,
> > +            slot_count,
> > +            slots: [const { Slot::Free }; MAX_SLOTS],
> > +            use_seqno: 1,
> > +        })
> > +    }
> > +
> > +    fn record_active_slot(
> > +        &mut self,
> > +        slot_idx: usize,
> > +        locked_seat: &LockedSeat<T, MAX_SLOTS>,
> > +        slot_data: T::SlotData,
> > +    ) -> Result {
> > +        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] = Slot::Active(SlotInfo {
> > +            slot_data,
> > +            seqno: cur_seqno,
> > +        });
> > +
> > +        self.use_seqno += 1;
> > +        Ok(())
> > +    }
> > +
> > +    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)
> > +    }
> > +
> > +    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 {
> > +                Slot::Free => {
> > +                    return self.activate_slot(slot_idx, locked_seat, 
> > 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)?;
> > +                }
> > +                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)
> > +            }
> > +        }
> > +    }
> > +
> > +    fn idle_slot(&mut self, slot_idx: usize, locked_seat: &LockedSeat<T, 
> > MAX_SLOTS>) -> Result {
> > +        let slot = take(&mut self.slots[slot_idx]);
> > +
> > +        if let Slot::Active(slot_info) = slot {
> > +            self.slots[slot_idx] = Slot::Idle(SlotInfo {
> > +                slot_data: slot_info.slot_data,
> > +                seqno: slot_info.seqno,
> > +            })
> > +        };
> > +
> > +        *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(())
> > +    }
> > +
> > +    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) => {
> > +                self.manager.evict(slot_idx, &slot_info.slot_data)?;
> > +                take(&mut self.slots[slot_idx]);
> > +            }
> > +            _ => (),
> > +        }
> > +
> > +        *locked_seat.access_mut(self) = Seat::NoSeat;
> > +        Ok(())
> > +    }
> > +
> > +    // Checks and updates the seat state based on the slot it points to
> > +    // (if any). Returns a Seat with a value matching the slot state.
> > +    fn check_seat(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> 
> > Seat {
> > +        let new_seat = match locked_seat.access(self) {
> > +            Seat::Active(seat_info) => {
> > +                let old_slot_idx = seat_info.slot as usize;
> > +                let slot = &self.slots[old_slot_idx];
> > +
> > +                if kernel::warn_on!(
> > +                    !matches!(slot, Slot::Active(slot_info) if 
> > slot_info.seqno == seat_info.seqno)
> > +                ) {
> > +                    Seat::NoSeat
> > +                } else {
> > +                    Seat::Active(SeatInfo {
> > +                        slot: seat_info.slot,
> > +                        seqno: seat_info.seqno,
> > +                    })
> > +                }
> > +            }
> > +
> > +            Seat::Idle(seat_info) => {
> > +                let old_slot_idx = seat_info.slot as usize;
> > +                let slot = &self.slots[old_slot_idx];
> > +
> > +                if !matches!(slot, Slot::Idle(slot_info) if 
> > slot_info.seqno == seat_info.seqno) {
> > +                    Seat::NoSeat
> > +                } else {
> > +                    Seat::Idle(SeatInfo {
> > +                        slot: seat_info.slot,
> > +                        seqno: seat_info.seqno,
> > +                    })
> > +                }
> > +            }
> > +
> > +            _ => Seat::NoSeat,
> > +        };
> > +
> > +        // FIXME: Annoying manual copy. The original idea was to not add 
> > Copy+Clone to SeatInfo,
> > +        // so that only slot.rs can change the seat state, but there might 
> > be better solutions
> > +        // to prevent that.
> 
> Okay, I guess we want some inputs from Daniel and/or Alice on that one.
> 
> > +        match &new_seat {
> > +            Seat::Active(seat_info) => {
> > +                *locked_seat.access_mut(self) = Seat::Active(SeatInfo {
> > +                    slot: seat_info.slot,
> > +                    seqno: seat_info.seqno,
> > +                })
> > +            }
> > +            Seat::Idle(seat_info) => {
> > +                *locked_seat.access_mut(self) = Seat::Idle(SeatInfo {
> > +                    slot: seat_info.slot,
> > +                    seqno: seat_info.seqno,
> > +                })
> > +            }
> > +            _ => *locked_seat.access_mut(self) = Seat::NoSeat,
> > +        }
> > +
> > +        new_seat
> > +    }
> > +
> 
>     /// Make a resource active on any available/reclaimable slot.
>     ///
>     /// Will return an error if no slot is available/reclaimable, or if
>     /// the reclaim failed.
> > +    pub(crate) fn activate(
> > +        &mut self,
> > +        locked_seat: &LockedSeat<T, MAX_SLOTS>,
> > +        slot_data: T::SlotData,
> > +    ) -> Result {
> > +        let seat = self.check_seat(locked_seat);
> > +        match seat {
> > +            Seat::Active(seat_info) | Seat::Idle(seat_info) => {
> > +                // With lazy eviction, if seqno matches, the hardware 
> > state is still
> > +                // valid for both Active and Idle slots, so just update 
> > our records
> > +                self.record_active_slot(seat_info.slot as usize, 
> > locked_seat, slot_data)
> > +            }
> > +            _ => self.allocate_slot(locked_seat, slot_data),
> > +        }
> > +    }
> > +
>     /// Flag a resource idle.
>     ///
>     /// The slot manager can decide to reclaim the slot this resource
>     /// was bound to at any point after function returns.
> > +    // The idle() method will be used when we start adding support for 
> > user VMs
> > +    #[expect(dead_code)]
> > +    pub(crate) fn idle(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) 
> > -> Result {
> > +        let seat = self.check_seat(locked_seat);
> > +        if let Seat::Active(seat_info) = seat {
> > +            self.idle_slot(seat_info.slot as usize, locked_seat)?;
> > +        }
> > +        Ok(())
> > +    }
> > +
> > +    /// Evict a seat from its slot, freeing up the hardware resource.
> 
> I think I'd go:
> 
>     /// Evict a resource from its slot, and make this slot free again
>     /// for other users.
> 
> > +    pub(crate) fn evict(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) 
> > -> Result {
> > +        let seat = self.check_seat(locked_seat);
> > +
> > +        match seat {
> > +            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 6eaa2135fe07..f54b997355e0 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: TyrPlatformDeviceData,
> 

Reply via email to