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}