xref: /linux/drivers/gpu/drm/tyr/mmu/address_space.rs (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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