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