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