Files
linux/rust/kernel/iommu/pgtable.rs
Linus Torvalds 4e69c1856b Merge tag 'drm-next-2026-08-20' of https://gitlab.freedesktop.org/drm/kernel
Pull drm updates from Dave Airlie:
 "Highlights:

   - dmemcg eviction support is good for low VRAM things like Steam
     Machine

   - AMD adds gfx6-8 modifier support for older GPUs that enables a
     bunch of wayland stuff

   - i915/xe has some new hw support but also a lot of display
     refactoring

  Everything:

  perf:
   - export perf_allow_ APIs for xe

  udmabuf:
   - remove default size limit of 64MB

  rust:
   - i/o rework (signed tag from driver-core tree)
   - add registration guard and registration data
   - fix unbounded lifetimes in ioctl handler args
   - fix a drm_dev_register race
   - gem_shmem: add DmaResvGuard helper
   - gpuvm: require send/sync for driver data
       - implement send/sync for GpuVaAlloc and GpuVmBo
       - add SmContext lifetime
   - rename dma_handle to dma_address
   - change pci_sriov_get_totalvfs return to unsigned int

  core:
   - create drm_of_get_panel_orientation
   - send per-connector hotplug events
   - add thunderbolt UBHR tunneling support

  connector:
   - add color format property

  dmem:
   - introduce a peak file
   - accept one region per limit
   - add dmemcg support for eviction

  gpusvm:
   - reorg code to give drivers more flexibility

  atomic:
   - add create_state callback and helper
   - add documentation on atomic commit lifetime

  buddy:
   - add per-order free
   - add used block scoreboard
   - fix UAF
   - test buffer clearance on resume
   - add phys_addr->block helper

  gem:
   - drop DRIVER_GEM_GPUVA flag

  ttm:
   - be more aggressive allocating below protection limit

  sched:
   - add test suite for concurrent job submissions

  hdmi:
   - hook the color format property in helpers

  mipi-dsi:
   - add MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT

  bridge:
   - add atomic create callbacks
   - drop atomic reset
   - display-connector: don't autoenable HPD IRQ
   - trigger initial HPD for DP
   - ti-sn65dsi83: remove NO_HFP and NO_HBP mode flags
   - analogix_dp: switch to DP link training helpers

  dp:
   - add support for DSC max delta BPP

  edid:
   - parse panel type from DisplayID 2.x Display Parameters

  sysfb:
   - improve panel, stride, framebuffer size validation

  panel:
   - implement ref counting for struct drm_panel
   - himax-hx83121a: add backlight regulator support
   - novatek-nt36672a: Inline panel init sequences
   - visionox-vtdr6130: enable DSC
   - novatek-nt37801: Use mipi_dsi_*_multi() functions
   - samsung-s6d16d0: Fix prepare error handling
   - support Novatek NT36536 plus DT bindings
   - sofef00: fix backlight updates
   - osd101t2587: use mipi_dsi_*_multi interface
   - panel-edp: adjust timing for AUO displays
   - panel-lvds: support Opto Logic SCX1001511GGC49
   - panel-simple: support Kyocera tcg070wvlq
   - panel-edp: quirks
       - AUO B116XAT04.3, CMN N116BCP-EA2, CSW MNB601LS1-8
       - BOE NV116WH2-M30, BOE NT116WHM-N21, BOE NV116FH1-M31
       - BOE NV116FH1-M30, NV140FHM-N5B, TM156VDXP25
       - BOE NE160QDM-NY1, MB116AS01
   - new:
       - Samsung ATNA40HQ08-0, Anbernic TD4310
       - Chipone ICNA35XX, Ilitek ILI9488
       - Ilitek ILI7807S, Renesas R63419
       - MNE001BS6-2, MNF601BS4-1, Sharp LQ120P1JX51

  virtio:
   - add support for save/restore virtio_gpu_objects
   - abort vq wait on device removal

  amdgpu:
   - add color format DRM property
   - initial compute pipe reset support
   - add GFX 6-8 modifier support
   - initial DCN 6.0.0 support
   - dmemcg eviction support
   - improved boundary checking for bios parsing
   - RAS updates and rework
   - VCN secure submission fixes
   - 8K panel fix
   - Display KUNIT tests
   - parse panel type from DisplayID
   - Align IP discovery to pci device lifetime
   - SOC15 register macro cleanups
   - UVD memory placement fixes
   - GFX9 mode2 reset fixes
   - drop unnecessary BUG/BUG_ON
   - GFX8 soft reset rework
   - enable soft reset on GFX8
   - PSP/SMU 15.0.9 update
   - VI ASPM fix
   - userq fixes
   - amdgpu_vm_get_task_info_pasid lifetime fix
   - DC CACP support
   - change system_unbound_wq with system_dfl_wq
   - Loosen VFCT bios parsing to deal with pci=realloc
   - SI/SMU7 AC/DC switch fix
   - VM fence handling fix
   - GEM close optimisation
   - Apple Studio Display fixes
   - DC FRL fixes

  amdkfd:
   - initial compute pipe reset support
   - allow applications to opt out of sigbus on fatal errors
   - improve CRIU boundary checks
   - MQD handling rework
   - move TBA/TMA from system to device memory
   - avoid topology-lock in kfd_mmap
   - SVM eviction fixes

  radeon:
   - fix unset CONFIG_ACPI build

  i915:
   - Novalake (NVL display version 35) timing generator enabling
   - NVL DC3CO enabling
   - enable UBHR link rates on thunderbolt tunnels
   - Reduce Xe3+ PM demand peak bandwidth
   - enable pipe DMC error interrupts for display 30+
   - add kunit tests for DP link config selection
   - refactor and document DP link recovery
   - i915/xe driver display probe/remove/suspend/resume/shutdown cleanup
     and unification
   - i915/xe display runtime PM unified
   - Break i915 and xe panic dependency on struct intel_framebuffer
   - Streamline Pre/Post-CSC LUT loops
   - drop TGL DC3DO support
   - CDCLK santization
   - fix HDMI scrambling enable
   - fix phys bo pread/pwrite with offset
   - add missing nospec on parallel submit slot
   - fix some NULL derefs

  xe:
   - drop force_execlist module param
   - gate observation streams with perf_allow_cpu
   - skip FORCE_WC and vm_bound check for external dma-bufs
   - dmemcg eviction support
   - remove unused NVL-S GuC
   - TLB invalidation improvements
   - NVL-S updated PCI-IDs and w/a
   - madvise: optimise invalidation path
   - fix infinite gt-reset loop in timeout recovery
   - update TTM device benefical_order
   - wait on external BO kernel fences in exec ioctl
   - add/use more KLV helpers
   - sriov: disable display in admin only PF mode
   - add RAS GPU health indicator
   - optimise TTM populate for DONTNEED BO
   - drop force_probe for NVL-s
   - add debugfs for pcode info

  amdxdna:
   - disable device buffer export

  nova:
   - build nova-core/nova-drm from drivers/gpu
   - export nova-core rust symbols (workaround)
   - GSP boot process consolidation
   - Boot GSP with vGPU enabled
   - TLV firmware image format support
   - Hopper/Blackwell fixes and cleanups
   - I/O projection adoption

  tyr:
   - firmware loading and MCU boot
   - add generic slot manager + MMU
   - GPU VM support ARM64 LPAE page tables
   - add kernel buffer object for internal allocations
   - add parser for Mali CSF
   - add MCU booting

  nouveau:
   - race fixes
   - check instmem iomapping at first use
   - add dmemcg support
   - expose NVDEC channels
   - add scanline position/head state support for GSP

  qxl:
   - convert simple encoder to regular

  ethosu:
   - add perf counter support

  etnaviv:
   - force flush on power register ops

  msm:
   - support DSC configuration with slice_per_pkt > 1

  mxsfb:
   - fix disable sequence

  panthor:
   - support sparse mappings

  rockchip:
   - switch away from simple helpers
   - support YUV background color
   - fix layer config timeout
   - add edp support for rk3576
   - add batch command submission function

  rocket:
   - error handling and NULL ptr deref fixes

  sun4i:
   - switch away from simple helpers

  imagination:
   - mark BXM-4-64 MC1 as support

  host1x:
   - support tegra264

  tegra:
   - add DSI for tegra 20/30

  v3d:
   - reduce PM runtime autosuspend delay
   - scheduler fixes and refactoring
   - deprecate v3d 3.3 and 4.1
   - validate CPU job query boundaries

  hibmc:
   - improve plane format handling
   - switch to gem shmem

  mediatek:
   - cec: correct compat for mt7623-8167?

  exynos:
   - remove simple dependency
   - add error handling to encoder paths
   - take i2c adapter module reference"

* tag 'drm-next-2026-08-20' of https://gitlab.freedesktop.org/drm/kernel: (2074 commits)
  drm/xe/mcr: Take vcs1/vecs1 into account for first media slice
  drm/xe: Fix a bug in pc_adjust_freq_bounds()
  drm/xe: Fix xe_device_probe() failure
  drm/xe/drm_ras: Move has_drm_ras check to drm_ras layer
  drm/xe/ras: Fix boot-time ras error processing
  drm/amd/display: make DC_RUN_WITH_PREEMPTION_ENABLED misuse a build error
  drm/amd/pm: silence uninitialized variable warnings
  drm/amdgpu: skip BOs being torn down during GTT recovery
  drm/amdgpu: Reject UVD message with invalid number of h265 refs
  drm/amdgpu: keep PRT mappings off the vm_bo state lists
  drm/amdgpu: fix nbif 6.3.1 l1 low power not functional
  drm/amd/display: fix BT.2020 YCbCr output CSC matrices for DCE
  drm/amd/display: fix BT.2020 YCbCr limited output CSC matrix
  drm/amdgpu: Implement insert_end for VCE 3
  drm/amdgpu: Fix UVD min buffer sizes
  drm/amdgpu: Fix UVD decode image min size calculation
  drm/amdgpu: Fix UVD dpb min size calculation for H264
  drm/amdgpu: Reject UVD message with dimensions above 4096
  drm/amdgpu: check ASPM on the dGPU host link
  drm/radeon: fix autosuspend cleanup during teardown
  ...
2026-08-21 08:41:00 -07:00

265 lines
9.0 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
//! IOMMU page table management.
//!
//! C header: [`include/linux/io-pgtable.h`](srctree/include/linux/io-pgtable.h)
use core::{
marker::PhantomData,
ptr::NonNull, //
};
use crate::{
alloc,
bindings,
device::{
Bound,
Device, //
},
error::to_result,
io::PhysAddr,
prelude::*, //
};
use bindings::io_pgtable_fmt;
/// Protection flags used with IOMMU mappings.
pub mod prot {
/// Read access.
pub const READ: u32 = bindings::IOMMU_READ;
/// Write access.
pub const WRITE: u32 = bindings::IOMMU_WRITE;
/// Request cache coherency.
pub const CACHE: u32 = bindings::IOMMU_CACHE;
/// Request no-execute permission.
pub const NOEXEC: u32 = bindings::IOMMU_NOEXEC;
/// MMIO peripheral mapping.
pub const MMIO: u32 = bindings::IOMMU_MMIO;
/// Privileged mapping.
pub const PRIVILEGED: u32 = bindings::IOMMU_PRIV;
}
/// Represents a requested `io_pgtable` configuration.
pub struct Config {
/// Quirk bitmask (type-specific).
pub quirks: usize,
/// Valid page sizes, as a bitmask of powers of two.
pub pgsize_bitmap: usize,
/// Input address space size in bits.
pub ias: u32,
/// Output address space size in bits.
pub oas: u32,
/// IOMMU uses coherent accesses for page table walks.
pub coherent_walk: bool,
}
/// An io page table using a specific format.
///
/// # Invariants
///
/// The pointer references a valid io page table.
pub struct IoPageTable<'a, F: IoPageTableFmt> {
ptr: NonNull<bindings::io_pgtable_ops>,
_dev: PhantomData<&'a Device<Bound>>,
_marker: PhantomData<F>,
}
// SAFETY: `struct io_pgtable_ops` is not restricted to a single thread.
unsafe impl<F: IoPageTableFmt> Send for IoPageTable<'_, F> {}
// SAFETY: `struct io_pgtable_ops` may be accessed concurrently.
unsafe impl<F: IoPageTableFmt> Sync for IoPageTable<'_, F> {}
/// The format used by this page table.
pub trait IoPageTableFmt: 'static {
/// The value representing this format.
const FORMAT: io_pgtable_fmt;
}
impl<'a, F: IoPageTableFmt> IoPageTable<'a, F> {
/// Create a new `IoPageTable`.
#[inline]
pub fn new(dev: &'a Device<Bound>, config: Config) -> Result<IoPageTable<'a, F>> {
let mut raw_cfg = bindings::io_pgtable_cfg {
quirks: config.quirks,
pgsize_bitmap: config.pgsize_bitmap,
ias: config.ias,
oas: config.oas,
coherent_walk: config.coherent_walk,
tlb: &raw const NOOP_FLUSH_OPS,
iommu_dev: dev.as_raw(),
..Zeroable::zeroed()
};
// SAFETY:
// * The raw_cfg pointer is valid for the duration of this call.
// * The provided `FLUSH_OPS` contains valid function pointers that accept a null pointer
// as cookie.
// * The caller ensures that the io pgtable does not outlive the device.
let ops = unsafe {
bindings::alloc_io_pgtable_ops(F::FORMAT, &mut raw_cfg, core::ptr::null_mut())
};
// INVARIANT: We successfully created a valid page table.
Ok(IoPageTable {
ptr: NonNull::new(ops).ok_or(ENOMEM)?,
_dev: PhantomData,
_marker: PhantomData,
})
}
/// Obtain a raw pointer to the underlying `struct io_pgtable_ops`.
#[inline]
pub fn raw_ops(&self) -> *mut bindings::io_pgtable_ops {
self.ptr.as_ptr()
}
/// Obtain a raw pointer to the underlying `struct io_pgtable`.
#[inline]
pub fn raw_pgtable(&self) -> *mut bindings::io_pgtable {
// SAFETY: The io_pgtable_ops of an io-pgtable is always the ops field of a io_pgtable.
unsafe { kernel::container_of!(self.raw_ops(), bindings::io_pgtable, ops) }
}
/// Obtain a raw pointer to the underlying `struct io_pgtable_cfg`.
#[inline]
pub fn raw_cfg(&self) -> *mut bindings::io_pgtable_cfg {
// SAFETY: The `raw_pgtable()` method returns a valid pointer.
unsafe { &raw mut (*self.raw_pgtable()).cfg }
}
/// Map a physically contiguous range of pages of the same size.
///
/// Even if successful, this operation may not map the entire range. In that case, only a
/// prefix of the range is mapped, and the returned integer indicates its length in bytes. In
/// this case, the caller will usually call `map_pages` again for the remaining range.
///
/// The returned [`Result`] indicates whether an error was encountered while mapping pages.
/// Note that this may return a non-zero length even if an error was encountered. The caller
/// will usually [unmap the relevant pages](Self::unmap_pages) on error.
///
/// The caller must flush the TLB before using the pgtable to access the newly created mapping.
///
/// # Safety
///
/// * No other io-pgtable operation may access the range `iova .. iova+pgsize*pgcount` while
/// this `map_pages` operation executes.
/// * This page table must not contain any mapping that overlaps with the mapping created by
/// this call.
/// * If this page table is live, then the caller must ensure that it's okay to access the
/// physical address being mapped for the duration in which it is mapped.
#[inline]
pub unsafe fn map_pages(
&self,
iova: usize,
paddr: PhysAddr,
pgsize: usize,
pgcount: usize,
prot: u32,
flags: alloc::Flags,
) -> (usize, Result) {
let mut mapped: usize = 0;
// SAFETY: The `map_pages` function in `io_pgtable_ops` is never null.
let map_pages = unsafe { (*self.raw_ops()).map_pages.unwrap_unchecked() };
// SAFETY: The safety requirements of this method are sufficient to call `map_pages`.
let ret = to_result(unsafe {
(map_pages)(
self.raw_ops(),
iova,
paddr,
pgsize,
pgcount,
prot as i32,
flags.as_raw(),
&mut mapped,
)
});
(mapped, ret)
}
/// Unmap a range of virtually contiguous pages of the same size.
///
/// This may not unmap the entire range, and returns the length of the unmapped prefix in
/// bytes.
///
/// # Safety
///
/// * No other io-pgtable operation may access the range `iova .. iova+pgsize*pgcount` while
/// this `unmap_pages` operation executes.
/// * This page table must contain one or more consecutive mappings starting at `iova` whose
/// total size is `pgcount * pgsize`.
#[inline]
#[must_use]
pub unsafe fn unmap_pages(&self, iova: usize, pgsize: usize, pgcount: usize) -> usize {
// SAFETY: The `unmap_pages` function in `io_pgtable_ops` is never null.
let unmap_pages = unsafe { (*self.raw_ops()).unmap_pages.unwrap_unchecked() };
// SAFETY: The safety requirements of this method are sufficient to call `unmap_pages`.
unsafe { (unmap_pages)(self.raw_ops(), iova, pgsize, pgcount, core::ptr::null_mut()) }
}
}
// For the initial users of these rust bindings, the GPU FW is managing the IOTLB and performs all
// required invalidations using a range. There is no need for it get ARM style invalidation
// instructions from the page table code.
//
// Support for flushing the TLB with ARM style invalidation instructions may be added in the
// future.
static NOOP_FLUSH_OPS: bindings::iommu_flush_ops = bindings::iommu_flush_ops {
tlb_flush_all: Some(rust_tlb_flush_all_noop),
tlb_flush_walk: Some(rust_tlb_flush_walk_noop),
tlb_add_page: None,
};
#[no_mangle]
extern "C" fn rust_tlb_flush_all_noop(_cookie: *mut core::ffi::c_void) {}
#[no_mangle]
extern "C" fn rust_tlb_flush_walk_noop(
_iova: usize,
_size: usize,
_granule: usize,
_cookie: *mut core::ffi::c_void,
) {
}
impl<F: IoPageTableFmt> Drop for IoPageTable<'_, F> {
fn drop(&mut self) {
// SAFETY: The caller of `Self::ttbr()` promised that the page table is not live when this
// destructor runs.
unsafe { bindings::free_io_pgtable_ops(self.raw_ops()) };
}
}
/// The `ARM_64_LPAE_S1` page table format.
pub enum ARM64LPAES1 {}
impl IoPageTableFmt for ARM64LPAES1 {
const FORMAT: io_pgtable_fmt = bindings::io_pgtable_fmt_ARM_64_LPAE_S1 as io_pgtable_fmt;
}
impl IoPageTable<'_, ARM64LPAES1> {
/// Access the `ttbr` field of the configuration.
///
/// This is the physical address of the page table, which may be passed to the device that
/// needs to use it.
///
/// # Safety
///
/// The caller must ensure that the device stops using the page table before dropping it.
#[inline]
pub unsafe fn ttbr(&self) -> u64 {
// SAFETY: `arm_lpae_s1_cfg` is the right cfg type for `ARM64LPAES1`.
unsafe { (*self.raw_cfg()).__bindgen_anon_1.arm_lpae_s1_cfg.ttbr }
}
/// Access the `mair` field of the configuration.
#[inline]
pub fn mair(&self) -> u64 {
// SAFETY: `arm_lpae_s1_cfg` is the right cfg type for `ARM64LPAES1`.
unsafe { (*self.raw_cfg()).__bindgen_anon_1.arm_lpae_s1_cfg.mair }
}
}