From: Daniel Almeida <daniel.almeida@collabora.com>
To: Deborah Brouwer <deborah.brouwer@collabora.com>
Cc: Alice Ryhl <aliceryhl@google.com>,
Danilo Krummrich <dakr@kernel.org>,
David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
Benno Lossin <lossin@kernel.org>, Gary Guo <gary@garyguo.net>,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
rust-for-linux@vger.kernel.org, boris.brezillon@collabora.com,
samitolvanen@google.com, acourbot@nvidia.com,
alvin.sun@linux.dev, laura.nao@collabora.com, work@onurozkan.dev,
beata.michalska@arm.com, steven.price@arm.com, lyude@redhat.com
Subject: Re: [PATCH v6 2/7] drm/tyr: add a generic slot manager
Date: Wed, 15 Jul 2026 20:17:02 -0300 [thread overview]
Message-ID: <41798711-8EFA-409B-9BA2-F718181AEB67@collabora.com> (raw)
In-Reply-To: <20260709-fw-boot-b4-v6-2-ca391e1a4108@collabora.com>
> On 9 Jul 2026, at 18:36, 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 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 <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 | 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
>
next prev parent reply other threads:[~2026-07-15 23:17 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-09 21:36 [PATCH v6 0/7] drm/tyr: firmware loading and MCU boot support Deborah Brouwer
2026-07-09 21:36 ` [PATCH v6 1/7] drm/tyr: add resources to RegistrationData Deborah Brouwer
2026-07-15 21:51 ` Daniel Almeida
2026-07-09 21:36 ` [PATCH v6 2/7] drm/tyr: add a generic slot manager Deborah Brouwer
2026-07-10 13:23 ` Alice Ryhl
2026-07-15 23:17 ` Daniel Almeida [this message]
2026-07-09 21:36 ` [PATCH v6 3/7] drm/tyr: add Memory Management Unit (MMU) support Deborah Brouwer
2026-07-10 13:45 ` Alice Ryhl
2026-07-16 23:24 ` Daniel Almeida
2026-07-09 21:36 ` [PATCH v6 4/7] drm/tyr: add GPU virtual memory (VM) support Deborah Brouwer
2026-07-10 14:15 ` Alice Ryhl
2026-07-14 3:12 ` Deborah Brouwer
2026-07-10 14:27 ` Alice Ryhl
2026-07-09 21:36 ` [PATCH v6 5/7] drm/tyr: add a kernel buffer object Deborah Brouwer
2026-07-09 21:36 ` [PATCH v6 6/7] drm/tyr: add parser for firmware binary Deborah Brouwer
2026-07-09 21:36 ` [PATCH v6 7/7] drm/tyr: add Microcontroller Unit (MCU) booting 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=41798711-8EFA-409B-9BA2-F718181AEB67@collabora.com \
--to=daniel.almeida@collabora.com \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=alvin.sun@linux.dev \
--cc=beata.michalska@arm.com \
--cc=boris.brezillon@collabora.com \
--cc=dakr@kernel.org \
--cc=deborah.brouwer@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=gary@garyguo.net \
--cc=laura.nao@collabora.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=lyude@redhat.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=samitolvanen@google.com \
--cc=simona@ffwll.ch \
--cc=steven.price@arm.com \
--cc=work@onurozkan.dev \
/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