summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/tyr/slot.rs
blob: d194ead53f7105f92f2aad7cafb471800245f4bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
// 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

use core::{
    mem,
    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(crate) fn slot(&self) -> Option<u8> {
        match self {
            Self::Active(info) => Some(info.slot),
            _ => None,
        }
    }
}

/// Information related to a slot.
struct SlotInfo<D> {
    /// Type specific data attached to a slot.
    slot_data: D,

    /// Sequence number from when this slot was last activated.
    seqno: u64,
}

/// Slot state.
#[derive(Default)]
enum Slot<D> {
    /// Slot is free.
    #[default]
    Free,

    /// Slot is active.
    Active(SlotInfo<D>),

    /// Slot is idle.
    Idle(SlotInfo<D>),
}

pub(crate) type LockedSeat<T, const MAX_SLOTS: usize> = LockedBy<Seat, SlotManager<T, MAX_SLOTS>>;

/// Trait describing the slot-related operations.
pub(crate) trait SlotOperations<const MAX_SLOTS: usize>: Sized {
    /// Implementation-specific data associated with each slot.
    type SlotData;

    /// Returns the seat belonging to this slot data.
    fn seat(slot_data: &Self::SlotData) -> &LockedSeat<Self, MAX_SLOTS>;

    /// 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<MAX_SLOTS>, 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: [Slot<T::SlotData>; MAX_SLOTS],

    /// Sequence number incremented each time a Seat is successfully activated
    use_seqno: u64,
}

impl<T: SlotOperations<MAX_SLOTS>, 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 {
            return Err(EINVAL);
        }
        if slot_count > MAX_SLOTS {
            return Err(EINVAL);
        }
        // Since the slot index is stored in SeatInfo as a u8, the maximum number of slots is 256.
        if slot_count > u8::MAX as usize + 1 {
            return Err(EINVAL);
        }

        Ok(Self {
            manager,
            slot_count,
            slots: [const { Slot::Free }; MAX_SLOTS],
            use_seqno: 1,
        })
    }

    /// Records a newly activated slot for the given seat.
    /// The slot manager takes ownership of the hardware-specific slot data.
    fn record_active_slot(&mut self, slot_idx: usize, slot_data: T::SlotData) {
        let cur_seqno = self.use_seqno;

        *T::seat(&slot_data).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;
    }

    /// Reactivates an active/idle slot for a given seat without reprogramming the hardware.
    /// The SlotManager reuses the existing slot_data. This ensures that the hardware-specific
    /// information is not changed between subsequent uses. It also ensures that resources
    /// owned by the existing slot_data remain alive while the hardware is configured to use them.
    fn reactivate_slot(&mut self, slot_idx: usize, slot_data: &T::SlotData) -> Result {
        let cur_seqno = self.use_seqno;

        let mut slot_info = match mem::take(&mut self.slots[slot_idx]) {
            Slot::Active(slot_info) | Slot::Idle(slot_info) => slot_info,
            Slot::Free => {
                *T::seat(slot_data).access_mut(self) = Seat::NoSeat;
                return Err(EINVAL);
            }
        };

        *T::seat(slot_data).access_mut(self) = Seat::Active(SeatInfo {
            slot: slot_idx as u8,
            seqno: cur_seqno,
        });

        slot_info.seqno = cur_seqno;
        self.slots[slot_idx] = Slot::Active(slot_info);

        self.use_seqno += 1;

        Ok(())
    }

    /// Activates a slot for the given seat.
    fn activate_slot(&mut self, slot_idx: usize, slot_data: T::SlotData) -> Result {
        self.manager.activate(slot_idx, &slot_data)?;
        self.record_active_slot(slot_idx, slot_data);
        Ok(())
    }

    /// 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, 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, 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)?;
                    mem::take(&mut self.slots[slot_idx]);
                }
                self.activate_slot(slot_idx, slot_data)
            }
            None => 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 = mem::take(&mut self.slots[slot_idx]);

        self.slots[slot_idx] = match slot {
            // If the slot was active, make it idle.
            Slot::Active(slot_info) => Slot::Idle(slot_info),

            // Preserve an already-idle slot.
            Slot::Idle(slot_info) => Slot::Idle(slot_info),

            // A free slot remains free.
            Slot::Free => Slot::Free,
        };

        // 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] {
            Slot::Active(slot_info) | Slot::Idle(slot_info) => {
                // If hardware eviction fails (e.g. times out), the slot retains
                // its SlotData so that any resources still referenced by the hardware
                // will remain alive. This prevents use-after-free errors.
                self.manager.evict(slot_idx, &slot_info.slot_data)?;
                mem::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],
                Slot::Active(slot_info) if slot_info.seqno == seat_seqno
            ))
        } else {
            matches!(
                &self.slots[slot_idx],
                Slot::Idle(slot_info) if slot_info.seqno == seat_seqno
            )
        };

        if !valid {
            *locked_seat.access_mut(self) = Seat::NoSeat;
        }
    }

    /// Activates a resource on any available/reclaimable slot.
    pub(crate) fn activate(&mut self, slot_data: T::SlotData) -> Result {
        self.check_seat(T::seat(&slot_data));

        // Copy out only the slot index so the borrow of slot_data ends here.
        let slot_idx = match T::seat(&slot_data).access(self) {
            Seat::Active(seat_info) | Seat::Idle(seat_info) => Some(seat_info.slot as usize),
            Seat::NoSeat => None,
        };

        match slot_idx {
            Some(slot_idx) => self.reactivate_slot(slot_idx, &slot_data),
            None => self.allocate_slot(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<MAX_SLOTS>, const MAX_SLOTS: usize> Deref for SlotManager<T, MAX_SLOTS> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.manager
    }
}

impl<T: SlotOperations<MAX_SLOTS>, const MAX_SLOTS: usize> DerefMut for SlotManager<T, MAX_SLOTS> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.manager
    }
}