virtio_device/
mem.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Structures for talking about memory shared between virtio devices and drivers
//!
//! Drivers, located in the guest, and devices, located in the host, operate with shared memory, but
//! may use different addresses to access it. All addresses published into the virtio data
//! structures are published by the driver and so refer to memory addresses as understood by the
//! driver.
//!
//! It is the responsibility of the host to know how to turn an address published by the driver,
//! a [`DriverRange`], into memory that can be ultimately be dereferenced and read/written to by
//! this device code, i.e. a [`DeviceRange`]. The [`DriverMem`] trait defines an interface for
//! performing this translation, although it is the responsibility of the application code to
//! provide and pass in implementations of this trait.
//!
//! [`DeviceRange`] is intended to describe valid ranges of memory that can be turned into pointers
//! through  [`try_ptr`](DeviceRange::try_ptr) and [`try_mut_ptr`](DeviceRange::try_mut_ptr) to
//! actually read and write.

use std::marker::PhantomData;
use std::mem;
use std::ops::Range;

/// Represents a range of memory as seen from the driver.
///
/// These ranges are only published by the driver and should never otherwise need to be created. The
/// The only meaningful thing that can be done with them is to use the [`DriverMem::translate`]
/// method to attempt to turn it into a [`DeviceRange`].
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DriverRange(pub Range<usize>);

impl DriverRange {
    /// Split the range at `offset` producing two new ranges.
    ///
    /// Returns `None` if `offset` is not in the range, otherwise produces the two ranges
    /// `[start.. start + offset)` and `[start + offset ..end)`.
    pub fn split_at(&self, offset: usize) -> Option<(Self, Self)> {
        if self.0.len() < offset {
            None
        } else {
            let mid = self.0.start + offset;
            Some((Self(self.0.start..mid), Self(mid..self.0.end)))
        }
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }
}

impl TryFrom<(u64, u32)> for DriverRange {
    type Error = ();

    fn try_from(range: (u64, u32)) -> Result<Self, Self::Error> {
        let (start, len) = range;
        let start = start as usize;
        let end = start.checked_add(len as usize).ok_or(())?;
        Ok(DriverRange(start..end))
    }
}

impl From<Range<usize>> for DriverRange {
    fn from(range: Range<usize>) -> Self {
        Self(range)
    }
}

/// Represents a range of memory as seen from the device.
///
/// A [`DeviceRange`] can be accessed through the [`try_ptr`](#try_ptr) and
/// [`try_mut_ptr`](#try_mut_ptr) methods. Although these functions are guaranteed to point to
/// valid memory, due to the requirements on [`new`](#new), raw pointers are still returned as the
/// memory may always be being modified in parallel by the guest and so references cannot be safely
/// created. The onus therefore is on the caller to access this memory in a way that is safe under
/// concurrent modifications.
///
/// Although there may be concurrent modifications, these are only from the guest, and it can be
/// assumed that a [`DeviceRange`] does not alias any other Rust objects from the heap, stack,
/// globals etc.
///
/// With the requirements on [`new`](#new) users of a [`DeviceRange`] can assume that pointers
/// returned from [`try_ptr`](#try_ptr) and [`try_mut_ptr`](#try_mut_ptr) are valid for reads and
/// writes and are correctly aligned. Further, it can be assumed that `ptr.offset(N)` is valid for
/// any `N < len() / size_of::<T>()`. These pointer are only valid as long as the original
/// [`DeviceRange`] is still alive.
///
/// The expected way to get [`DeviceRange`] is through [`DriverMem::translate`], and it is only
/// implementations of that trait that are expected to use [`new`](#new) and actually construct
/// a [`DeviceRange`].
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DeviceRange<'a>(Range<usize>, PhantomData<&'a ()>);

impl<'a> DeviceRange<'a> {
    /// Split the range at `offset` producing two new ranges.
    ///
    /// Returns `None` if `offset` is not in the range, otherwise produces the two ranges
    /// `[start.. start + offset)` and `[start + offset ..end)`.
    pub fn split_at(&self, offset: usize) -> Option<(Self, Self)> {
        if self.0.len() < offset {
            None
        } else {
            let mid = self.0.start + offset;
            // The returned ranges do not exceed the original range of self, and we return them for
            // the same lifetime `'a` as self. Therefore, as long as the original range was valid,
            // our produced ranges are too.
            unsafe { Some((Self::new(self.0.start..mid), Self::new(mid..self.0.end))) }
        }
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }
    /// Construct a new [`DeviceRange`].
    ///
    /// # Safety
    ///
    /// The provided range must be:
    /// - Valid memory that can be read or written to if it were cast to a pointer.
    /// - Not alias any Rust objects from the heap, stack, globals etc.
    /// - Remain valid for the lifetime `'a`.
    pub unsafe fn new(range: Range<usize>) -> Self {
        Self(range, PhantomData)
    }

    /// Attempt to get a pointer to a mutable `T` at the start of the range.
    ///
    /// Returns a `None` if the range is too small to represent a `T`, or if the start of the range
    /// has the wrong alignment. Although there ways to safely perform accesses to unaligned
    /// pointers, as virtio requires all objects to be placed with correct alignment any
    /// misalignment represents a configuration issue.
    ///
    /// The caller may assume that if a pointer is returned that it is valid for reads and writes of
    /// an object of size and alignment of T, however no guarantee is made on T being a copy-able
    /// object that can be safely read or written. Further, the returned pointer is valid only as
    /// long as the underlying [`DeviceRange`] is alive.
    pub fn try_mut_ptr<T>(&self) -> Option<*mut T> {
        if self.len() < mem::size_of::<T>() {
            return None;
        }
        let byte_ptr = self.0.start as *mut u8;
        if byte_ptr.align_offset(mem::align_of::<T>()) != 0 {
            return None;
        }
        Some(byte_ptr.cast())
    }

    /// Attempt to get a pointer to a `T` at the start of the range.
    ///
    /// See `try_mut_ptr`.
    pub fn try_ptr<T>(&self) -> Option<*const T> {
        self.try_mut_ptr().map(|x| x as *const T)
    }

    /// Retrieve the underlying range.
    pub fn get(&self) -> Range<usize> {
        self.0.clone()
    }
}

/// Provides interface for converting from a [`DriverRange`] to a [`DeviceRange`].
pub trait DriverMem {
    /// Attempt to turn a [`DriverRange`] into a [`DeviceRange`].
    ///
    /// May return `None` if [`DriverRange`] does not represent valid driver memory, otherwise should
    /// return the corresponding `DeviceRange`. [`DriverMem`] is borrowed for lifetime of the
    /// returned [`DeviceRange`] ensuring that any returned ranges do not outlive the backing
    /// memory.
    fn translate<'a>(&'a self, driver: DriverRange) -> Option<DeviceRange<'a>>;
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_split_at() {
        let range = DriverRange(10..20);
        assert_eq!(range.split_at(4), Some((DriverRange(10..14), DriverRange(14..20))));
        assert_eq!(range.split_at(9), Some((DriverRange(10..19), DriverRange(19..20))));
        let (r1, r2) = range.split_at(10).unwrap();
        assert_eq!(r1, DriverRange(10..20));
        assert_eq!(r2.len(), 0);
        let (r1, r2) = range.split_at(0).unwrap();
        assert_eq!(r1.len(), 0);
        assert_eq!(r2, DriverRange(10..20));
    }
    #[test]
    fn test_ptr() {
        // We build some invalid DeviceRanges here, but we know not to dereference any pointers from
        // them so this safe.
        unsafe {
            assert!(mem::align_of::<u64>() > 1);
            let range = DeviceRange::new(65..128);
            assert!(range.try_ptr::<u64>().is_none());
            let range = DeviceRange::new(64..128);
            assert!(range.try_ptr::<u64>().is_some());
            let range = DeviceRange::new(64..(64 + mem::size_of::<u64>() - 1));
            assert!(range.try_ptr::<u64>().is_none());
        }
    }
}