xref: /linux/drivers/gpu/drm/tyr/slot.rs (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1 // SPDX-License-Identifier: GPL-2.0 or MIT
2 
3 //! Slot management abstraction for limited hardware resources.
4 //!
5 //! This module provides a generic [`SlotManager`] that assigns limited hardware
6 //! slots to logical "seats". A seat represents an entity (such as a virtual memory
7 //! (VM) address space) that needs access to a hardware slot.
8 //!
9 //! The [`SlotManager`] tracks slot allocation using sequence numbers (seqno) to detect
10 //! when a seat's binding has been invalidated. When a seat requests activation,
11 //! the manager will either reuse the seat's existing slot (if still valid),
12 //! allocate a free slot (if any are available), or evict the oldest idle slot if any
13 //! slots are idle.
14 //!
15 //! Hardware-specific behavior is customized by implementing the [`SlotOperations`]
16 //! trait, which allows callbacks when slots are activated or evicted.
17 //!
18 //! This is currently used for managing address space slots in the GPU, and it will
19 //! also be used to manage Command Stream Group (CSG) interface slots in the future.
20 //!
21 //! [SlotOperations]: crate::slot::SlotOperations
22 //! [SlotManager]: crate::slot::SlotManager
23 
24 use core::{
25     mem,
26     ops::{
27         Deref,
28         DerefMut, //
29     }, //
30 };
31 
32 use kernel::{
33     prelude::*,
34     sync::LockedBy, //
35 };
36 
37 /// Seat information.
38 ///
39 /// This can't be accessed directly by the element embedding a `Seat`,
40 /// but is used by the generic slot manager logic to control residency
41 /// of a certain object on a hardware slot.
42 pub(crate) struct SeatInfo {
43     /// Slot used by this seat.
44     ///
45     /// This index is only valid if the slot pointed to by this index
46     /// has its `SlotInfo::seqno` match `SeatInfo::seqno`. Otherwise,
47     /// it means the object has been evicted from the hardware slot,
48     /// and a new slot needs to be acquired to make this object
49     /// resident again.
50     slot: u8,
51 
52     /// Sequence number encoding the last time this seat was active.
53     /// We also use it to check if a slot is still bound to a seat.
54     seqno: u64,
55 }
56 
57 /// Seat state.
58 ///
59 /// This is meant to be embedded in the object that wants to acquire
60 /// hardware slots. It also starts in the `Seat::NoSeat` state, and
61 /// the slot manager will change the object value when an active/evict
62 /// request is issued.
63 #[derive(Default)]
64 pub(crate) enum Seat {
65     #[expect(clippy::enum_variant_names)]
66     /// Resource is not resident.
67     ///
68     /// All objects start with a seat in the `Seat::NoSeat` state. The seat also
69     /// gets back to that state if the user requests eviction. It
70     /// can also end up in that state next time an operation is done
71     /// on a `Seat::Idle` seat and the slot manager finds out this
72     /// object has been evicted from the slot.
73     #[default]
74     NoSeat,
75 
76     /// Resource is actively used and resident.
77     ///
78     /// When a seat is in the `Seat::Active` state, it can't be evicted, and the
79     /// slot pointed to by `SeatInfo::slot` is guaranteed to be reserved
80     /// for this object as long as the seat stays active.
81     Active(SeatInfo),
82 
83     /// Resource is idle and might or might not be resident.
84     ///
85     /// When a seat is in the`Seat::Idle` state, we can't know for sure if the
86     /// object is resident or evicted until the next request we issue
87     /// to the slot manager. This tells the slot manager it can
88     /// reclaim the underlying slot if needed.
89     /// In order for the hardware to use this object again, the seat
90     /// needs to be turned into an `Seat::Active` state again
91     /// with a `SlotManager::activate()` call.
92     Idle(SeatInfo),
93 }
94 
95 impl Seat {
96     /// Get the slot index this seat is pointing to.
97     ///
98     /// If the seat is not `Seat::Active` we can't trust the
99     /// `SeatInfo`. In that case `None` is returned, otherwise
100     /// `Some(SeatInfo::slot)` is returned.
101     pub(crate) fn slot(&self) -> Option<u8> {
102         match self {
103             Self::Active(info) => Some(info.slot),
104             _ => None,
105         }
106     }
107 }
108 
109 /// Information related to a slot.
110 struct SlotInfo<D> {
111     /// Type specific data attached to a slot.
112     slot_data: D,
113 
114     /// Sequence number from when this slot was last activated.
115     seqno: u64,
116 }
117 
118 /// Slot state.
119 #[derive(Default)]
120 enum Slot<D> {
121     /// Slot is free.
122     #[default]
123     Free,
124 
125     /// Slot is active.
126     Active(SlotInfo<D>),
127 
128     /// Slot is idle.
129     Idle(SlotInfo<D>),
130 }
131 
132 pub(crate) type LockedSeat<T, const MAX_SLOTS: usize> = LockedBy<Seat, SlotManager<T, MAX_SLOTS>>;
133 
134 /// Trait describing the slot-related operations.
135 pub(crate) trait SlotOperations<const MAX_SLOTS: usize>: Sized {
136     /// Implementation-specific data associated with each slot.
137     type SlotData;
138 
139     /// Returns the seat belonging to this slot data.
140     fn seat(slot_data: &Self::SlotData) -> &LockedSeat<Self, MAX_SLOTS>;
141 
142     /// Called when a slot is being activated for a seat.
143     fn activate(&mut self, _slot_idx: usize, _slot_data: &Self::SlotData) -> Result {
144         Ok(())
145     }
146 
147     /// Called when a slot is being evicted and freed.
148     fn evict(&mut self, _slot_idx: usize, _slot_data: &Self::SlotData) -> Result {
149         Ok(())
150     }
151 }
152 
153 /// A generic slot manager that provides access to a limited number of hardware slots.
154 pub(crate) struct SlotManager<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> {
155     /// A specific implementation of the generic slot manager.
156     manager: T,
157 
158     /// Number of slots actually available.
159     slot_count: usize,
160 
161     /// Slot array used to track the state of each slot.
162     slots: [Slot<T::SlotData>; MAX_SLOTS],
163 
164     /// Sequence number incremented each time a Seat is successfully activated
165     use_seqno: u64,
166 }
167 
168 impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> SlotManager<T, MAX_SLOTS> {
169     /// Creates a specific instance of a slot manager.
170     pub(crate) fn new(manager: T, slot_count: usize) -> Result<Self> {
171         if slot_count == 0 {
172             return Err(EINVAL);
173         }
174         if slot_count > MAX_SLOTS {
175             return Err(EINVAL);
176         }
177         // Since the slot index is stored in SeatInfo as a u8, the maximum number of slots is 256.
178         if slot_count > u8::MAX as usize + 1 {
179             return Err(EINVAL);
180         }
181 
182         Ok(Self {
183             manager,
184             slot_count,
185             slots: [const { Slot::Free }; MAX_SLOTS],
186             use_seqno: 1,
187         })
188     }
189 
190     /// Records a newly activated slot for the given seat.
191     /// The slot manager takes ownership of the hardware-specific slot data.
192     fn record_active_slot(&mut self, slot_idx: usize, slot_data: T::SlotData) {
193         let cur_seqno = self.use_seqno;
194 
195         *T::seat(&slot_data).access_mut(self) = Seat::Active(SeatInfo {
196             slot: slot_idx as u8,
197             seqno: cur_seqno,
198         });
199 
200         self.slots[slot_idx] = Slot::Active(SlotInfo {
201             slot_data,
202             seqno: cur_seqno,
203         });
204 
205         self.use_seqno += 1;
206     }
207 
208     /// Reactivates an active/idle slot for a given seat without reprogramming the hardware.
209     /// The SlotManager reuses the existing slot_data. This ensures that the hardware-specific
210     /// information is not changed between subsequent uses. It also ensures that resources
211     /// owned by the existing slot_data remain alive while the hardware is configured to use them.
212     fn reactivate_slot(&mut self, slot_idx: usize, slot_data: &T::SlotData) -> Result {
213         let cur_seqno = self.use_seqno;
214 
215         let mut slot_info = match mem::take(&mut self.slots[slot_idx]) {
216             Slot::Active(slot_info) | Slot::Idle(slot_info) => slot_info,
217             Slot::Free => {
218                 *T::seat(slot_data).access_mut(self) = Seat::NoSeat;
219                 return Err(EINVAL);
220             }
221         };
222 
223         *T::seat(slot_data).access_mut(self) = Seat::Active(SeatInfo {
224             slot: slot_idx as u8,
225             seqno: cur_seqno,
226         });
227 
228         slot_info.seqno = cur_seqno;
229         self.slots[slot_idx] = Slot::Active(slot_info);
230 
231         self.use_seqno += 1;
232 
233         Ok(())
234     }
235 
236     /// Activates a slot for the given seat.
237     fn activate_slot(&mut self, slot_idx: usize, slot_data: T::SlotData) -> Result {
238         self.manager.activate(slot_idx, &slot_data)?;
239         self.record_active_slot(slot_idx, slot_data);
240         Ok(())
241     }
242 
243     /// Finds a slot for the given seat. A free slot is preferred, but if none
244     /// are available, the oldest idle slot is evicted and reused. Otherwise, if
245     /// there are no free or idle slots, return [`EBUSY`].
246     fn allocate_slot(&mut self, slot_data: T::SlotData) -> Result {
247         let slots = &self.slots[..self.slot_count];
248 
249         let mut idle_slot_idx = None;
250         let mut idle_slot_seqno: u64 = 0;
251 
252         for (slot_idx, slot) in slots.iter().enumerate() {
253             match slot {
254                 Slot::Free => {
255                     return self.activate_slot(slot_idx, slot_data);
256                 }
257                 Slot::Idle(slot_info) => {
258                     if idle_slot_idx.is_none() || slot_info.seqno < idle_slot_seqno {
259                         idle_slot_idx = Some(slot_idx);
260                         idle_slot_seqno = slot_info.seqno;
261                     }
262                 }
263                 Slot::Active(_) => (),
264             }
265         }
266 
267         match idle_slot_idx {
268             Some(slot_idx) => {
269                 // Lazily evict idle slot just before it is reused.
270                 if let Slot::Idle(slot_info) = &self.slots[slot_idx] {
271                     self.manager.evict(slot_idx, &slot_info.slot_data)?;
272                     mem::take(&mut self.slots[slot_idx]);
273                 }
274                 self.activate_slot(slot_idx, slot_data)
275             }
276             None => Err(EBUSY),
277         }
278     }
279 
280     /// Converts an active slot and its seat to idle state.
281     fn idle_slot(&mut self, slot_idx: usize, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
282         let slot = mem::take(&mut self.slots[slot_idx]);
283 
284         self.slots[slot_idx] = match slot {
285             // If the slot was active, make it idle.
286             Slot::Active(slot_info) => Slot::Idle(slot_info),
287 
288             // Preserve an already-idle slot.
289             Slot::Idle(slot_info) => Slot::Idle(slot_info),
290 
291             // A free slot remains free.
292             Slot::Free => Slot::Free,
293         };
294 
295         // If the seat was active, make it idle, or keep it idle if it was already idle.
296         *locked_seat.access_mut(self) = match locked_seat.access(self) {
297             Seat::Active(seat_info) | Seat::Idle(seat_info) => Seat::Idle(SeatInfo {
298                 slot: seat_info.slot,
299                 seqno: seat_info.seqno,
300             }),
301             Seat::NoSeat => Seat::NoSeat,
302         };
303         Ok(())
304     }
305 
306     /// Evicts an active or idle slot: calls the eviction callback and marks the slot as free
307     /// and the seat as NoSeat.
308     fn evict_slot(&mut self, slot_idx: usize, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
309         match &self.slots[slot_idx] {
310             Slot::Active(slot_info) | Slot::Idle(slot_info) => {
311                 // If hardware eviction fails (e.g. times out), the slot retains
312                 // its SlotData so that any resources still referenced by the hardware
313                 // will remain alive. This prevents use-after-free errors.
314                 self.manager.evict(slot_idx, &slot_info.slot_data)?;
315                 mem::take(&mut self.slots[slot_idx]);
316             }
317             _ => (),
318         }
319 
320         *locked_seat.access_mut(self) = Seat::NoSeat;
321         Ok(())
322     }
323 
324     /// Checks that the seat state matches the slot's state.
325     /// If they don't match, the seat is stale and is reset to `NoSeat`.
326     fn check_seat(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) {
327         let (slot_idx, seat_seqno, is_active) = match locked_seat.access(self) {
328             Seat::Active(seat_info) => (seat_info.slot as usize, seat_info.seqno, true),
329             Seat::Idle(seat_info) => (seat_info.slot as usize, seat_info.seqno, false),
330             _ => return,
331         };
332 
333         let valid = if is_active {
334             !kernel::warn_on!(!matches!(
335                 &self.slots[slot_idx],
336                 Slot::Active(slot_info) if slot_info.seqno == seat_seqno
337             ))
338         } else {
339             matches!(
340                 &self.slots[slot_idx],
341                 Slot::Idle(slot_info) if slot_info.seqno == seat_seqno
342             )
343         };
344 
345         if !valid {
346             *locked_seat.access_mut(self) = Seat::NoSeat;
347         }
348     }
349 
350     /// Activates a resource on any available/reclaimable slot.
351     pub(crate) fn activate(&mut self, slot_data: T::SlotData) -> Result {
352         self.check_seat(T::seat(&slot_data));
353 
354         // Copy out only the slot index so the borrow of slot_data ends here.
355         let slot_idx = match T::seat(&slot_data).access(self) {
356             Seat::Active(seat_info) | Seat::Idle(seat_info) => Some(seat_info.slot as usize),
357             Seat::NoSeat => None,
358         };
359 
360         match slot_idx {
361             Some(slot_idx) => self.reactivate_slot(slot_idx, &slot_data),
362             None => self.allocate_slot(slot_data),
363         }
364     }
365 
366     /// Flag a resource as idle. This method will be used for user VM support.
367     #[expect(dead_code)]
368     pub(crate) fn idle(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
369         self.check_seat(locked_seat);
370         if let Seat::Active(seat_info) = locked_seat.access(self) {
371             self.idle_slot(seat_info.slot as usize, locked_seat)?;
372         }
373         Ok(())
374     }
375 
376     /// Evict a resource from its slot.
377     pub(crate) fn evict(&mut self, locked_seat: &LockedSeat<T, MAX_SLOTS>) -> Result {
378         self.check_seat(locked_seat);
379 
380         match locked_seat.access(self) {
381             Seat::Active(seat_info) | Seat::Idle(seat_info) => {
382                 let slot_idx = seat_info.slot as usize;
383                 self.evict_slot(slot_idx, locked_seat)?;
384             }
385             _ => (),
386         }
387 
388         Ok(())
389     }
390 }
391 
392 impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> Deref for SlotManager<T, MAX_SLOTS> {
393     type Target = T;
394 
395     fn deref(&self) -> &Self::Target {
396         &self.manager
397     }
398 }
399 
400 impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> DerefMut for SlotManager<T, MAX_SLOTS> {
401     fn deref_mut(&mut self) -> &mut Self::Target {
402         &mut self.manager
403     }
404 }
405