1 // SPDX-License-Identifier: GPL-2.0 or MIT 2 3 //! Address space module. 4 //! 5 //! This module handles the hardware interaction for MMU operations through 6 //! MMIO register access. 7 //! 8 9 use core::ops::Range; 10 11 use kernel::{ 12 device::{ 13 Bound, 14 Device, // 15 }, // 16 error::Result, 17 io::{ 18 poll, 19 register::Array, 20 Io, // 21 }, 22 iommu::pgtable::{ 23 Config, 24 IoPageTable, 25 ARM64LPAES1, // 26 }, 27 num::Bounded, 28 prelude::*, 29 sizes::{ 30 SZ_2M, 31 SZ_4K, // 32 }, 33 sync::{ 34 Arc, 35 ArcBorrow, 36 LockedBy, // 37 }, 38 time::Delta, // 39 }; 40 41 use crate::{ 42 driver::IoMem, 43 mmu::{ 44 AsSlotManager, 45 Mmu, // 46 }, 47 regs::{ 48 mmu_control::mmu_as_control, 49 mmu_control::mmu_as_control::*, 50 MAX_AS, // 51 }, 52 slot::{ 53 LockedSeat, 54 Seat, 55 SlotOperations, // 56 }, // 57 }; 58 59 /// Address space configuration values to be written to MMU registers. 60 #[derive(Clone, Copy)] 61 struct AddressSpaceConfig { 62 /// Translation configuration. Configures how the MMU walks the page table for this 63 /// address space. 64 transcfg: u64, 65 66 /// Translation table base address. The address of the page table. 67 transtab: u64, 68 69 /// Memory attributes such as cacheability. 70 memattr: u64, 71 } 72 73 /// Virtual memory (VM) address space data for use in MMU operations. 74 #[pin_data] 75 pub(crate) struct VmAsData<'drm> { 76 /// This address-space seat tracks this VM's binding to a hardware address space slot. 77 /// It can only be accessed when holding the `Mmu::as_manager` lock. 78 as_seat: LockedSeat<AddressSpaceManager<'drm>, MAX_AS>, 79 80 /// Virtual address bits for this address space. 81 va_bits: u8, 82 83 /// The page table which maps GPU virtual addresses to physical addresses for this VM. 84 #[pin] 85 pub(crate) page_table: IoPageTable<'drm, ARM64LPAES1>, 86 } 87 88 impl<'drm> VmAsData<'drm> { 89 /// Creates VM address space data by initializing all of its fields. 90 pub(crate) fn new<'a>( 91 mmu: &'a Mmu<'drm>, 92 dev: &'drm Device<Bound>, 93 va_bits: u32, 94 pa_bits: u32, 95 ) -> impl pin_init::PinInit<VmAsData<'drm>, Error> + 'a { 96 let pt_config = Config { 97 quirks: 0, 98 pgsize_bitmap: SZ_4K | SZ_2M, 99 ias: va_bits, 100 oas: pa_bits, 101 coherent_walk: false, 102 }; 103 104 let page_table_init = IoPageTable::new(dev, pt_config); 105 106 try_pin_init!(Self { 107 as_seat: LockedBy::new(&mmu.as_manager, Seat::NoSeat), 108 va_bits: va_bits as u8, 109 page_table <- page_table_init, 110 }? Error) 111 } 112 113 /// Computes the hardware configuration for this address space. 114 fn as_config(&self) -> Result<AddressSpaceConfig> { 115 let pt = &self.page_table; 116 // The hardware computes the valid input address range as: 117 // INA_BITS_VALID = min(HW_INA_BITS, 55 - INA_BITS) 118 // To configure our desired va_bits, we solve for INA_BITS: 119 // INA_BITS = 55 - va_bits 120 // This assumes HW_INA_BITS (hardware capability) >= va_bits. 121 let field = 55u64.checked_sub(self.va_bits.into()).ok_or(EINVAL)?; 122 let ina_bits = 123 match mmu_as_control::InaBits::try_from(Bounded::try_new(field).ok_or(EINVAL)?)? { 124 mmu_as_control::InaBits::Reset => return Err(EINVAL), 125 bits => bits, 126 }; 127 128 let transcfg = mmu_as_control::TRANSCFG::zeroed() 129 .with_ptw_memattr(mmu_as_control::PtwMemattr::WriteBack) 130 .with_r_allocate(true) 131 .with_mode(mmu_as_control::AddressSpaceMode::Aarch64_4K) 132 .with_ina_bits(ina_bits) 133 .into_raw(); 134 135 Ok(AddressSpaceConfig { 136 transcfg, 137 // SAFETY: The SlotManager holds an `Arc<VmAsData>` as SlotData while this 138 // TTBR is programmed and stores that Arc in the active slot before 139 // returning. Eviction flushes and disables the slot before releasing 140 // the Arc; if eviction fails, the slot retains it. Therefore the page 141 // table cannot be dropped while the GPU is using it. 142 transtab: unsafe { pt.ttbr() }, 143 memattr: MEMATTR::from_mair(pt.mair()).into_raw(), 144 }) 145 } 146 } 147 148 /// Coordinates all hardware-level address space operations through MMIO register 149 /// operations including enabling, disabling, flushing, and updating address spaces. 150 pub(crate) struct AddressSpaceManager<'drm> { 151 /// Parent device used for logging. 152 dev: &'drm Device<Bound>, 153 154 /// Memory-mapped I/O region for GPU register access. 155 iomem: Arc<IoMem<'drm>>, 156 157 /// Bitmask of present address space slots from GPU_AS_PRESENT register. 158 as_present: u32, 159 } 160 161 impl<'drm> AddressSpaceManager<'drm> { 162 /// Creates a new address space manager. 163 /// 164 /// Initializes the manager with references to the platform device and 165 /// I/O memory region, along with the bitmask of available AS slots. 166 pub(super) fn new( 167 dev: &'drm Device<Bound>, 168 iomem: Arc<IoMem<'drm>>, 169 as_present: u32, 170 ) -> Result<AddressSpaceManager<'drm>> { 171 if as_present.trailing_ones() != as_present.count_ones() { 172 dev_err!( 173 dev, 174 "Sparse AS_PRESENT mask is unsupported: {:#x}", 175 as_present 176 ); 177 return Err(EINVAL); 178 } 179 Ok(Self { 180 dev, 181 iomem, 182 as_present, 183 }) 184 } 185 186 /// Validates that an AS slot number is within range and present in hardware. 187 /// 188 /// Checks that the slot index is less than [`MAX_AS`] and that 189 /// the corresponding bit is set in the `as_present` mask read from the GPU. 190 /// 191 /// Returns [`EINVAL`] if the slot is out of range or not present in hardware. 192 fn validate_as_slot(&self, as_nr: usize) -> Result { 193 if as_nr >= MAX_AS { 194 dev_err!( 195 self.dev, 196 "AS slot {} out of valid range (max {})", 197 as_nr, 198 MAX_AS 199 ); 200 return Err(EINVAL); 201 } 202 203 if (self.as_present & (1 << as_nr)) == 0 { 204 dev_err!( 205 self.dev, 206 "AS slot {} not present in hardware (AS_PRESENT={:#x})", 207 as_nr, 208 self.as_present 209 ); 210 return Err(EINVAL); 211 } 212 Ok(()) 213 } 214 215 /// Waits for an AS slot to become ready (not active). 216 /// 217 /// Returns an error if polling times out after 10ms or if register access fails. 218 fn as_wait_ready(&self, as_nr: usize) -> Result { 219 let io = &*self.iomem; 220 let op = || { 221 let status_reg = STATUS::try_at(as_nr).ok_or(EINVAL)?; 222 Ok(io.read(status_reg)) 223 }; 224 let cond = |status: &STATUS| -> bool { !status.active_ext() }; 225 poll::read_poll_timeout(op, cond, Delta::from_micros(50), Delta::from_millis(10))?; 226 227 Ok(()) 228 } 229 230 /// Sends a command to an AS slot. 231 /// 232 /// Returns an error if waiting for ready times out or if register write fails. 233 fn as_send_cmd(&mut self, as_nr: usize, cmd: MmuCommand) -> Result { 234 self.as_wait_ready(as_nr)?; 235 let io = &*self.iomem; 236 let command_reg = COMMAND::try_at(as_nr).ok_or(EINVAL)?; 237 io.write(command_reg, COMMAND::zeroed().with_command(cmd)); 238 Ok(()) 239 } 240 241 /// Sends a command to an AS slot and waits for completion. 242 /// 243 /// Returns an error if sending the command fails or if waiting for completion times out. 244 fn as_send_cmd_and_wait(&mut self, as_nr: usize, cmd: MmuCommand) -> Result { 245 self.as_send_cmd(as_nr, cmd)?; 246 self.as_wait_ready(as_nr)?; 247 Ok(()) 248 } 249 250 /// Enables an AS slot with the provided configuration. 251 /// 252 /// Returns an error if the slot is invalid or if register writes/commands fail. 253 fn as_enable(&mut self, as_nr: usize, as_config: &AddressSpaceConfig) -> Result { 254 self.validate_as_slot(as_nr)?; 255 256 let io = &*self.iomem; 257 258 let transtab = as_config.transtab; 259 io.write( 260 TRANSTAB_LO::try_at(as_nr).ok_or(EINVAL)?, 261 TRANSTAB_LO::from_raw(transtab as u32), 262 ); 263 io.write( 264 TRANSTAB_HI::try_at(as_nr).ok_or(EINVAL)?, 265 TRANSTAB_HI::from_raw((transtab >> 32) as u32), 266 ); 267 268 let transcfg = as_config.transcfg; 269 io.write( 270 TRANSCFG_LO::try_at(as_nr).ok_or(EINVAL)?, 271 TRANSCFG_LO::from_raw(transcfg as u32), 272 ); 273 io.write( 274 TRANSCFG_HI::try_at(as_nr).ok_or(EINVAL)?, 275 TRANSCFG_HI::from_raw((transcfg >> 32) as u32), 276 ); 277 278 let memattr = as_config.memattr; 279 io.write( 280 MEMATTR_LO::try_at(as_nr).ok_or(EINVAL)?, 281 MEMATTR_LO::from_raw(memattr as u32), 282 ); 283 io.write( 284 MEMATTR_HI::try_at(as_nr).ok_or(EINVAL)?, 285 MEMATTR_HI::from_raw((memattr >> 32) as u32), 286 ); 287 288 self.as_send_cmd_and_wait(as_nr, MmuCommand::Update)?; 289 290 Ok(()) 291 } 292 293 /// Disables an AS slot and clears its configuration. 294 /// 295 /// Returns an error if the slot is invalid or if register writes/commands fail. 296 fn as_disable(&mut self, as_nr: usize) -> Result { 297 self.validate_as_slot(as_nr)?; 298 299 // Flush AS before disabling 300 self.as_send_cmd_and_wait(as_nr, MmuCommand::FlushMem)?; 301 302 let io = &*self.iomem; 303 304 io.write( 305 TRANSTAB_LO::try_at(as_nr).ok_or(EINVAL)?, 306 TRANSTAB_LO::from_raw(0), 307 ); 308 io.write( 309 TRANSTAB_HI::try_at(as_nr).ok_or(EINVAL)?, 310 TRANSTAB_HI::from_raw(0), 311 ); 312 313 io.write( 314 MEMATTR_LO::try_at(as_nr).ok_or(EINVAL)?, 315 MEMATTR_LO::from_raw(0), 316 ); 317 io.write( 318 MEMATTR_HI::try_at(as_nr).ok_or(EINVAL)?, 319 MEMATTR_HI::from_raw(0), 320 ); 321 322 let transcfg = TRANSCFG::zeroed() 323 .with_mode(AddressSpaceMode::Unmapped) 324 .into_raw(); 325 326 io.write( 327 TRANSCFG_LO::try_at(as_nr).ok_or(EINVAL)?, 328 TRANSCFG_LO::from_raw(transcfg as u32), 329 ); 330 io.write( 331 TRANSCFG_HI::try_at(as_nr).ok_or(EINVAL)?, 332 TRANSCFG_HI::from_raw((transcfg >> 32) as u32), 333 ); 334 335 self.as_send_cmd_and_wait(as_nr, MmuCommand::Update)?; 336 337 Ok(()) 338 } 339 340 /// Locks a region of the translation tables for an atomic update. 341 /// 342 /// Programs the MMU [`LOCKADDR`] register for the given address space and issues 343 /// the lock command. The hardware rounds the requested range up to a 344 /// power-of-two region aligned to its size. 345 /// 346 /// Returns an error if the slot is invalid or if register writes/commands fail. 347 fn as_start_update(&mut self, as_nr: usize, region: &Range<u64>) -> Result { 348 self.validate_as_slot(as_nr)?; 349 350 // Avoid both an empty range and an inverted range. 351 if region.start >= region.end { 352 return Err(EINVAL); 353 } 354 355 // The lock operates on full 64-byte cache lines of translation table entries. 356 // Since each translation table entry (TTE) is 8 bytes, a cache line has 8 TTEs. 357 // Since each TTE maps one page, the minimum locked region size will be 8 pages. 358 // 359 // With 4KiB pages (Aarch64_4K mode), the minimum locked region is 32KiB. 360 let lock_region_min_size: u64 = 4096 * 8; 361 362 // Count the number of trailing zero bits (zeros at the right/least-significant 363 // end of the binary representation). For a power-of-two value, this equals the 364 // base-2 exponent (e.g., 32 KiB = 2^15 → 15). 365 let lock_region_min_size_log2 = lock_region_min_size.trailing_zeros() as u8; 366 367 // XOR the first and last addresses to identify which bits differ between them. 368 // The highest set bit in the result determines the exponent of the smallest 369 // power-of-two region that can contain both addresses. 370 // 371 // Example: 372 // addr_xor = 0x1000 ^ 0x2FFF = 0x3FFF 373 // highest set bit in 0x3FFF is bit 13 374 // minimum region size = 2^(13 + 1) = 16 KiB 375 let addr_xor = region.start ^ (region.end - 1); 376 let region_size_log2 = 64 - addr_xor.leading_zeros() as u8; 377 378 let lock_region_log2 = core::cmp::max(region_size_log2, lock_region_min_size_log2); 379 380 let lock_region_size = 1u64.checked_shl(lock_region_log2.into()).ok_or(EINVAL)?; 381 // Align the LOCKADDR base address down to the lock region size (1 << lock_region_log2). 382 // 383 // The MMU ignores the low lock_region_log2 bits of LOCKADDR base, so ensure 384 // they are cleared in software to avoid ambiguity. 385 // 386 // Example: 387 // lock_region_log2 = 14 (16 KiB) 388 // region.start = 0x1000 389 // lockaddr_base = 0x1000 & ~(0x3FFF) = 0x0000 390 let lockaddr_base = region.start & !(lock_region_size - 1); 391 392 // The LOCKADDR size field encodes the lock region size as log2(size) - 1, 393 // per the hardware definition. For example, a 32 KiB region is encoded as 14 394 // because log2(32 KiB) = 15. 395 let lockaddr_size = lock_region_log2 - 1; 396 397 let io = &*self.iomem; 398 399 // The LOCKADDR base field stores address bits 63:12, so remove the low 12 bits 400 // before passing this value to the register macro helper. 401 // These bits are guaranteed to be zero anyway because of the minimum 402 // size of the locked region. 403 let lockaddr_base_field = lockaddr_base >> 12; 404 let lockaddr_val = LOCKADDR::zeroed() 405 .try_with_size(lockaddr_size)? 406 .try_with_base(lockaddr_base_field)? 407 .into_raw(); 408 409 io.write( 410 LOCKADDR_LO::try_at(as_nr).ok_or(EINVAL)?, 411 LOCKADDR_LO::from_raw(lockaddr_val as u32), 412 ); 413 io.write( 414 LOCKADDR_HI::try_at(as_nr).ok_or(EINVAL)?, 415 LOCKADDR_HI::from_raw((lockaddr_val >> 32) as u32), 416 ); 417 418 self.as_send_cmd_and_wait(as_nr, MmuCommand::Lock) 419 } 420 421 /// Completes an atomic translation table update. 422 /// 423 /// Returns an error if the slot is invalid or if the flush command fails. 424 fn as_end_update(&mut self, as_nr: usize) -> Result { 425 self.validate_as_slot(as_nr)?; 426 self.as_send_cmd_and_wait(as_nr, MmuCommand::FlushPt)?; 427 Ok(()) 428 } 429 430 /// Flushes the translation table cache for an AS slot. 431 /// 432 /// Returns an error if the slot is invalid or if the flush command fails. 433 fn as_flush(&mut self, as_nr: usize) -> Result { 434 self.validate_as_slot(as_nr)?; 435 self.as_send_cmd_and_wait(as_nr, MmuCommand::FlushPt) 436 } 437 } 438 439 impl<'drm> SlotOperations<MAX_AS> for AddressSpaceManager<'drm> { 440 /// VM address space data associated with a hardware slot. 441 type SlotData = Arc<VmAsData<'drm>>; 442 443 fn seat(slot_data: &Self::SlotData) -> &LockedSeat<Self, MAX_AS> { 444 &slot_data.as_seat 445 } 446 447 /// Activates a VM in a hardware slot. 448 fn activate(&mut self, slot_idx: usize, slot_data: &Self::SlotData) -> Result { 449 let as_config = slot_data.as_config()?; 450 self.as_enable(slot_idx, &as_config) 451 } 452 453 /// Evicts a VM from a hardware slot. 454 fn evict(&mut self, slot_idx: usize, _slot_data: &Self::SlotData) -> Result { 455 self.as_flush(slot_idx)?; 456 self.as_disable(slot_idx)?; 457 Ok(()) 458 } 459 } 460 461 impl<'drm> AsSlotManager<'drm> { 462 /// Locks a region for translation table updates if the VM has an active slot. 463 pub(super) fn start_vm_update( 464 &mut self, 465 vm_as_data: &VmAsData<'drm>, 466 region: &Range<u64>, 467 ) -> Result { 468 let seat = vm_as_data.as_seat.access(self); 469 match seat.slot() { 470 Some(slot) => { 471 let as_nr = slot as usize; 472 self.as_start_update(as_nr, region) 473 } 474 _ => Ok(()), 475 } 476 } 477 478 /// Completes translation table updates and unlocks the region. 479 pub(super) fn end_vm_update(&mut self, vm_as_data: &VmAsData<'drm>) -> Result { 480 let seat = vm_as_data.as_seat.access(self); 481 match seat.slot() { 482 Some(slot) => { 483 let as_nr = slot as usize; 484 self.as_end_update(as_nr) 485 } 486 _ => Ok(()), 487 } 488 } 489 490 /// Flushes the translation table cache if the VM has an active slot. 491 pub(super) fn flush_vm(&mut self, vm_as_data: &VmAsData<'drm>) -> Result { 492 let seat = vm_as_data.as_seat.access(self); 493 match seat.slot() { 494 Some(slot) => { 495 let as_nr = slot as usize; 496 self.as_flush(as_nr) 497 } 498 _ => Ok(()), 499 } 500 } 501 502 /// Activates a VM by assigning it to a hardware slot. 503 pub(super) fn activate_vm(&mut self, vm_as_data: ArcBorrow<'_, VmAsData<'drm>>) -> Result { 504 self.activate(vm_as_data.into()) 505 } 506 507 /// Deactivates a VM by evicting it from its hardware slot. 508 pub(super) fn deactivate_vm(&mut self, vm_as_data: &VmAsData<'drm>) -> Result { 509 self.evict(&vm_as_data.as_seat) 510 } 511 } 512