Re: [PATCH v6 3/7] drm/tyr: add Memory Management Unit (MMU) support
Daniel Almeida <[email protected]>
| Newsgroups | org.kernel.vger.rust-for-linux,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
> On 9 Jul 2026, at 18:36, Deborah Brouwer <[email protected]> wrote: > > From: Boris Brezillon <[email protected]> > > Add Memory Management Unit (MMU) support in Tyr. The MMU module wraps a > SlotManager instance to allocate MMU address-space slots for use by > virtual memory (VM) address spaces. The MMU's SlotManager uses an > AddressSpaceManager to handle the hardware-specific callbacks. For > example, the AddressSpaceManager activates and evicts VMs from slots by > writing commands to the MMU registers. > > Add an implementation block for the MMU's MEMATTR register to provide > a method for translating the Memory Attribute Indirection Register (MAIR) > format from the pagetable configuration to a format understood by the MMU. > > Create an mmu instance during probe, it will be used by subsequent patches > in this series. > > Wrap the iomem stored in TyrDrmRegistrationData in an Arc. The iomem > is stored in the mmu through its AddressSpaceManager. In anticipation > of the iomem also being stored in the firmware object, set up shared > ownership of the iomem now. > > Update Kconfig to add the new MMU and IOMMU dependencies required > by this MMU module. > > Signed-off-by: Boris Brezillon <[email protected]> > Co-developed-by: Deborah Brouwer <[email protected]> > Signed-off-by: Deborah Brouwer <[email protected]> > --- > drivers/gpu/drm/tyr/Kconfig | 3 + > drivers/gpu/drm/tyr/driver.rs | 13 +- > drivers/gpu/drm/tyr/mmu.rs | 103 +++++++ > drivers/gpu/drm/tyr/mmu/address_space.rs | 484 +++++++++++++++++++++++++++++++ > drivers/gpu/drm/tyr/regs.rs | 121 ++++++++ > drivers/gpu/drm/tyr/tyr.rs | 1 + > 6 files changed, 722 insertions(+), 3 deletions(-) > > diff --git a/drivers/gpu/drm/tyr/Kconfig b/drivers/gpu/drm/tyr/Kconfig > index 51a68ef8212c..61a2fd6f961a 100644 > --- a/drivers/gpu/drm/tyr/Kconfig > +++ b/drivers/gpu/drm/tyr/Kconfig > @@ -5,9 +5,12 @@ config DRM_TYR > depends on DRM=y > depends on RUST > depends on ARM || ARM64 || COMPILE_TEST > + depends on MMU > depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_LPAE > depends on COMMON_CLK > + depends on IOMMU_SUPPORT > default n > + select IOMMU_IO_PGTABLE_LPAE > select RUST_DRM_GEM_SHMEM_HELPER > help > Rust DRM driver for ARM Mali CSF-based GPUs. > diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs > index 46ce5c41e310..74b55a389754 100644 > --- a/drivers/gpu/drm/tyr/driver.rs > +++ b/drivers/gpu/drm/tyr/driver.rs > @@ -28,7 +28,10 @@ > regulator, > regulator::Regulator, > sizes::SZ_2M, > - sync::Mutex, > + sync::{ > + Arc, > + Mutex, // > + }, > time, // > }; > > @@ -37,6 +40,7 @@ > gem::BoData, > gpu, > gpu::GpuInfo, > + mmu::Mmu, > regs::gpu_control::*, // > }; > > @@ -70,7 +74,7 @@ pub(crate) struct TyrDrmRegistrationData<'bound> { > regulators: Mutex<Regulators>, > > /// GPU MMIO register mapping. > - pub(crate) iomem: IoMem<'bound>, > + pub(crate) iomem: Arc<IoMem<'bound>>, > > /// GPU information read from hardware during probe. > pub(crate) gpu_info: GpuInfo, > @@ -121,7 +125,8 @@ fn probe<'bound>( > let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"sram")?; > > let request = pdev.io_request_by_index(0).ok_or(ENODEV)?; > - let iomem = request.iomap_sized::<SZ_2M>()?; > + > + let iomem = Arc::new(request.iomap_sized::<SZ_2M>()?, GFP_KERNEL)?; > > issue_soft_reset(pdev.as_ref(), &iomem)?; > gpu::l2_power_on(pdev.as_ref(), &iomem)?; > @@ -139,6 +144,8 @@ fn probe<'bound>( > > let unreg_dev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, Ok(()))?; > > + let _mmu = Mmu::new(iomem.as_arc_borrow(), &gpu_info)?; > + > let reg_data = try_pin_init!(TyrDrmRegistrationData { > pdev, > clks <- new_mutex!(Clocks { > diff --git a/drivers/gpu/drm/tyr/mmu.rs b/drivers/gpu/drm/tyr/mmu.rs > new file mode 100644 > index 000000000000..c0341557d730 > --- /dev/null > +++ b/drivers/gpu/drm/tyr/mmu.rs > @@ -0,0 +1,103 @@ > +// SPDX-License-Identifier: GPL-2.0 or MIT > + > +//! Memory Management Unit (MMU) module. > +//! > +//! The GPU MMU provides a limited number of memory address spaces for use by command streams. > +//! The MMU translates virtual addresses to physical addresses and manages memory configuration > +//! and access permissions. > +//! > +//! This MMU module is essentially a locked wrapper around a [`SlotManager`] instance. > +//! The [`SlotManager`] manages the assignment of virtual address spaces to hardware address-space > +//! (AS) slots. MMU commands such as updates and flushes are carried out by the > +//! [`AddressSpaceManager`] which actually writes to the MMU registers. > +#![allow(dead_code)] > + > +use core::ops::Range; > + > +use kernel::{ > + new_mutex, > + prelude::*, > + sync::{ > + Arc, > + ArcBorrow, > + Mutex, // > + }, // > +}; > + > +use crate::{ > + driver::IoMem, > + gpu::GpuInfo, > + mmu::address_space::{ > + AddressSpaceManager, > + VmAsData, // > + }, > + regs::{ > + gpu_control::AS_PRESENT, > + MAX_AS, // > + }, > + slot::SlotManager, // > +}; > + > +pub(crate) mod address_space; > + > +pub(crate) type AsSlotManager<'bound> = SlotManager<AddressSpaceManager<'bound>, MAX_AS>; > + > +/// Locked wrapper for carrying out virtual memory (VM) operations on the MMU. > +#[pin_data] > +pub(crate) struct Mmu<'bound> { > + /// Slot Manager instance used to allocate hardware slots and write to MMU registers. > + #[pin] > + pub(crate) as_manager: Mutex<AsSlotManager<'bound>>, > +} > + > +impl<'bound> Mmu<'bound> { > + /// Create an MMU component for this device. > + pub(crate) fn new( > + iomem: ArcBorrow<'_, IoMem<'bound>>, > + gpu_info: &GpuInfo, > + ) -> Result<Arc<Mmu<'bound>>> { > + let present = AS_PRESENT::from_raw(gpu_info.as_present).present().get(); > + let slot_count = present.count_ones().try_into()?; > + > + let as_manager = AddressSpaceManager::new(iomem.into(), present)?; > + let mmu_init = try_pin_init!(Self{ > + as_manager <- new_mutex!(SlotManager::new(as_manager, slot_count)?), > + }); > + Arc::pin_init(mmu_init, GFP_KERNEL) > + } > + > + /// Assign a VM to an AS slot, provide a translation table, > + /// and update the MMU to make the VM resident. > + pub(crate) fn activate_vm(&self, vm: ArcBorrow<'_, VmAsData<'bound>>) -> Result { > + self.as_manager.lock().activate_vm(vm) > + } > + > + /// Evict a VM from its AS slot and flush the MMU. > + pub(crate) fn deactivate_vm(&self, vm: &VmAsData<'bound>) -> Result { > + self.as_manager.lock().deactivate_vm(vm) > + } > + > + /// Flush MMU translation caches after a VM update. > + pub(crate) fn flush_vm(&self, vm: &VmAsData<'bound>) -> Result { > + self.as_manager.lock().flush_vm(vm) > + } > + > + /// Flags the start of a VM update. > + /// > + /// If the VM is resident, any GPU access on the memory range being > + /// updated will be blocked until `Mmu::end_vm_update()` is called. > + /// This guarantees the atomicity of a VM update. > + /// If the VM is not resident, this is a NOP. > + pub(crate) fn start_vm_update(&self, vm: &VmAsData<'bound>, region: &Range<u64>) -> Result { > + self.as_manager.lock().start_vm_update(vm, region) > + } > + > + /// Flags the end of a VM update. > + /// > + /// If the VM is resident, this will let GPU accesses on the updated > + /// range go through, in case any of them were blocked. > + /// If the VM is not resident, this is a NOP. > + pub(crate) fn end_vm_update(&self, vm: &VmAsData<'bound>) -> Result { > + self.as_manager.lock().end_vm_update(vm) > + } > +} > diff --git a/drivers/gpu/drm/tyr/mmu/address_space.rs b/drivers/gpu/drm/tyr/mmu/address_space.rs > new file mode 100644 > index 000000000000..a97f44774576 > --- /dev/null > +++ b/drivers/gpu/drm/tyr/mmu/address_space.rs > @@ -0,0 +1,484 @@ > +// SPDX-License-Identifier: GPL-2.0 or MIT > + > +//! Address space module. > +//! > +//! This module handles the hardware interaction for MMU operations through > +//! MMIO register access. > +//! > + > +use core::ops::Range; > + > +use kernel::{ > + device::{ > + Bound, > + Device, // > + }, // > + error::Result, > + io::{ > + poll, > + register::Array, > + Io, // > + }, > + iommu::pgtable::{ > + Config, > + IoPageTable, > + ARM64LPAES1, // > + }, > + prelude::*, > + sizes::{ > + SZ_2M, > + SZ_4K, // > + }, > + sync::{ > + Arc, > + ArcBorrow, > + LockedBy, // > + }, > + time::Delta, // > +}; > + > +use crate::{ > + driver::IoMem, > + mmu::{ > + AsSlotManager, > + Mmu, // > + }, > + regs::{ > + mmu_control::mmu_as_control, > + mmu_control::mmu_as_control::*, > + MAX_AS, // > + }, > + slot::{ > + Seat, > + SlotOperations, // > + }, // > +}; > + > +/// Address space configuration values to be written to MMU registers. > +#[derive(Clone, Copy)] > +struct AddressSpaceConfig { > + /// Translation configuration. Configures how the MMU walks the page table for this > + /// address space. > + transcfg: u64, > + > + /// Translation table base address. The address of the page table. > + transtab: u64, > + > + /// Memory attributes such as cacheability. > + memattr: u64, > +} > + > +/// Virtual memory (VM) address space data for use in MMU operations. > +#[pin_data] > +pub(crate) struct VmAsData<'bound> { > + /// The address space seat tracks this VM's binding to a hardware address space slot. > + /// Uses [`LockedBy`] to ensure safe concurrent access to the slot assignment state, > + /// protected by the [`AsSlotManager`] lock. > + as_seat: LockedBy<Seat, AsSlotManager<'bound>>, > + > + /// Virtual address bits for this address space. > + va_bits: u8, > + > + /// The page table which maps GPU virtual addresses to physical addresses for this VM. > + #[pin] > + pub(crate) page_table: IoPageTable<'bound, ARM64LPAES1>, > +} > + > +impl<'bound> VmAsData<'bound> { > + /// Creates VM address space data by initializing all of its fields. > + pub(crate) fn new<'a>( > + mmu: &'a Mmu<'bound>, > + pdev: &'bound Device<Bound>, > + va_bits: u32, > + pa_bits: u32, > + ) -> impl pin_init::PinInit<VmAsData<'bound>, Error> + 'a { > + let pt_config = Config { > + quirks: 0, > + pgsize_bitmap: SZ_4K | SZ_2M, > + ias: va_bits, > + oas: pa_bits, > + coherent_walk: false, > + }; > + > + let page_table_init = IoPageTable::new(pdev, pt_config); > + > + try_pin_init!(Self { > + as_seat: LockedBy::new(&mmu.as_manager, Seat::NoSeat), > + va_bits: va_bits as u8, > + page_table <- page_table_init, > + }? Error) > + } > + > + /// Computes the hardware configuration for this address space. > + fn as_config(&self) -> Result<AddressSpaceConfig> { > + let pt = &self.page_table; > + // The hardware computes the valid input address range as: > + // INA_BITS_VALID = min(HW_INA_BITS, 55 - INA_BITS) > + // To configure our desired va_bits, we solve for INA_BITS: > + // INA_BITS = 55 - va_bits > + // This assumes HW_INA_BITS (hardware capability) >= va_bits. > + let ina_bits_field_value = 55 - self.va_bits; > + let ina_bits = match ina_bits_field_value { > + 7 => mmu_as_control::InaBits::Bits48, > + 8 => mmu_as_control::InaBits::Bits47, > + 9 => mmu_as_control::InaBits::Bits46, > + 10 => mmu_as_control::InaBits::Bits45, > + 11 => mmu_as_control::InaBits::Bits44, > + 12 => mmu_as_control::InaBits::Bits43, > + 13 => mmu_as_control::InaBits::Bits42, > + 14 => mmu_as_control::InaBits::Bits41, > + 15 => mmu_as_control::InaBits::Bits40, > + 16 => mmu_as_control::InaBits::Bits39, > + 17 => mmu_as_control::InaBits::Bits38, > + 18 => mmu_as_control::InaBits::Bits37, > + 19 => mmu_as_control::InaBits::Bits36, > + 20 => mmu_as_control::InaBits::Bits35, > + 21 => mmu_as_control::InaBits::Bits34, > + 22 => mmu_as_control::InaBits::Bits33, > + 23 => mmu_as_control::InaBits::Bits32, > + 24 => mmu_as_control::InaBits::Bits31, > + 25 => mmu_as_control::InaBits::Bits30, > + 26 => mmu_as_control::InaBits::Bits29, > + 27 => mmu_as_control::InaBits::Bits28, > + 28 => mmu_as_control::InaBits::Bits27, > + 29 => mmu_as_control::InaBits::Bits26, > + 30 => mmu_as_control::InaBits::Bits25, > + _ => return Err(EINVAL), Would this work? let field = 55u64.checked_sub(self.va_bits.into()).ok_or(EINVAL)?; let ina_bits = match InaBits::try_from(Bounded::try_new(field).ok_or(EINVAL)?)? { InaBits::Reset => return Err(EINVAL), bits => bits, }; > + }; > + > + let transcfg = mmu_as_control::TRANSCFG::zeroed() > + .with_ptw_memattr(mmu_as_control::PtwMemattr::WriteBack) > + .with_r_allocate(true) > + .with_mode(mmu_as_control::AddressSpaceMode::Aarch64_4K) > + .with_ina_bits(ina_bits) > + .into_raw(); > + > + Ok(AddressSpaceConfig { > + transcfg, > + // SAFETY: The caller must ensure that the address space is evicted > + // and cleaned up before the `VmAsData` is dropped. > + transtab: unsafe { pt.ttbr() }, We need to say why this is safe instead ^ > + memattr: MEMATTR::from_mair(pt.mair()).into_raw(), > + }) > + } > +} > + > +/// Coordinates all hardware-level address space operations through MMIO register > +/// operations including enabling, disabling, flushing, and updating address spaces. > +pub(crate) struct AddressSpaceManager<'bound> { > + /// Memory-mapped I/O region for GPU register access. > + iomem: Arc<IoMem<'bound>>, > + > + /// Bitmask of present address space slots from GPU_AS_PRESENT register. > + as_present: u32, Throughout this whole patch, we treat AS’s as usize. I wonder if we would benefit from using Bounded instead? This is a mere question. > +} > + > +impl<'bound> AddressSpaceManager<'bound> { > + /// Creates a new address space manager. > + /// > + /// Initializes the manager with references to the platform device and > + /// I/O memory region, along with the bitmask of available AS slots. > + pub(super) fn new( > + iomem: Arc<IoMem<'bound>>, > + as_present: u32, > + ) -> Result<AddressSpaceManager<'bound>> { > + Ok(Self { iomem, as_present }) > + } > + > + /// Validates that an AS slot number is within range and present in hardware. > + /// > + /// Checks that the slot index is less than [`MAX_AS`] and that > + /// the corresponding bit is set in the `as_present` mask read from the GPU. > + /// > + /// Returns [`EINVAL`] if the slot is out of range or not present in hardware. > + fn validate_as_slot(&self, as_nr: usize) -> Result { > + if as_nr >= MAX_AS { > + pr_err!("AS slot {} out of valid range (max {})\n", as_nr, MAX_AS); > + return Err(EINVAL); ^ This comparison would come for free with Bounded > + } > + > + if (self.as_present & (1 << as_nr)) == 0 { ^ But not this one, as it’s a runtime check :/ > + pr_err!( > + "AS slot {} not present in hardware (AS_PRESENT={:#x})\n", > + as_nr, > + self.as_present > + ); > + return Err(EINVAL); > + } > + > + Ok(()) > + } > + > + /// Waits for an AS slot to become ready (not active). > + /// > + /// Returns an error if polling times out after 10ms or if register access fails. > + fn as_wait_ready(&self, as_nr: usize) -> Result { > + let io = &*self.iomem; > + let op = || { > + let status_reg = STATUS::try_at(as_nr).ok_or(EINVAL)?; > + Ok(io.read(status_reg)) > + }; > + let cond = |status: &STATUS| -> bool { !status.active_ext() }; > + poll::read_poll_timeout(op, cond, Delta::from_millis(0), Delta::from_millis(10))?; > + > + Ok(()) > + } > + > + /// Sends a command to an AS slot. > + /// > + /// Returns an error if waiting for ready times out or if register write fails. > + fn as_send_cmd(&mut self, as_nr: usize, cmd: MmuCommand) -> Result { > + self.as_wait_ready(as_nr)?; > + let io = &*self.iomem; > + let command_reg = COMMAND::try_at(as_nr).ok_or(EINVAL)?; > + io.write(command_reg, COMMAND::zeroed().with_command(cmd)); > + Ok(()) > + } > + > + /// Sends a command to an AS slot and waits for completion. > + /// > + /// Returns an error if sending the command fails or if waiting for completion times out. > + fn as_send_cmd_and_wait(&mut self, as_nr: usize, cmd: MmuCommand) -> Result { > + self.as_send_cmd(as_nr, cmd)?; > + self.as_wait_ready(as_nr)?; > + Ok(()) > + } > + > + /// Enables an AS slot with the provided configuration. > + /// > + /// Returns an error if the slot is invalid or if register writes/commands fail. > + fn as_enable(&mut self, as_nr: usize, as_config: &AddressSpaceConfig) -> Result { > + self.validate_as_slot(as_nr)?; > + > + let io = &*self.iomem; > + > + let transtab = as_config.transtab; > + io.write( > + TRANSTAB_LO::try_at(as_nr).ok_or(EINVAL)?, > + TRANSTAB_LO::from_raw(transtab as u32), > + ); > + io.write( > + TRANSTAB_HI::try_at(as_nr).ok_or(EINVAL)?, > + TRANSTAB_HI::from_raw((transtab >> 32) as u32), > + ); > + > + let transcfg = as_config.transcfg; > + io.write( > + TRANSCFG_LO::try_at(as_nr).ok_or(EINVAL)?, > + TRANSCFG_LO::from_raw(transcfg as u32), > + ); > + io.write( > + TRANSCFG_HI::try_at(as_nr).ok_or(EINVAL)?, > + TRANSCFG_HI::from_raw((transcfg >> 32) as u32), > + ); > + > + let memattr = as_config.memattr; > + io.write( > + MEMATTR_LO::try_at(as_nr).ok_or(EINVAL)?, > + MEMATTR_LO::from_raw(memattr as u32), > + ); > + io.write( > + MEMATTR_HI::try_at(as_nr).ok_or(EINVAL)?, > + MEMATTR_HI::from_raw((memattr >> 32) as u32), > + ); > + > + self.as_send_cmd_and_wait(as_nr, MmuCommand::Update)?; > + > + Ok(()) > + } > + > + /// Disables an AS slot and clears its configuration. > + /// > + /// Returns an error if the slot is invalid or if register writes/commands fail. > + fn as_disable(&mut self, as_nr: usize) -> Result { > + self.validate_as_slot(as_nr)?; > + > + // Flush AS before disabling > + self.as_send_cmd_and_wait(as_nr, MmuCommand::FlushMem)?; > + > + let io = &*self.iomem; > + > + io.write( > + TRANSTAB_LO::try_at(as_nr).ok_or(EINVAL)?, > + TRANSTAB_LO::from_raw(0), > + ); > + io.write( > + TRANSTAB_HI::try_at(as_nr).ok_or(EINVAL)?, > + TRANSTAB_HI::from_raw(0), > + ); > + > + io.write( > + MEMATTR_LO::try_at(as_nr).ok_or(EINVAL)?, > + MEMATTR_LO::from_raw(0), > + ); > + io.write( > + MEMATTR_HI::try_at(as_nr).ok_or(EINVAL)?, > + MEMATTR_HI::from_raw(0), > + ); > + > + let transcfg = TRANSCFG::zeroed() > + .with_mode(AddressSpaceMode::Unmapped) > + .into_raw(); > + > + io.write( > + TRANSCFG_LO::try_at(as_nr).ok_or(EINVAL)?, > + TRANSCFG_LO::from_raw(transcfg as u32), > + ); > + io.write( > + TRANSCFG_HI::try_at(as_nr).ok_or(EINVAL)?, > + TRANSCFG_HI::from_raw((transcfg >> 32) as u32), > + ); > + > + self.as_send_cmd_and_wait(as_nr, MmuCommand::Update)?; > + > + Ok(()) > + } > + > + /// Locks a region of the translation tables for an atomic update. > + /// > + /// Programs the MMU LOCKADDR register for the given address space and issues Can we link to this? > + /// the lock command. The hardware rounds the requested range up to a > + /// power-of-two region aligned to its size. > + /// > + /// Returns an error if the slot is invalid or if register writes/commands fail. > + fn as_start_update(&mut self, as_nr: usize, region: &Range<u64>) -> Result { > + self.validate_as_slot(as_nr)?; > + > + // The lock operates on full 64-byte cache lines of translation table entries. > + // Since each translation table entry (TTE) is 8 bytes, a cache line has 8 TTEs. > + // Since each TTE maps one page, the minimum locked region size will be 8 pages. > + // > + // With 4KiB pages (Aarch64_4K mode), the minimum locked region is 32KiB. > + let lock_region_min_size: u64 = 32 * 1024; > + > + // Count the number of trailing zero bits (zeros at the right/least-significant > + // end of the binary representation). For a power-of-two value, this equals the > + // base-2 exponent (e.g., 32 KiB = 2^15 → 15). > + let lock_region_min_size_log2 = lock_region_min_size.trailing_zeros() as u8; > + > + // XOR the first and last addresses to identify which bits differ between them. > + // The highest set bit in the result determines the exponent of the smallest > + // power-of-two region that can contain both addresses. > + // > + // Example: > + // addr_xor = 0x1000 ^ 0x2FFF = 0x3FFF > + // highest set bit in 0x3FFF is bit 13 > + // minimum region size = 2^(13 + 1) = 16 KiB > + let addr_xor = region.start ^ (region.end - 1); > + let region_size_log2 = 64 - addr_xor.leading_zeros() as u8; > + > + let lock_region_log2 = core::cmp::max(region_size_log2, lock_region_min_size_log2); > + > + // Align the LOCKADDR base address down to the lock region size (1 << lock_region_log2). > + // > + // The MMU ignores the low lock_region_log2 bits of LOCKADDR base, so ensure > + // they are cleared in software to avoid ambiguity. > + let lockaddr_base = region.start & !((1u64 << lock_region_log2) - 1); > + > + // The LOCKADDR size field encodes the lock region size as log2(size) - 1, > + // per the hardware definition. For example, a 32 KiB region is encoded as 14 > + // because log2(32 KiB) = 15. > + let lockaddr_size = lock_region_log2 - 1; > + > + let io = &*self.iomem; > + > + let lockaddr_val = LOCKADDR::zeroed() > + .try_with_size(lockaddr_size)? > + .try_with_base(lockaddr_base)? > + .into_raw(); > + > + io.write( > + LOCKADDR_LO::try_at(as_nr).ok_or(EINVAL)?, > + LOCKADDR_LO::from_raw(lockaddr_val as u32), > + ); > + io.write( > + LOCKADDR_HI::try_at(as_nr).ok_or(EINVAL)?, > + LOCKADDR_HI::from_raw((lockaddr_val >> 32) as u32), > + ); > + > + self.as_send_cmd(as_nr, MmuCommand::Lock) > + } > + > + /// Completes an atomic translation table update. > + /// > + /// Returns an error if the slot is invalid or if the flush command fails. > + fn as_end_update(&mut self, as_nr: usize) -> Result { > + self.validate_as_slot(as_nr)?; > + self.as_send_cmd_and_wait(as_nr, MmuCommand::FlushPt)?; > + Ok(()) > + } > + > + /// Flushes the translation table cache for an AS slot. > + /// > + /// Returns an error if the slot is invalid or if the flush command fails. > + fn as_flush(&mut self, as_nr: usize) -> Result { > + self.validate_as_slot(as_nr)?; > + self.as_send_cmd(as_nr, MmuCommand::FlushPt) > + } > +} > + > +impl<'bound> SlotOperations for AddressSpaceManager<'bound> { > + /// VM address space data associated with a hardware slot. > + type SlotData = Arc<VmAsData<'bound>>; > + > + /// Activates an address space in a hardware slot. > + fn activate(&mut self, slot_idx: usize, slot_data: &Self::SlotData) -> Result { > + let as_config = slot_data.as_config()?; > + self.as_enable(slot_idx, &as_config) > + } > + > + /// Evicts an address space from a hardware slot. > + fn evict(&mut self, slot_idx: usize, _slot_data: &Self::SlotData) -> Result { > + self.as_flush(slot_idx)?; > + self.as_disable(slot_idx)?; > + Ok(()) > + } > +} > + > +impl<'bound> AsSlotManager<'bound> { > + /// Locks a region for translation table updates if the VM has an active slot. > + pub(super) fn start_vm_update(&mut self, vm: &VmAsData<'bound>, region: &Range<u64>) -> Result { > + let seat = vm.as_seat.access(self); > + match seat.slot() { > + Some(slot) => { > + let as_nr = slot as usize; > + self.as_start_update(as_nr, region) > + } > + _ => Ok(()), > + } > + } > + > + /// Completes translation table updates and unlocks the region. > + pub(super) fn end_vm_update(&mut self, vm: &VmAsData<'bound>) -> Result { > + let seat = vm.as_seat.access(self); > + match seat.slot() { > + Some(slot) => { > + let as_nr = slot as usize; > + self.as_end_update(as_nr) > + } > + _ => Ok(()), > + } > + } > + > + /// Flushes the translation table cache if the VM has an active slot. > + pub(super) fn flush_vm(&mut self, vm: &VmAsData<'bound>) -> Result { > + let seat = vm.as_seat.access(self); > + match seat.slot() { > + Some(slot) => { > + let as_nr = slot as usize; > + self.as_flush(as_nr) > + } > + _ => Ok(()), > + } > + } > + > + /// Activates a VM by assigning it to a hardware slot. > + pub(super) fn activate_vm(&mut self, vm: ArcBorrow<'_, VmAsData<'bound>>) -> Result { > + self.activate(&vm.as_seat, vm.into()) > + } > + > + /// Deactivates a VM by evicting it from its hardware slot. > + pub(super) fn deactivate_vm(&mut self, vm: &VmAsData<'bound>) -> Result { > + self.evict(&vm.as_seat) > + } > +} > diff --git a/drivers/gpu/drm/tyr/regs.rs b/drivers/gpu/drm/tyr/regs.rs > index 831357a8ef87..c4f6b1596b31 100644 > --- a/drivers/gpu/drm/tyr/regs.rs > +++ b/drivers/gpu/drm/tyr/regs.rs > @@ -45,6 +45,8 @@ pub(crate) fn read_u64_no_tearing(lo_read: impl Fn() -> u32, hi_read: impl Fn() > } > } > > +pub(crate) use mmu_control::mmu_as_control::MAX_AS; > + > /// These registers correspond to the GPU_CONTROL register page. > /// They are involved in GPU configuration and control. > pub(crate) mod gpu_control { > @@ -965,6 +967,8 @@ pub(crate) mod mmu_as_control { > register, // > }; > > + use pin_init::Zeroable; > + > /// Maximum number of hardware address space slots. > /// The actual number of slots available is usually lower. > pub(crate) const MAX_AS: usize = 16; > @@ -1158,7 +1162,124 @@ fn from(val: MMU_MEMATTR_STAGE1) -> Self { > pub(crate) MEMATTR_HI(u32)[MAX_AS, stride = STRIDE] @ 0x240c { > 31:0 value; > } > + } > + > + impl MEMATTR { > + /// > + /// In the ARM Architecture Reference Manual, the MAIR encoding for Normal memory > + /// uses the format `0bxxRW` where: > + /// - `W` (bit 0) = Write-Allocate policy > + /// - `R` (bit 1) = Read-Allocate policy > + /// E.g., `0b0011` would allow both read and write allocation on a cache miss. > + /// > + /// ARM MAIR Write-Allocate bit (bit 0 of a cache policy nibble). > + const ARM_MAIR_WRITE_ALLOCATE: u8 = 0x1; > + /// ARM MAIR Read-Allocate bit (bit 1 of a cache policy nibble). > + const ARM_MAIR_READ_ALLOCATE: u8 = 0x2; > + /// ARM MAIR Write-back bit (bit 2 of a cache policy nibble). > + const ARM_MAIR_WRITE_BACK: u8 = 0x4; > + /// Mask for the inner cache policy nibble in MAIR attribute bytes. > + const ARM_MAIR_INNER_MASK: u8 = 0x0f; > + > + /// Check if a MAIR attribute byte represents device memory. > + /// > + /// Device memory (memory-mapped I/O, registers) cannot be cached and must > + /// be mapped as GPU `NonCacheable`. > + fn is_device_memory(mair_attr: u8) -> bool { > + // In AArch64 MAIR, device memory has bits [1:0] of outer nibble = 0. > + let outer = mair_attr >> 4; > + (outer & 0x3) == 0 > + } > + > + /// Check if normal memory is fully write-back cacheable. > + /// > + /// ARM MAIR has two cache policy levels (outer [7:4] and inner [3:0]). > + /// For memory to be truly write-back, BOTH levels must have the write-back bit set. > + /// If only one level is write-back, treat it as non-cacheable for GPU purposes. > + fn is_writeback_cacheable(mair_attr: u8) -> bool { > + let outer = mair_attr >> 4; > + let inner = mair_attr & Self::ARM_MAIR_INNER_MASK; > + > + (outer & Self::ARM_MAIR_WRITE_BACK) != 0 && (inner & Self::ARM_MAIR_WRITE_BACK) != 0 > + } > + > + // Helper to encode a MEMATTR attribute from its individual fields. > + fn encode_attribute( > + alloc_w: bool, > + alloc_r: bool, > + alloc_sel: AllocPolicySelect, > + coherency: Coherency, > + memory_type: MemoryType, > + ) -> MMU_MEMATTR_STAGE1 { > + MMU_MEMATTR_STAGE1::zeroed() > + .with_alloc_w(alloc_w) > + .with_alloc_r(alloc_r) > + .with_alloc_sel(alloc_sel) > + .with_coherency(coherency) > + .with_memory_type(memory_type) > + } > + > + /// Convert one MAIR attribute byte into a MEMATTR attribute. > + // TODO: Add a `coherent` parameter like panthor's mair_to_memattr(). > + // For now, assume a non-coherent system and always encode write-back > + // memory with MidgardInnerDomain coherency. > + fn attribute_from_mair(mair_attr: u8) -> MMU_MEMATTR_STAGE1 { > + // Device memory or non-write-back normal memory > + if Self::is_device_memory(mair_attr) || !Self::is_writeback_cacheable(mair_attr) { > + return Self::encode_attribute( > + false, > + false, > + AllocPolicySelect::Alloc, > + Coherency::MidgardInnerDomain, > + MemoryType::NonCacheable, > + ); > + } > + > + // Write-back cacheable normal memory > + let inner: u8 = mair_attr & Self::ARM_MAIR_INNER_MASK; > + Self::encode_attribute( > + (inner & Self::ARM_MAIR_WRITE_ALLOCATE) != 0, > + (inner & Self::ARM_MAIR_READ_ALLOCATE) != 0, > + AllocPolicySelect::Alloc, > + Coherency::MidgardInnerDomain, > + MemoryType::WriteBack, > + ) > + } > + > + /// Write one converted MAIR attribute into a corresponding MEMATTR slot. > + fn with_encoded_attribute(self, index: usize, attr: MMU_MEMATTR_STAGE1) -> Self { > + debug_assert!(index < 8); > + > + let shift = index * 8; > + let mask = !(0xffu64 << shift); > + let raw = (self.into_raw() & mask) | ((u64::from(attr.into_raw())) << shift); > + > + Self::from_raw(raw) > + } > > + /// Convert an AArch64 MAIR value into the GPU MEMATTR register encoding. > + /// > + /// Both MAIR and MEMATTR are 64-bit values with eight 8-bit memory > + /// attribute entries, but the bits do not map directly. The GPU MEMATTR encoding > + /// is less detailed than the MAIR encoding, so MAIR is converted to MEMATTR > + /// conservatively as follows: > + /// > + /// 1. Device memory, or Normal Memory that is not write-back cacheable, is encoded > + /// as GPU `NonCacheable` > + /// > + /// 2. Normal memory that is write-back cacheable is encoded as GPU `WriteBack`, > + /// and the inner allocation hints are preserved. > + pub(crate) fn from_mair(mair: u64) -> Self { > + mair.to_le_bytes() > + .into_iter() > + .enumerate() > + .fold(Self::zeroed(), |acc, (i, attr)| { > + acc.with_encoded_attribute(i, Self::attribute_from_mair(attr)) > + }) > + } > + } > + > + register! { > /// Lock region address for each address space. > pub(crate) LOCKADDR(u64)[MAX_AS, stride = STRIDE] @ 0x2410 { > /// Lock region size. > diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs > index 7c9a8063b3b9..79045d0135a8 100644 > --- a/drivers/gpu/drm/tyr/tyr.rs > +++ b/drivers/gpu/drm/tyr/tyr.rs > @@ -11,6 +11,7 @@ > mod file; > mod gem; > mod gpu; > +mod mmu; > mod regs; > mod slot; > > > -- > 2.54.0 > Reviewed-by: Daniel Almeida <[email protected]>