12e2f6b0eSAsahi Lina // SPDX-License-Identifier: GPL-2.0 22e2f6b0eSAsahi Lina 32e2f6b0eSAsahi Lina //! IOMMU page table management. 42e2f6b0eSAsahi Lina //! 5*12248a38SMiguel Ojeda //! C header: [`include/linux/io-pgtable.h`](srctree/include/linux/io-pgtable.h) 62e2f6b0eSAsahi Lina 72e2f6b0eSAsahi Lina use core::{ 82e2f6b0eSAsahi Lina marker::PhantomData, 92e2f6b0eSAsahi Lina ptr::NonNull, // 102e2f6b0eSAsahi Lina }; 112e2f6b0eSAsahi Lina 122e2f6b0eSAsahi Lina use crate::{ 132e2f6b0eSAsahi Lina alloc, 142e2f6b0eSAsahi Lina bindings, 152e2f6b0eSAsahi Lina device::{ 162e2f6b0eSAsahi Lina Bound, 177222dd07SMiguel Ojeda Device, // 182e2f6b0eSAsahi Lina }, 192e2f6b0eSAsahi Lina devres::Devres, 202e2f6b0eSAsahi Lina error::to_result, 212e2f6b0eSAsahi Lina io::PhysAddr, 222e2f6b0eSAsahi Lina prelude::*, // 232e2f6b0eSAsahi Lina }; 242e2f6b0eSAsahi Lina 252e2f6b0eSAsahi Lina use bindings::io_pgtable_fmt; 262e2f6b0eSAsahi Lina 272e2f6b0eSAsahi Lina /// Protection flags used with IOMMU mappings. 282e2f6b0eSAsahi Lina pub mod prot { 292e2f6b0eSAsahi Lina /// Read access. 302e2f6b0eSAsahi Lina pub const READ: u32 = bindings::IOMMU_READ; 312e2f6b0eSAsahi Lina /// Write access. 322e2f6b0eSAsahi Lina pub const WRITE: u32 = bindings::IOMMU_WRITE; 332e2f6b0eSAsahi Lina /// Request cache coherency. 342e2f6b0eSAsahi Lina pub const CACHE: u32 = bindings::IOMMU_CACHE; 352e2f6b0eSAsahi Lina /// Request no-execute permission. 362e2f6b0eSAsahi Lina pub const NOEXEC: u32 = bindings::IOMMU_NOEXEC; 372e2f6b0eSAsahi Lina /// MMIO peripheral mapping. 382e2f6b0eSAsahi Lina pub const MMIO: u32 = bindings::IOMMU_MMIO; 392e2f6b0eSAsahi Lina /// Privileged mapping. 402e2f6b0eSAsahi Lina pub const PRIVILEGED: u32 = bindings::IOMMU_PRIV; 412e2f6b0eSAsahi Lina } 422e2f6b0eSAsahi Lina 432e2f6b0eSAsahi Lina /// Represents a requested `io_pgtable` configuration. 442e2f6b0eSAsahi Lina pub struct Config { 452e2f6b0eSAsahi Lina /// Quirk bitmask (type-specific). 462e2f6b0eSAsahi Lina pub quirks: usize, 472e2f6b0eSAsahi Lina /// Valid page sizes, as a bitmask of powers of two. 482e2f6b0eSAsahi Lina pub pgsize_bitmap: usize, 492e2f6b0eSAsahi Lina /// Input address space size in bits. 502e2f6b0eSAsahi Lina pub ias: u32, 512e2f6b0eSAsahi Lina /// Output address space size in bits. 522e2f6b0eSAsahi Lina pub oas: u32, 532e2f6b0eSAsahi Lina /// IOMMU uses coherent accesses for page table walks. 542e2f6b0eSAsahi Lina pub coherent_walk: bool, 552e2f6b0eSAsahi Lina } 562e2f6b0eSAsahi Lina 572e2f6b0eSAsahi Lina /// An io page table using a specific format. 582e2f6b0eSAsahi Lina /// 592e2f6b0eSAsahi Lina /// # Invariants 602e2f6b0eSAsahi Lina /// 612e2f6b0eSAsahi Lina /// The pointer references a valid io page table. 622e2f6b0eSAsahi Lina pub struct IoPageTable<F: IoPageTableFmt> { 632e2f6b0eSAsahi Lina ptr: NonNull<bindings::io_pgtable_ops>, 642e2f6b0eSAsahi Lina _marker: PhantomData<F>, 652e2f6b0eSAsahi Lina } 662e2f6b0eSAsahi Lina 672e2f6b0eSAsahi Lina // SAFETY: `struct io_pgtable_ops` is not restricted to a single thread. 682e2f6b0eSAsahi Lina unsafe impl<F: IoPageTableFmt> Send for IoPageTable<F> {} 692e2f6b0eSAsahi Lina // SAFETY: `struct io_pgtable_ops` may be accessed concurrently. 702e2f6b0eSAsahi Lina unsafe impl<F: IoPageTableFmt> Sync for IoPageTable<F> {} 712e2f6b0eSAsahi Lina 722e2f6b0eSAsahi Lina /// The format used by this page table. 732e2f6b0eSAsahi Lina pub trait IoPageTableFmt: 'static { 742e2f6b0eSAsahi Lina /// The value representing this format. 752e2f6b0eSAsahi Lina const FORMAT: io_pgtable_fmt; 762e2f6b0eSAsahi Lina } 772e2f6b0eSAsahi Lina 782e2f6b0eSAsahi Lina impl<F: IoPageTableFmt> IoPageTable<F> { 792e2f6b0eSAsahi Lina /// Create a new `IoPageTable` as a device resource. 802e2f6b0eSAsahi Lina #[inline] 812e2f6b0eSAsahi Lina pub fn new( 822e2f6b0eSAsahi Lina dev: &Device<Bound>, 832e2f6b0eSAsahi Lina config: Config, 842e2f6b0eSAsahi Lina ) -> impl PinInit<Devres<IoPageTable<F>>, Error> + '_ { 852e2f6b0eSAsahi Lina // SAFETY: Devres ensures that the value is dropped during device unbind. 862e2f6b0eSAsahi Lina Devres::new(dev, unsafe { Self::new_raw(dev, config) }) 872e2f6b0eSAsahi Lina } 882e2f6b0eSAsahi Lina 892e2f6b0eSAsahi Lina /// Create a new `IoPageTable`. 902e2f6b0eSAsahi Lina /// 912e2f6b0eSAsahi Lina /// # Safety 922e2f6b0eSAsahi Lina /// 932e2f6b0eSAsahi Lina /// If successful, then the returned `IoPageTable` must be dropped before the device is 942e2f6b0eSAsahi Lina /// unbound. 952e2f6b0eSAsahi Lina #[inline] 962e2f6b0eSAsahi Lina pub unsafe fn new_raw(dev: &Device<Bound>, config: Config) -> Result<IoPageTable<F>> { 972e2f6b0eSAsahi Lina let mut raw_cfg = bindings::io_pgtable_cfg { 982e2f6b0eSAsahi Lina quirks: config.quirks, 992e2f6b0eSAsahi Lina pgsize_bitmap: config.pgsize_bitmap, 1002e2f6b0eSAsahi Lina ias: config.ias, 1012e2f6b0eSAsahi Lina oas: config.oas, 1022e2f6b0eSAsahi Lina coherent_walk: config.coherent_walk, 1032e2f6b0eSAsahi Lina tlb: &raw const NOOP_FLUSH_OPS, 1042e2f6b0eSAsahi Lina iommu_dev: dev.as_raw(), 1052e2f6b0eSAsahi Lina // SAFETY: All zeroes is a valid value for `struct io_pgtable_cfg`. 1062e2f6b0eSAsahi Lina ..unsafe { core::mem::zeroed() } 1072e2f6b0eSAsahi Lina }; 1082e2f6b0eSAsahi Lina 1092e2f6b0eSAsahi Lina // SAFETY: 1102e2f6b0eSAsahi Lina // * The raw_cfg pointer is valid for the duration of this call. 1112e2f6b0eSAsahi Lina // * The provided `FLUSH_OPS` contains valid function pointers that accept a null pointer 1122e2f6b0eSAsahi Lina // as cookie. 1132e2f6b0eSAsahi Lina // * The caller ensures that the io pgtable does not outlive the device. 1142e2f6b0eSAsahi Lina let ops = unsafe { 1152e2f6b0eSAsahi Lina bindings::alloc_io_pgtable_ops(F::FORMAT, &mut raw_cfg, core::ptr::null_mut()) 1162e2f6b0eSAsahi Lina }; 1172e2f6b0eSAsahi Lina 1182e2f6b0eSAsahi Lina // INVARIANT: We successfully created a valid page table. 1192e2f6b0eSAsahi Lina Ok(IoPageTable { 1202e2f6b0eSAsahi Lina ptr: NonNull::new(ops).ok_or(ENOMEM)?, 1212e2f6b0eSAsahi Lina _marker: PhantomData, 1222e2f6b0eSAsahi Lina }) 1232e2f6b0eSAsahi Lina } 1242e2f6b0eSAsahi Lina 1252e2f6b0eSAsahi Lina /// Obtain a raw pointer to the underlying `struct io_pgtable_ops`. 1262e2f6b0eSAsahi Lina #[inline] 1272e2f6b0eSAsahi Lina pub fn raw_ops(&self) -> *mut bindings::io_pgtable_ops { 1282e2f6b0eSAsahi Lina self.ptr.as_ptr() 1292e2f6b0eSAsahi Lina } 1302e2f6b0eSAsahi Lina 1312e2f6b0eSAsahi Lina /// Obtain a raw pointer to the underlying `struct io_pgtable`. 1322e2f6b0eSAsahi Lina #[inline] 1332e2f6b0eSAsahi Lina pub fn raw_pgtable(&self) -> *mut bindings::io_pgtable { 1342e2f6b0eSAsahi Lina // SAFETY: The io_pgtable_ops of an io-pgtable is always the ops field of a io_pgtable. 1352e2f6b0eSAsahi Lina unsafe { kernel::container_of!(self.raw_ops(), bindings::io_pgtable, ops) } 1362e2f6b0eSAsahi Lina } 1372e2f6b0eSAsahi Lina 1382e2f6b0eSAsahi Lina /// Obtain a raw pointer to the underlying `struct io_pgtable_cfg`. 1392e2f6b0eSAsahi Lina #[inline] 1402e2f6b0eSAsahi Lina pub fn raw_cfg(&self) -> *mut bindings::io_pgtable_cfg { 1412e2f6b0eSAsahi Lina // SAFETY: The `raw_pgtable()` method returns a valid pointer. 1422e2f6b0eSAsahi Lina unsafe { &raw mut (*self.raw_pgtable()).cfg } 1432e2f6b0eSAsahi Lina } 1442e2f6b0eSAsahi Lina 1452e2f6b0eSAsahi Lina /// Map a physically contiguous range of pages of the same size. 1462e2f6b0eSAsahi Lina /// 1472e2f6b0eSAsahi Lina /// Even if successful, this operation may not map the entire range. In that case, only a 1482e2f6b0eSAsahi Lina /// prefix of the range is mapped, and the returned integer indicates its length in bytes. In 1492e2f6b0eSAsahi Lina /// this case, the caller will usually call `map_pages` again for the remaining range. 1502e2f6b0eSAsahi Lina /// 1512e2f6b0eSAsahi Lina /// The returned [`Result`] indicates whether an error was encountered while mapping pages. 1522e2f6b0eSAsahi Lina /// Note that this may return a non-zero length even if an error was encountered. The caller 1532e2f6b0eSAsahi Lina /// will usually [unmap the relevant pages](Self::unmap_pages) on error. 1542e2f6b0eSAsahi Lina /// 1552e2f6b0eSAsahi Lina /// The caller must flush the TLB before using the pgtable to access the newly created mapping. 1562e2f6b0eSAsahi Lina /// 1572e2f6b0eSAsahi Lina /// # Safety 1582e2f6b0eSAsahi Lina /// 1592e2f6b0eSAsahi Lina /// * No other io-pgtable operation may access the range `iova .. iova+pgsize*pgcount` while 1602e2f6b0eSAsahi Lina /// this `map_pages` operation executes. 1612e2f6b0eSAsahi Lina /// * This page table must not contain any mapping that overlaps with the mapping created by 1622e2f6b0eSAsahi Lina /// this call. 1632e2f6b0eSAsahi Lina /// * If this page table is live, then the caller must ensure that it's okay to access the 1642e2f6b0eSAsahi Lina /// physical address being mapped for the duration in which it is mapped. 1652e2f6b0eSAsahi Lina #[inline] 1662e2f6b0eSAsahi Lina pub unsafe fn map_pages( 1672e2f6b0eSAsahi Lina &self, 1682e2f6b0eSAsahi Lina iova: usize, 1692e2f6b0eSAsahi Lina paddr: PhysAddr, 1702e2f6b0eSAsahi Lina pgsize: usize, 1712e2f6b0eSAsahi Lina pgcount: usize, 1722e2f6b0eSAsahi Lina prot: u32, 1732e2f6b0eSAsahi Lina flags: alloc::Flags, 1742e2f6b0eSAsahi Lina ) -> (usize, Result) { 1752e2f6b0eSAsahi Lina let mut mapped: usize = 0; 1762e2f6b0eSAsahi Lina 1772e2f6b0eSAsahi Lina // SAFETY: The `map_pages` function in `io_pgtable_ops` is never null. 1782e2f6b0eSAsahi Lina let map_pages = unsafe { (*self.raw_ops()).map_pages.unwrap_unchecked() }; 1792e2f6b0eSAsahi Lina 1802e2f6b0eSAsahi Lina // SAFETY: The safety requirements of this method are sufficient to call `map_pages`. 1812e2f6b0eSAsahi Lina let ret = to_result(unsafe { 1822e2f6b0eSAsahi Lina (map_pages)( 1832e2f6b0eSAsahi Lina self.raw_ops(), 1842e2f6b0eSAsahi Lina iova, 1852e2f6b0eSAsahi Lina paddr, 1862e2f6b0eSAsahi Lina pgsize, 1872e2f6b0eSAsahi Lina pgcount, 1882e2f6b0eSAsahi Lina prot as i32, 1892e2f6b0eSAsahi Lina flags.as_raw(), 1902e2f6b0eSAsahi Lina &mut mapped, 1912e2f6b0eSAsahi Lina ) 1922e2f6b0eSAsahi Lina }); 1932e2f6b0eSAsahi Lina 1942e2f6b0eSAsahi Lina (mapped, ret) 1952e2f6b0eSAsahi Lina } 1962e2f6b0eSAsahi Lina 1972e2f6b0eSAsahi Lina /// Unmap a range of virtually contiguous pages of the same size. 1982e2f6b0eSAsahi Lina /// 1992e2f6b0eSAsahi Lina /// This may not unmap the entire range, and returns the length of the unmapped prefix in 2002e2f6b0eSAsahi Lina /// bytes. 2012e2f6b0eSAsahi Lina /// 2022e2f6b0eSAsahi Lina /// # Safety 2032e2f6b0eSAsahi Lina /// 2042e2f6b0eSAsahi Lina /// * No other io-pgtable operation may access the range `iova .. iova+pgsize*pgcount` while 2052e2f6b0eSAsahi Lina /// this `unmap_pages` operation executes. 2062e2f6b0eSAsahi Lina /// * This page table must contain one or more consecutive mappings starting at `iova` whose 2072e2f6b0eSAsahi Lina /// total size is `pgcount * pgsize`. 2082e2f6b0eSAsahi Lina #[inline] 2092e2f6b0eSAsahi Lina #[must_use] 2102e2f6b0eSAsahi Lina pub unsafe fn unmap_pages(&self, iova: usize, pgsize: usize, pgcount: usize) -> usize { 2112e2f6b0eSAsahi Lina // SAFETY: The `unmap_pages` function in `io_pgtable_ops` is never null. 2122e2f6b0eSAsahi Lina let unmap_pages = unsafe { (*self.raw_ops()).unmap_pages.unwrap_unchecked() }; 2132e2f6b0eSAsahi Lina 2142e2f6b0eSAsahi Lina // SAFETY: The safety requirements of this method are sufficient to call `unmap_pages`. 2152e2f6b0eSAsahi Lina unsafe { (unmap_pages)(self.raw_ops(), iova, pgsize, pgcount, core::ptr::null_mut()) } 2162e2f6b0eSAsahi Lina } 2172e2f6b0eSAsahi Lina } 2182e2f6b0eSAsahi Lina 2192e2f6b0eSAsahi Lina // For the initial users of these rust bindings, the GPU FW is managing the IOTLB and performs all 2202e2f6b0eSAsahi Lina // required invalidations using a range. There is no need for it get ARM style invalidation 2212e2f6b0eSAsahi Lina // instructions from the page table code. 2222e2f6b0eSAsahi Lina // 2232e2f6b0eSAsahi Lina // Support for flushing the TLB with ARM style invalidation instructions may be added in the 2242e2f6b0eSAsahi Lina // future. 2252e2f6b0eSAsahi Lina static NOOP_FLUSH_OPS: bindings::iommu_flush_ops = bindings::iommu_flush_ops { 2262e2f6b0eSAsahi Lina tlb_flush_all: Some(rust_tlb_flush_all_noop), 2272e2f6b0eSAsahi Lina tlb_flush_walk: Some(rust_tlb_flush_walk_noop), 2282e2f6b0eSAsahi Lina tlb_add_page: None, 2292e2f6b0eSAsahi Lina }; 2302e2f6b0eSAsahi Lina 2312e2f6b0eSAsahi Lina #[no_mangle] 2322e2f6b0eSAsahi Lina extern "C" fn rust_tlb_flush_all_noop(_cookie: *mut core::ffi::c_void) {} 2332e2f6b0eSAsahi Lina 2342e2f6b0eSAsahi Lina #[no_mangle] 2352e2f6b0eSAsahi Lina extern "C" fn rust_tlb_flush_walk_noop( 2362e2f6b0eSAsahi Lina _iova: usize, 2372e2f6b0eSAsahi Lina _size: usize, 2382e2f6b0eSAsahi Lina _granule: usize, 2392e2f6b0eSAsahi Lina _cookie: *mut core::ffi::c_void, 2402e2f6b0eSAsahi Lina ) { 2412e2f6b0eSAsahi Lina } 2422e2f6b0eSAsahi Lina 2432e2f6b0eSAsahi Lina impl<F: IoPageTableFmt> Drop for IoPageTable<F> { 2442e2f6b0eSAsahi Lina fn drop(&mut self) { 2452e2f6b0eSAsahi Lina // SAFETY: The caller of `Self::ttbr()` promised that the page table is not live when this 2462e2f6b0eSAsahi Lina // destructor runs. 2472e2f6b0eSAsahi Lina unsafe { bindings::free_io_pgtable_ops(self.raw_ops()) }; 2482e2f6b0eSAsahi Lina } 2492e2f6b0eSAsahi Lina } 2502e2f6b0eSAsahi Lina 2512e2f6b0eSAsahi Lina /// The `ARM_64_LPAE_S1` page table format. 2522e2f6b0eSAsahi Lina pub enum ARM64LPAES1 {} 2532e2f6b0eSAsahi Lina 2542e2f6b0eSAsahi Lina impl IoPageTableFmt for ARM64LPAES1 { 2552e2f6b0eSAsahi Lina const FORMAT: io_pgtable_fmt = bindings::io_pgtable_fmt_ARM_64_LPAE_S1 as io_pgtable_fmt; 2562e2f6b0eSAsahi Lina } 2572e2f6b0eSAsahi Lina 2582e2f6b0eSAsahi Lina impl IoPageTable<ARM64LPAES1> { 2592e2f6b0eSAsahi Lina /// Access the `ttbr` field of the configuration. 2602e2f6b0eSAsahi Lina /// 2612e2f6b0eSAsahi Lina /// This is the physical address of the page table, which may be passed to the device that 2622e2f6b0eSAsahi Lina /// needs to use it. 2632e2f6b0eSAsahi Lina /// 2642e2f6b0eSAsahi Lina /// # Safety 2652e2f6b0eSAsahi Lina /// 2662e2f6b0eSAsahi Lina /// The caller must ensure that the device stops using the page table before dropping it. 2672e2f6b0eSAsahi Lina #[inline] 2682e2f6b0eSAsahi Lina pub unsafe fn ttbr(&self) -> u64 { 2692e2f6b0eSAsahi Lina // SAFETY: `arm_lpae_s1_cfg` is the right cfg type for `ARM64LPAES1`. 2702e2f6b0eSAsahi Lina unsafe { (*self.raw_cfg()).__bindgen_anon_1.arm_lpae_s1_cfg.ttbr } 2712e2f6b0eSAsahi Lina } 2722e2f6b0eSAsahi Lina 2732e2f6b0eSAsahi Lina /// Access the `mair` field of the configuration. 2742e2f6b0eSAsahi Lina #[inline] 2752e2f6b0eSAsahi Lina pub fn mair(&self) -> u64 { 2762e2f6b0eSAsahi Lina // SAFETY: `arm_lpae_s1_cfg` is the right cfg type for `ARM64LPAES1`. 2772e2f6b0eSAsahi Lina unsafe { (*self.raw_cfg()).__bindgen_anon_1.arm_lpae_s1_cfg.mair } 2782e2f6b0eSAsahi Lina } 2792e2f6b0eSAsahi Lina } 280