xref: /linux/rust/kernel/iommu/pgtable.rs (revision c17ee635fd3a482b2ad2bf5e269755c2eae5f25e)
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