From: Boris Brezillon <boris.brezillon@collabora.com>
To: Deborah Brouwer <deborah.brouwer@collabora.com>
Cc: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org,
daniel.almeida@collabora.com, aliceryhl@google.com,
beata.michalska@arm.com, lyude@redhat.com
Subject: Re: [PATCH 07/12] drm/tyr: Add generic slot manager
Date: Thu, 12 Feb 2026 11:11:13 +0100 [thread overview]
Message-ID: <20260212111113.68778819@fedora> (raw)
In-Reply-To: <20260212013713.304343-8-deborah.brouwer@collabora.com>
On Wed, 11 Feb 2026 17:37:08 -0800
Deborah Brouwer <deborah.brouwer@collabora.com> wrote:
> From: Boris Brezillon <boris.brezillon@collabora.com>
>
> 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 <boris.brezillon@collabora.com>
> Co-developed-by: Deborah Brouwer <deborah.brouwer@collabora.com>
> Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
> ---
> 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.
> +//!
> +//! [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.
/// 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` ....
?
> +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).
> +
> +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,
next prev parent reply other threads:[~2026-02-12 10:11 UTC|newest]
Thread overview: 63+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-12 1:37 [PATCH 0/12] drm/tyr: firmware loading and MCU boot support Deborah Brouwer
2026-02-12 1:37 ` [PATCH 01/12] drm/tyr: select DRM abstractions in Kconfig Deborah Brouwer
2026-02-12 1:37 ` [PATCH 02/12] drm/tyr: move clock cleanup into Clocks Drop impl Deborah Brouwer
2026-02-12 8:12 ` Boris Brezillon
2026-02-28 0:18 ` Deborah Brouwer
2026-02-20 14:03 ` Daniel Almeida
2026-02-21 9:01 ` Alice Ryhl
2026-02-12 1:37 ` [PATCH 03/12] drm/tyr: rename TyrObject to BoData Deborah Brouwer
2026-02-20 14:04 ` Daniel Almeida
2026-02-21 9:01 ` Alice Ryhl
2026-02-12 1:37 ` [PATCH 04/12] drm/tyr: set DMA mask using GPU physical address Deborah Brouwer
2026-02-12 10:16 ` Boris Brezillon
2026-02-20 14:19 ` Daniel Almeida
2026-02-21 9:03 ` Alice Ryhl
2026-02-12 1:37 ` [PATCH 05/12] drm/tyr: add MMU address space registers Deborah Brouwer
2026-02-12 8:16 ` Boris Brezillon
2026-02-28 0:12 ` Deborah Brouwer
2026-02-20 14:21 ` Daniel Almeida
2026-02-21 9:09 ` Alice Ryhl
2026-02-22 18:13 ` Boris Brezillon
2026-02-28 0:13 ` Deborah Brouwer
2026-02-12 1:37 ` [PATCH 06/12] drm/tyr: add shmem backing for GEM objects Deborah Brouwer
2026-02-12 8:17 ` Boris Brezillon
2026-02-28 0:15 ` Deborah Brouwer
2026-02-20 14:25 ` Daniel Almeida
2026-02-28 0:17 ` Deborah Brouwer
2026-03-02 10:17 ` Boris Brezillon
2026-03-02 17:03 ` Deborah Brouwer
2026-02-12 1:37 ` [PATCH 07/12] drm/tyr: Add generic slot manager Deborah Brouwer
2026-02-12 10:11 ` Boris Brezillon [this message]
2026-02-12 10:45 ` Miguel Ojeda
2026-02-20 15:25 ` Daniel Almeida
2026-02-20 16:21 ` Boris Brezillon
2026-02-20 16:55 ` Daniel Almeida
2026-02-22 17:57 ` Boris Brezillon
2026-02-22 18:46 ` Daniel Almeida
2026-02-28 0:28 ` Deborah Brouwer
2026-03-02 10:10 ` Boris Brezillon
2026-02-21 11:16 ` Alice Ryhl
2026-02-21 12:44 ` Daniel Almeida
2026-02-21 13:40 ` Alice Ryhl
2026-02-21 13:48 ` Daniel Almeida
2026-02-28 0:25 ` Deborah Brouwer
2026-02-12 1:37 ` [PATCH 08/12] drm/tyr: add MMU module Deborah Brouwer
2026-02-12 10:44 ` Boris Brezillon
2026-02-28 0:31 ` Deborah Brouwer
2026-02-12 11:05 ` Boris Brezillon
2026-02-20 15:41 ` Daniel Almeida
2026-02-21 11:17 ` Alice Ryhl
2026-02-20 17:11 ` Daniel Almeida
2026-02-28 0:46 ` Deborah Brouwer
2026-02-21 11:20 ` Alice Ryhl
2026-02-28 0:49 ` Deborah Brouwer
2026-02-12 1:37 ` [PATCH 09/12] drm/tyr: add GPU virtual memory module Deborah Brouwer
2026-02-12 10:54 ` Boris Brezillon
2026-02-28 0:52 ` Deborah Brouwer
2026-02-12 1:37 ` [PATCH 10/12] drm/tyr: add a kernel buffer object Deborah Brouwer
2026-02-12 11:00 ` Boris Brezillon
2026-02-28 1:01 ` Deborah Brouwer
2026-02-12 1:37 ` [PATCH 11/12] drm/tyr: add parser for firmware binary Deborah Brouwer
2026-02-12 1:37 ` [PATCH 12/12] drm/tyr: add firmware loading and MCU boot support Deborah Brouwer
2026-02-21 11:25 ` Alice Ryhl
2026-02-28 1:02 ` Deborah Brouwer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260212111113.68778819@fedora \
--to=boris.brezillon@collabora.com \
--cc=aliceryhl@google.com \
--cc=beata.michalska@arm.com \
--cc=daniel.almeida@collabora.com \
--cc=deborah.brouwer@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=lyude@redhat.com \
--cc=rust-for-linux@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox