Skip to content

Commit 94423bd

Browse files
committed
Add IoMemory trait
The existing `GuestMemory` trait is insufficient for representing virtual memory, as it does not allow specifying the required access permissions. Its focus on all guest memory implementations consisting of a relatively small number of regions is also unsuited for paged virtual memory with a potentially very lage set of non-continuous mappings. The new `IoMemory` trait in contrast provides only a small number of methods that keep the implementing type’s internal structure more opaque, and every access needs to be accompanied by the required permissions. Signed-off-by: Hanna Czenczek <[email protected]>
1 parent 96c70ea commit 94423bd

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

src/io_memory.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright (C) 2025 Red Hat. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license that can be
4+
// found in the LICENSE-BSD-3-Clause file.
5+
//
6+
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
7+
8+
//! Provides a trait for virtual I/O memory.
9+
//!
10+
//! This trait is more stripped down than `GuestMemory` because the fragmented nature of virtual
11+
//! memory does not allow a direct translation to long continuous regions.
12+
//!
13+
//! In addition, any access to virtual memory must be annotated with the intended access mode (i.e.
14+
//! reading and/or writing).
15+
16+
use crate::guest_memory::Result;
17+
use crate::{bitmap, GuestAddress, GuestMemory, MemoryRegionAddress, VolatileSlice};
18+
19+
/// Permissions for accessing virtual memory.
20+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21+
#[repr(u8)]
22+
pub enum Permissions {
23+
/// No permissions
24+
No = 0b00,
25+
/// Read-only
26+
Read = 0b01,
27+
/// Write-only
28+
Write = 0b10,
29+
/// Allow both reading and writing
30+
ReadWrite = 0b11,
31+
}
32+
33+
impl Permissions {
34+
/// Convert the numerical representation into the enum.
35+
///
36+
/// # Panics
37+
///
38+
/// Panics if `raw` is not a valid representation of any `Permissions` variant.
39+
fn from_repr(raw: u8) -> Self {
40+
use Permissions::*;
41+
42+
match raw {
43+
value if value == No as u8 => No,
44+
value if value == Read as u8 => Read,
45+
value if value == Write as u8 => Write,
46+
value if value == ReadWrite as u8 => ReadWrite,
47+
_ => panic!("{raw:x} is not a valid raw Permissions value"),
48+
}
49+
}
50+
51+
/// Check whether the permissions `self` allow the given `access`.
52+
pub fn allow(&self, access: Self) -> bool {
53+
*self & access == access
54+
}
55+
}
56+
57+
impl std::ops::BitOr for Permissions {
58+
type Output = Permissions;
59+
60+
/// Return the union of `self` and `rhs`.
61+
fn bitor(self, rhs: Permissions) -> Self::Output {
62+
Self::from_repr(self as u8 | rhs as u8)
63+
}
64+
}
65+
66+
impl std::ops::BitAnd for Permissions {
67+
type Output = Permissions;
68+
69+
/// Return the intersection of `self` and `rhs`.
70+
fn bitand(self, rhs: Permissions) -> Self::Output {
71+
Self::from_repr(self as u8 & rhs as u8)
72+
}
73+
}
74+
75+
/// Represents virtual I/O memory.
76+
///
77+
/// `IoMemory` is generally backed by some “physical” `GuestMemory`, which then consists for
78+
/// `GuestMemoryRegion` objects. However, the mapping from I/O virtual addresses (IOVAs) to
79+
/// physical addresses may be arbitrarily fragmented. Translation is done via an IOMMU.
80+
///
81+
/// Note in contrast to `GuestMemory`:
82+
/// - Any IOVA range may consist of arbitrarily many underlying ranges in physical memory.
83+
/// - Accessing an IOVA requires passing the intended access mode, and the IOMMU will check whether
84+
/// the given access mode is permitted for the given IOVA.
85+
/// - The translation result for a given IOVA may change over time (i.e. the physical address
86+
/// associated with an IOVA may change).
87+
pub trait IoMemory {
88+
/// Underlying `GuestMemory` type.
89+
type PhysicalMemory: GuestMemory + ?Sized;
90+
91+
/// Return `true` if `addr..(addr + count)` is accessible with `access`.
92+
fn range_accessible(&self, addr: GuestAddress, count: usize, access: Permissions) -> bool;
93+
94+
/// Invokes callback `f` to handle data in the address range `[addr, addr + count)`, with
95+
/// permissions `access`.
96+
///
97+
/// The address range `[addr, addr + count)` may span more than a single page in virtual
98+
/// memory, and more than one [`GuestMemoryRegion`](trait.GuestMemoryRegion.html) object, or
99+
/// even have holes or non-accessible regions in it. So `f` is invoked for each
100+
/// [`GuestMemoryRegion`](trait.GuestMemoryRegion.html) object and each non-continuous page
101+
/// involved, and then this function returns:
102+
/// - the error code returned by the callback 'f'
103+
/// - the size of the already handled data when encountering the first hole
104+
/// - the size of the already handled data when the whole range has been handled
105+
///
106+
/// The parameters to `f` are, in order:
107+
/// - Offset inside of the whole range (i.e. `addr` corresponds to offset `0`),
108+
/// - Length of the current chunk in bytes,
109+
/// - Relative address inside the [`GuestMemoryRegion`],
110+
/// - The underlying [`GuestMemoryRegion`].
111+
///
112+
/// `f` should return the number of bytes it handled. That number may be less than the length
113+
/// passed to it, in which case it will be called again for the chunk following immediately
114+
/// after that returned length. If `f` returns 0, processing will be stopped.
115+
fn try_access<F>(
116+
&self,
117+
count: usize,
118+
addr: GuestAddress,
119+
access: Permissions,
120+
f: F,
121+
) -> Result<usize>
122+
where
123+
F: FnMut(
124+
usize,
125+
usize,
126+
MemoryRegionAddress,
127+
&<Self::PhysicalMemory as GuestMemory>::R,
128+
) -> Result<usize>;
129+
130+
/// Returns a [`VolatileSlice`](struct.VolatileSlice.html) of `count` bytes starting at
131+
/// `addr`.
132+
///
133+
/// Note that because of the fragmented nature of virtual memory, it can easily happen that the
134+
/// range `[addr, addr + count)` is not backed by a continuous region in our own virtual
135+
/// memory, which will make generating the slice impossible.
136+
fn get_slices<'a>(
137+
&'a self,
138+
addr: GuestAddress,
139+
count: usize,
140+
access: Permissions,
141+
) -> Result<impl Iterator<Item = Result<VolatileSlice<'a, bitmap::MS<'a, Self::PhysicalMemory>>>>>;
142+
143+
/// If this virtual memory is just a plain `GuestMemory` object underneath without an IOMMU
144+
/// translation layer in between, return that `GuestMemory` object.
145+
fn physical_memory(&self) -> Option<&Self::PhysicalMemory> {
146+
None
147+
}
148+
}
149+
150+
/// Allow accessing every [`GuestMemory`] via [`IoMemory`].
151+
///
152+
/// [`IoMemory`] is a generalization of [`GuestMemory`]: Every object implementing the former is a
153+
/// subset of an object implementing the latter (there always is an underlying [`GuestMemory`]),
154+
/// with an opaque internal mapping on top, e.g. provided by an IOMMU.
155+
///
156+
/// Every [`GuestMemory`] is therefore trivially also an [`IoMemory`], assuming a complete identity
157+
/// mapping (which we must assume, so that accessing such objects via either trait will yield the
158+
/// same result): Basically, all [`IoMemory`] methods are implemented as trivial wrappers around
159+
/// the same [`GuestMemory`] methods (if available), discarding the `access` parameter.
160+
impl<M: GuestMemory + ?Sized> IoMemory for M {
161+
type PhysicalMemory = M;
162+
163+
fn range_accessible(&self, addr: GuestAddress, count: usize, _access: Permissions) -> bool {
164+
if let Ok(done) = <M as GuestMemory>::try_access(self, count, addr, |_, len, _, _| Ok(len))
165+
{
166+
done == count
167+
} else {
168+
false
169+
}
170+
}
171+
172+
fn try_access<F>(
173+
&self,
174+
count: usize,
175+
addr: GuestAddress,
176+
_access: Permissions,
177+
f: F,
178+
) -> Result<usize>
179+
where
180+
F: FnMut(
181+
usize,
182+
usize,
183+
MemoryRegionAddress,
184+
&<Self::PhysicalMemory as GuestMemory>::R,
185+
) -> Result<usize>,
186+
{
187+
<M as GuestMemory>::try_access(self, count, addr, f)
188+
}
189+
190+
fn get_slices<'a>(
191+
&'a self,
192+
addr: GuestAddress,
193+
count: usize,
194+
_access: Permissions,
195+
) -> Result<impl Iterator<Item = Result<VolatileSlice<'a, bitmap::MS<'a, Self::PhysicalMemory>>>>>
196+
{
197+
Ok(<M as GuestMemory>::get_slices(self, addr, count))
198+
}
199+
200+
fn physical_memory(&self) -> Option<&Self::PhysicalMemory> {
201+
Some(self)
202+
}
203+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ pub use region::{
5858
pub mod io;
5959
pub use io::{ReadVolatile, WriteVolatile};
6060

61+
pub mod io_memory;
62+
pub use io_memory::{IoMemory, Permissions};
63+
6164
#[cfg(feature = "backend-mmap")]
6265
pub mod mmap;
6366

0 commit comments

Comments
 (0)