Skip to content

Commit 7a29522

Browse files
hoshinolinajannau
authored andcommitted
rust: io_pgtable: Add io_pgtable abstraction
The io_pgtable subsystem implements page table management for various IOMMU page table formats. This abstraction allows Rust drivers for devices with an embedded MMU to use this shared code. Signed-off-by: Asahi Lina <lina@asahilina.net>
1 parent c1b8820 commit 7a29522

3 files changed

Lines changed: 354 additions & 0 deletions

File tree

rust/bindings/bindings_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <linux/ethtool.h>
2020
#include <linux/firmware.h>
2121
#include <linux/fs.h>
22+
#include <linux/io-pgtable.h>
2223
#include <linux/jiffies.h>
2324
#include <linux/ktime.h>
2425
#include <linux/lockdep.h>

rust/kernel/io_pgtable.rs

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// FIXME
3+
#![allow(clippy::undocumented_unsafe_blocks)]
4+
5+
//! IOMMU page table management
6+
//!
7+
//! C header: [`include/io-pgtable.h`](../../../../include/io-pgtable.h)
8+
9+
use crate::{
10+
bindings, device,
11+
error::{code::*, to_result, Result},
12+
types::{ForeignOwnable, ScopeGuard},
13+
};
14+
15+
use core::marker::PhantomData;
16+
use core::mem;
17+
use core::num::NonZeroU64;
18+
19+
/// Protection flags used with IOMMU mappings.
20+
pub mod prot {
21+
/// Read access.
22+
pub const READ: u32 = bindings::IOMMU_READ;
23+
/// Write access.
24+
pub const WRITE: u32 = bindings::IOMMU_WRITE;
25+
/// Request cache coherency.
26+
pub const CACHE: u32 = bindings::IOMMU_CACHE;
27+
/// Request no-execute permission.
28+
pub const NOEXEC: u32 = bindings::IOMMU_NOEXEC;
29+
/// MMIO peripheral mapping.
30+
pub const MMIO: u32 = bindings::IOMMU_MMIO;
31+
/// Privileged mapping.
32+
pub const PRIV: u32 = bindings::IOMMU_PRIV;
33+
}
34+
35+
/// Represents a requested io_pgtable configuration.
36+
pub struct Config {
37+
/// Quirk bitmask (type-specific).
38+
pub quirks: usize,
39+
/// Valid page sizes, as a bitmask of powers of two.
40+
pub pgsize_bitmap: usize,
41+
/// Input address space size in bits.
42+
pub ias: usize,
43+
/// Output address space size in bits.
44+
pub oas: usize,
45+
/// IOMMU uses coherent accesses for page table walks.
46+
pub coherent_walk: bool,
47+
}
48+
49+
/// IOMMU callbacks for TLB and page table management.
50+
///
51+
/// Users must implement this trait to perform the TLB flush actions for this IOMMU, if
52+
/// required.
53+
pub trait FlushOps {
54+
/// User-specified type owned by the IOPagetable that will be passed to TLB operations.
55+
type Data: ForeignOwnable + Send + Sync;
56+
57+
/// Synchronously invalidate the entire TLB context.
58+
fn tlb_flush_all(data: <Self::Data as ForeignOwnable>::Borrowed<'_>);
59+
60+
/// Synchronously invalidate all intermediate TLB state (sometimes referred to as the "walk
61+
/// cache") for a virtual address range.
62+
fn tlb_flush_walk(
63+
data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
64+
iova: usize,
65+
size: usize,
66+
granule: usize,
67+
);
68+
69+
/// Optional callback to queue up leaf TLB invalidation for a single page.
70+
///
71+
/// IOMMUs that cannot batch TLB invalidation operations efficiently will typically issue
72+
/// them here, but others may decide to update the iommu_iotlb_gather structure and defer
73+
/// the invalidation until iommu_iotlb_sync() instead.
74+
///
75+
/// TODO: Implement the gather argument for batching.
76+
fn tlb_add_page(
77+
data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
78+
iova: usize,
79+
granule: usize,
80+
);
81+
}
82+
83+
/// Inner page table info shared across all table types.
84+
/// # Invariants
85+
///
86+
/// - [`self.ops`] is valid and non-null.
87+
/// - [`self.cfg`] is valid and non-null.
88+
#[doc(hidden)]
89+
pub struct IoPageTableInner {
90+
ops: *mut bindings::io_pgtable_ops,
91+
cfg: bindings::io_pgtable_cfg,
92+
data: *mut core::ffi::c_void,
93+
}
94+
95+
/// Helper trait to get the config type for a single page table type from the union.
96+
pub trait GetConfig {
97+
/// Returns the specific output configuration for this page table type.
98+
fn cfg(iopt: &impl IoPageTable) -> &Self
99+
where
100+
Self: Sized;
101+
}
102+
103+
/// A generic IOMMU page table
104+
pub trait IoPageTable: crate::private::Sealed {
105+
#[doc(hidden)]
106+
const FLUSH_OPS: bindings::iommu_flush_ops;
107+
108+
#[doc(hidden)]
109+
fn new_fmt<T: FlushOps>(
110+
dev: &device::Device,
111+
format: u32,
112+
config: Config,
113+
data: T::Data,
114+
) -> Result<IoPageTableInner> {
115+
let ptr = data.into_foreign() as *mut _;
116+
let guard = ScopeGuard::new(|| {
117+
// SAFETY: `ptr` came from a previous call to `into_foreign`.
118+
unsafe { T::Data::from_foreign(ptr) };
119+
});
120+
121+
let mut raw_cfg = bindings::io_pgtable_cfg {
122+
quirks: config.quirks.try_into()?,
123+
pgsize_bitmap: config.pgsize_bitmap.try_into()?,
124+
ias: config.ias.try_into()?,
125+
oas: config.oas.try_into()?,
126+
coherent_walk: config.coherent_walk,
127+
tlb: &Self::FLUSH_OPS,
128+
iommu_dev: dev.as_raw(),
129+
alloc: None,
130+
free: None,
131+
__bindgen_anon_1: unsafe { mem::zeroed() },
132+
};
133+
134+
let ops = unsafe {
135+
bindings::alloc_io_pgtable_ops(format as bindings::io_pgtable_fmt, &mut raw_cfg, ptr)
136+
};
137+
138+
if ops.is_null() {
139+
return Err(EINVAL);
140+
}
141+
142+
guard.dismiss();
143+
Ok(IoPageTableInner {
144+
ops,
145+
cfg: raw_cfg,
146+
data: ptr,
147+
})
148+
}
149+
150+
/// Map a range of pages.
151+
fn map_pages(
152+
&mut self,
153+
iova: usize,
154+
paddr: usize,
155+
pgsize: usize,
156+
pgcount: usize,
157+
prot: u32,
158+
) -> Result<usize> {
159+
let mut mapped: usize = 0;
160+
161+
to_result(unsafe {
162+
(*self.inner_mut().ops).map_pages.unwrap()(
163+
self.inner_mut().ops,
164+
iova as u64,
165+
paddr as u64,
166+
pgsize,
167+
pgcount,
168+
prot as i32,
169+
bindings::GFP_KERNEL,
170+
&mut mapped,
171+
)
172+
})?;
173+
174+
Ok(mapped)
175+
}
176+
177+
/// Unmap a range of pages.
178+
fn unmap_pages(
179+
&mut self,
180+
iova: usize,
181+
pgsize: usize,
182+
pgcount: usize,
183+
// TODO: gather: *mut iommu_iotlb_gather,
184+
) -> usize {
185+
unsafe {
186+
(*self.inner_mut().ops).unmap_pages.unwrap()(
187+
self.inner_mut().ops,
188+
iova as u64,
189+
pgsize,
190+
pgcount,
191+
core::ptr::null_mut(),
192+
)
193+
}
194+
}
195+
196+
/// Translate an IOVA to the corresponding physical address, if mapped.
197+
fn iova_to_phys(&mut self, iova: usize) -> Option<NonZeroU64> {
198+
NonZeroU64::new(unsafe {
199+
(*self.inner_mut().ops).iova_to_phys.unwrap()(self.inner_mut().ops, iova as u64)
200+
})
201+
}
202+
203+
#[doc(hidden)]
204+
fn inner_mut(&mut self) -> &mut IoPageTableInner;
205+
206+
#[doc(hidden)]
207+
fn inner(&self) -> &IoPageTableInner;
208+
209+
#[doc(hidden)]
210+
fn raw_cfg(&self) -> &bindings::io_pgtable_cfg {
211+
&self.inner().cfg
212+
}
213+
}
214+
215+
unsafe impl Send for IoPageTableInner {}
216+
unsafe impl Sync for IoPageTableInner {}
217+
218+
unsafe extern "C" fn tlb_flush_all_callback<T: FlushOps>(cookie: *mut core::ffi::c_void) {
219+
T::tlb_flush_all(unsafe { T::Data::borrow(cookie) });
220+
}
221+
222+
unsafe extern "C" fn tlb_flush_walk_callback<T: FlushOps>(
223+
iova: core::ffi::c_ulong,
224+
size: usize,
225+
granule: usize,
226+
cookie: *mut core::ffi::c_void,
227+
) {
228+
T::tlb_flush_walk(
229+
unsafe { T::Data::borrow(cookie) },
230+
iova as usize,
231+
size,
232+
granule,
233+
);
234+
}
235+
236+
unsafe extern "C" fn tlb_add_page_callback<T: FlushOps>(
237+
_gather: *mut bindings::iommu_iotlb_gather,
238+
iova: core::ffi::c_ulong,
239+
granule: usize,
240+
cookie: *mut core::ffi::c_void,
241+
) {
242+
T::tlb_add_page(unsafe { T::Data::borrow(cookie) }, iova as usize, granule);
243+
}
244+
245+
macro_rules! iopt_cfg {
246+
($name:ident, $field:ident, $type:ident) => {
247+
/// An IOMMU page table configuration for a specific kind of pagetable.
248+
pub type $name = bindings::$type;
249+
250+
impl GetConfig for $name {
251+
fn cfg(iopt: &impl IoPageTable) -> &$name {
252+
unsafe { &iopt.raw_cfg().__bindgen_anon_1.$field }
253+
}
254+
}
255+
};
256+
}
257+
258+
impl GetConfig for () {
259+
fn cfg(_iopt: &impl IoPageTable) -> &() {
260+
&()
261+
}
262+
}
263+
264+
macro_rules! iopt_type {
265+
($type:ident, $cfg:ty, $fmt:ident) => {
266+
/// Represents an IOPagetable of this type.
267+
pub struct $type<T: FlushOps>(IoPageTableInner, PhantomData<T>);
268+
269+
impl<T: FlushOps> $type<T> {
270+
/// Creates a new IOPagetable implementation of this type.
271+
pub fn new(dev: &device::Device, config: Config, data: T::Data) -> Result<Self> {
272+
Ok(Self(
273+
<Self as IoPageTable>::new_fmt::<T>(dev, bindings::$fmt, config, data)?,
274+
PhantomData,
275+
))
276+
}
277+
278+
/// Get the configuration for this IOPagetable.
279+
pub fn cfg(&self) -> &$cfg {
280+
<$cfg as GetConfig>::cfg(self)
281+
}
282+
}
283+
284+
impl<T: FlushOps> crate::private::Sealed for $type<T> {}
285+
286+
impl<T: FlushOps> IoPageTable for $type<T> {
287+
const FLUSH_OPS: bindings::iommu_flush_ops = bindings::iommu_flush_ops {
288+
tlb_flush_all: Some(tlb_flush_all_callback::<T>),
289+
tlb_flush_walk: Some(tlb_flush_walk_callback::<T>),
290+
tlb_add_page: Some(tlb_add_page_callback::<T>),
291+
};
292+
293+
fn inner(&self) -> &IoPageTableInner {
294+
&self.0
295+
}
296+
297+
fn inner_mut(&mut self) -> &mut IoPageTableInner {
298+
&mut self.0
299+
}
300+
}
301+
302+
impl<T: FlushOps> Drop for $type<T> {
303+
fn drop(&mut self) {
304+
// SAFETY: The pointer is valid by the type invariant.
305+
unsafe { bindings::free_io_pgtable_ops(self.0.ops) };
306+
307+
// Free context data.
308+
//
309+
// SAFETY: This matches the call to `into_foreign` from `new` in the success case.
310+
unsafe { T::Data::from_foreign(self.0.data) };
311+
}
312+
}
313+
};
314+
}
315+
316+
// Ew...
317+
iopt_cfg!(
318+
ARMLPAES1Cfg,
319+
arm_lpae_s1_cfg,
320+
io_pgtable_cfg__bindgen_ty_1__bindgen_ty_1
321+
);
322+
iopt_cfg!(
323+
ARMLPAES2Cfg,
324+
arm_lpae_s2_cfg,
325+
io_pgtable_cfg__bindgen_ty_1__bindgen_ty_2
326+
);
327+
iopt_cfg!(
328+
ARMv7SCfg,
329+
arm_v7s_cfg,
330+
io_pgtable_cfg__bindgen_ty_1__bindgen_ty_3
331+
);
332+
iopt_cfg!(
333+
ARMMaliLPAECfg,
334+
arm_mali_lpae_cfg,
335+
io_pgtable_cfg__bindgen_ty_1__bindgen_ty_4
336+
);
337+
iopt_cfg!(
338+
AppleDARTCfg,
339+
apple_dart_cfg,
340+
io_pgtable_cfg__bindgen_ty_1__bindgen_ty_5
341+
);
342+
iopt_cfg!(AmdCfg, amd, io_pgtable_cfg__bindgen_ty_1__bindgen_ty_6);
343+
344+
iopt_type!(ARM32LPAES1, ARMLPAES1Cfg, io_pgtable_fmt_ARM_32_LPAE_S1);
345+
iopt_type!(ARM32LPAES2, ARMLPAES2Cfg, io_pgtable_fmt_ARM_32_LPAE_S2);
346+
iopt_type!(ARM64LPAES1, ARMLPAES1Cfg, io_pgtable_fmt_ARM_64_LPAE_S1);
347+
iopt_type!(ARM64LPAES2, ARMLPAES2Cfg, io_pgtable_fmt_ARM_64_LPAE_S2);
348+
iopt_type!(ARMv7S, ARMv7SCfg, io_pgtable_fmt_ARM_V7S);
349+
iopt_type!(ARMMaliLPAE, ARMMaliLPAECfg, io_pgtable_fmt_ARM_MALI_LPAE);
350+
iopt_type!(AMDIOMMUV1, (), io_pgtable_fmt_AMD_IOMMU_V1);
351+
iopt_type!(AppleDART, AppleDARTCfg, io_pgtable_fmt_APPLE_DART);
352+
iopt_type!(AppleDART2, AppleDARTCfg, io_pgtable_fmt_APPLE_DART2);

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub mod error;
5151
#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
5252
pub mod firmware;
5353
pub mod init;
54+
pub mod io_pgtable;
5455
pub mod ioctl;
5556
#[cfg(CONFIG_KUNIT)]
5657
pub mod kunit;

0 commit comments

Comments
 (0)