virtio_device/mem.rs
1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Structures for talking about memory shared between virtio devices and drivers
6//!
7//! Drivers, located in the guest, and devices, located in the host, operate with shared memory, but
8//! may use different addresses to access it. All addresses published into the virtio data
9//! structures are published by the driver and so refer to memory addresses as understood by the
10//! driver.
11//!
12//! It is the responsibility of the host to know how to turn an address published by the driver,
13//! a [`DriverRange`], into memory that can be ultimately be dereferenced and read/written to by
14//! this device code, i.e. a [`DeviceRange`]. The [`DriverMem`] trait defines an interface for
15//! performing this translation, although it is the responsibility of the application code to
16//! provide and pass in implementations of this trait.
17//!
18//! [`DeviceRange`] is intended to describe valid ranges of memory that can be turned into pointers
19//! through [`try_ptr`](DeviceRange::try_ptr) and [`try_mut_ptr`](DeviceRange::try_mut_ptr) to
20//! actually read and write.
21
22use std::marker::PhantomData;
23use std::mem;
24use std::ops::Range;
25
26/// Represents a range of memory as seen from the driver.
27///
28/// These ranges are only published by the driver and should never otherwise need to be created. The
29/// The only meaningful thing that can be done with them is to use the [`DriverMem::translate`]
30/// method to attempt to turn it into a [`DeviceRange`].
31#[derive(Debug, Clone, Eq, PartialEq)]
32pub struct DriverRange(pub Range<usize>);
33
34impl DriverRange {
35 /// Split the range at `offset` producing two new ranges.
36 ///
37 /// Returns `None` if `offset` is not in the range, otherwise produces the two ranges
38 /// `[start.. start + offset)` and `[start + offset ..end)`.
39 pub fn split_at(&self, offset: usize) -> Option<(Self, Self)> {
40 if self.0.len() < offset {
41 None
42 } else {
43 let mid = self.0.start + offset;
44 Some((Self(self.0.start..mid), Self(mid..self.0.end)))
45 }
46 }
47
48 pub fn len(&self) -> usize {
49 self.0.len()
50 }
51}
52
53impl TryFrom<(u64, u32)> for DriverRange {
54 type Error = ();
55
56 fn try_from(range: (u64, u32)) -> Result<Self, Self::Error> {
57 let (start, len) = range;
58 let start = start as usize;
59 let end = start.checked_add(len as usize).ok_or(())?;
60 Ok(DriverRange(start..end))
61 }
62}
63
64impl From<Range<usize>> for DriverRange {
65 fn from(range: Range<usize>) -> Self {
66 Self(range)
67 }
68}
69
70/// Represents a range of memory as seen from the device.
71///
72/// A [`DeviceRange`] can be accessed through the [`try_ptr`](#try_ptr) and
73/// [`try_mut_ptr`](#try_mut_ptr) methods. Although these functions are guaranteed to point to
74/// valid memory, due to the requirements on [`new`](#new), raw pointers are still returned as the
75/// memory may always be being modified in parallel by the guest and so references cannot be safely
76/// created. The onus therefore is on the caller to access this memory in a way that is safe under
77/// concurrent modifications.
78///
79/// Although there may be concurrent modifications, these are only from the guest, and it can be
80/// assumed that a [`DeviceRange`] does not alias any other Rust objects from the heap, stack,
81/// globals etc.
82///
83/// With the requirements on [`new`](#new) users of a [`DeviceRange`] can assume that pointers
84/// returned from [`try_ptr`](#try_ptr) and [`try_mut_ptr`](#try_mut_ptr) are valid for reads and
85/// writes and are correctly aligned. Further, it can be assumed that `ptr.offset(N)` is valid for
86/// any `N < len() / size_of::<T>()`. These pointer are only valid as long as the original
87/// [`DeviceRange`] is still alive.
88///
89/// The expected way to get [`DeviceRange`] is through [`DriverMem::translate`], and it is only
90/// implementations of that trait that are expected to use [`new`](#new) and actually construct
91/// a [`DeviceRange`].
92#[derive(Debug, Clone, Eq, PartialEq)]
93pub struct DeviceRange<'a>(Range<usize>, PhantomData<&'a ()>);
94
95impl<'a> DeviceRange<'a> {
96 /// Split the range at `offset` producing two new ranges.
97 ///
98 /// Returns `None` if `offset` is not in the range, otherwise produces the two ranges
99 /// `[start.. start + offset)` and `[start + offset ..end)`.
100 pub fn split_at(&self, offset: usize) -> Option<(Self, Self)> {
101 if self.0.len() < offset {
102 None
103 } else {
104 let mid = self.0.start + offset;
105 // The returned ranges do not exceed the original range of self, and we return them for
106 // the same lifetime `'a` as self. Therefore, as long as the original range was valid,
107 // our produced ranges are too.
108 unsafe { Some((Self::new(self.0.start..mid), Self::new(mid..self.0.end))) }
109 }
110 }
111
112 pub fn len(&self) -> usize {
113 self.0.len()
114 }
115 /// Construct a new [`DeviceRange`].
116 ///
117 /// # Safety
118 ///
119 /// The provided range must be:
120 /// - Valid memory that can be read or written to if it were cast to a pointer.
121 /// - Not alias any Rust objects from the heap, stack, globals etc.
122 /// - Remain valid for the lifetime `'a`.
123 pub unsafe fn new(range: Range<usize>) -> Self {
124 Self(range, PhantomData)
125 }
126
127 /// Attempt to get a pointer to a mutable `T` at the start of the range.
128 ///
129 /// Returns a `None` if the range is too small to represent a `T`, or if the start of the range
130 /// has the wrong alignment. Although there ways to safely perform accesses to unaligned
131 /// pointers, as virtio requires all objects to be placed with correct alignment any
132 /// misalignment represents a configuration issue.
133 ///
134 /// The caller may assume that if a pointer is returned that it is valid for reads and writes of
135 /// an object of size and alignment of T, however no guarantee is made on T being a copy-able
136 /// object that can be safely read or written. Further, the returned pointer is valid only as
137 /// long as the underlying [`DeviceRange`] is alive.
138 pub fn try_mut_ptr<T>(&self) -> Option<*mut T> {
139 if self.len() < mem::size_of::<T>() {
140 return None;
141 }
142 let byte_ptr = self.0.start as *mut u8;
143 if byte_ptr.align_offset(mem::align_of::<T>()) != 0 {
144 return None;
145 }
146 Some(byte_ptr.cast())
147 }
148
149 /// Attempt to get a pointer to a `T` at the start of the range.
150 ///
151 /// See `try_mut_ptr`.
152 pub fn try_ptr<T>(&self) -> Option<*const T> {
153 self.try_mut_ptr().map(|x| x as *const T)
154 }
155
156 /// Retrieve the underlying range.
157 pub fn get(&self) -> Range<usize> {
158 self.0.clone()
159 }
160}
161
162/// Provides interface for converting from a [`DriverRange`] to a [`DeviceRange`].
163pub trait DriverMem {
164 /// Attempt to turn a [`DriverRange`] into a [`DeviceRange`].
165 ///
166 /// May return `None` if [`DriverRange`] does not represent valid driver memory, otherwise should
167 /// return the corresponding `DeviceRange`. [`DriverMem`] is borrowed for lifetime of the
168 /// returned [`DeviceRange`] ensuring that any returned ranges do not outlive the backing
169 /// memory.
170 fn translate<'a>(&'a self, driver: DriverRange) -> Option<DeviceRange<'a>>;
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 #[test]
177 fn test_split_at() {
178 let range = DriverRange(10..20);
179 assert_eq!(range.split_at(4), Some((DriverRange(10..14), DriverRange(14..20))));
180 assert_eq!(range.split_at(9), Some((DriverRange(10..19), DriverRange(19..20))));
181 let (r1, r2) = range.split_at(10).unwrap();
182 assert_eq!(r1, DriverRange(10..20));
183 assert_eq!(r2.len(), 0);
184 let (r1, r2) = range.split_at(0).unwrap();
185 assert_eq!(r1.len(), 0);
186 assert_eq!(r2, DriverRange(10..20));
187 }
188 #[test]
189 fn test_ptr() {
190 // We build some invalid DeviceRanges here, but we know not to dereference any pointers from
191 // them so this safe.
192 unsafe {
193 assert!(mem::align_of::<u64>() > 1);
194 let range = DeviceRange::new(65..128);
195 assert!(range.try_ptr::<u64>().is_none());
196 let range = DeviceRange::new(64..128);
197 assert!(range.try_ptr::<u64>().is_some());
198 let range = DeviceRange::new(64..(64 + mem::size_of::<u64>() - 1));
199 assert!(range.try_ptr::<u64>().is_none());
200 }
201 }
202}