Skip to content

Commit 1b60528

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 d780d9d commit 1b60528

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

src/io_memory.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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+
pub enum Permissions {
22+
/// No permissions
23+
No,
24+
/// Read-only
25+
Read,
26+
/// Write-only
27+
Write,
28+
/// Allow both reading and writing
29+
ReadWrite,
30+
}
31+
32+
impl Permissions {
33+
/// Check whether the permissions `self` allow the given `access`.
34+
pub fn allow(&self, access: Self) -> bool {
35+
*self & access == access
36+
}
37+
}
38+
39+
impl std::ops::BitOr for Permissions {
40+
type Output = Permissions;
41+
42+
/// Return the union of `self` and `rhs`.
43+
fn bitor(self, rhs: Permissions) -> Self::Output {
44+
use Permissions::*;
45+
46+
match (self, rhs) {
47+
(No, rhs) => rhs,
48+
(lhs, No) => lhs,
49+
(ReadWrite, _) | (_, ReadWrite) => ReadWrite,
50+
(Read, Read) => Read,
51+
(Read, Write) | (Write, Read) => ReadWrite,
52+
(Write, Write) => Write,
53+
}
54+
}
55+
}
56+
57+
impl std::ops::BitAnd for Permissions {
58+
type Output = Permissions;
59+
60+
/// Return the intersection of `self` and `rhs`.
61+
fn bitand(self, rhs: Permissions) -> Self::Output {
62+
use Permissions::*;
63+
64+
match (self, rhs) {
65+
(No, _) | (_, No) => No,
66+
(ReadWrite, rhs) => rhs,
67+
(lhs, ReadWrite) => lhs,
68+
(Read, Read) => Read,
69+
(Read, Write) | (Write, Read) => No,
70+
(Write, Write) => Write,
71+
}
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;
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 one
98+
/// [`GuestMemoryRegion`](trait.GuestMemoryRegion.html) object, or even have holes in it.
99+
/// So [`try_access()`](trait.IoMemory.html#method.try_access) invokes the callback 'f'
100+
/// for each [`GuestMemoryRegion`](trait.GuestMemoryRegion.html) object involved and returns:
101+
/// - the error code returned by the callback 'f'
102+
/// - the size of the already handled data when encountering the first hole
103+
/// - the size of the already handled data when the whole range has been handled
104+
fn try_access<'a, F>(
105+
&'a self,
106+
count: usize,
107+
addr: GuestAddress,
108+
access: Permissions,
109+
f: F,
110+
) -> Result<usize>
111+
where
112+
F: FnMut(
113+
usize,
114+
usize,
115+
MemoryRegionAddress,
116+
&'a <Self::PhysicalMemory as GuestMemory>::R,
117+
) -> Result<usize>;
118+
119+
/// Returns a [`VolatileSlice`](struct.VolatileSlice.html) of `count` bytes starting at
120+
/// `addr`.
121+
///
122+
/// Note that because of the fragmented nature of virtual memory, it can easily happen that the
123+
/// range `[addr, addr + count)` is not backed by a continuous region in our own virtual
124+
/// memory, which will make generating the slice impossible.
125+
fn get_slice(
126+
&self,
127+
addr: GuestAddress,
128+
count: usize,
129+
access: Permissions,
130+
) -> Result<VolatileSlice<bitmap::MS<Self::PhysicalMemory>>>;
131+
132+
/// If this virtual memory is just a plain `GuestMemory` object underneath without an IOMMU
133+
/// translation layer in between, return that `GuestMemory` object.
134+
fn physical_memory(&self) -> Option<&Self::PhysicalMemory> {
135+
None
136+
}
137+
}
138+
139+
impl<M: GuestMemory> IoMemory for M {
140+
type PhysicalMemory = M;
141+
142+
fn range_accessible(&self, addr: GuestAddress, count: usize, _access: Permissions) -> bool {
143+
if count <= 1 {
144+
<M as GuestMemory>::address_in_range(self, addr)
145+
} else if let Some(end) = addr.0.checked_add(count as u64 - 1) {
146+
<M as GuestMemory>::address_in_range(self, addr)
147+
&& <M as GuestMemory>::address_in_range(self, GuestAddress(end))
148+
} else {
149+
false
150+
}
151+
}
152+
153+
fn try_access<'a, F>(
154+
&'a self,
155+
count: usize,
156+
addr: GuestAddress,
157+
_access: Permissions,
158+
f: F,
159+
) -> Result<usize>
160+
where
161+
F: FnMut(
162+
usize,
163+
usize,
164+
MemoryRegionAddress,
165+
&'a <Self::PhysicalMemory as GuestMemory>::R,
166+
) -> Result<usize>,
167+
{
168+
<M as GuestMemory>::try_access(self, count, addr, f)
169+
}
170+
171+
fn get_slice(
172+
&self,
173+
addr: GuestAddress,
174+
count: usize,
175+
_access: Permissions,
176+
) -> Result<VolatileSlice<bitmap::MS<M>>> {
177+
<M as GuestMemory>::get_slice(self, addr, count)
178+
}
179+
180+
fn physical_memory(&self) -> Option<&Self::PhysicalMemory> {
181+
Some(self)
182+
}
183+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ pub use guest_memory::{
5353
pub mod io;
5454
pub use io::{ReadVolatile, WriteVolatile};
5555

56+
pub mod io_memory;
57+
pub use io_memory::{IoMemory, Permissions};
58+
5659
#[cfg(feature = "backend-mmap")]
5760
pub mod mmap;
5861

0 commit comments

Comments
 (0)