Skip to main content

vmbus_ring/
gparange.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use guestmem_core::ranges::PagedRange;
7use thiserror::Error;
8use zerocopy::FromBytes;
9use zerocopy::Immutable;
10use zerocopy::IntoBytes;
11use zerocopy::KnownLayout;
12
13const PAGE_SIZE: usize = 4096;
14
15#[repr(C)]
16#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
17pub struct GpaRange {
18    pub len: u32,
19    pub offset: u32,
20}
21
22/// Validates that `buf` contains `count` valid GPA ranges. Returns the number
23/// of `u64` entries actually used to describe those ranges.
24pub fn validate_gpa_ranges(count: usize, buf: &[u64]) -> Result<usize, Error> {
25    let mut rem: &[u64] = buf;
26    for _ in 0..count {
27        let (_, rest) = parse(rem)?;
28        rem = rest;
29    }
30    Ok(buf.len() - rem.len())
31}
32
33#[derive(Debug, Default, Clone)]
34pub struct MultiPagedRangeBuf {
35    /// The buffer used to store the range data, concatenated. Each range
36    /// consists of a [`GpaRange`] header followed by the list of GPNs.
37    /// Note that `size_of::<GpaRange>() == size_of::<u64>()`.
38    buf: Box<[u64]>,
39    /// The number of u64 elements in the buffer that are valid. Data after this
40    /// point is initialized from Rust's point of view but is not logically part of
41    /// the ranges.
42    valid: usize,
43    /// The number of ranges stored in the buffer.
44    count: usize,
45}
46
47impl MultiPagedRangeBuf {
48    pub fn from_range_buffer(count: usize, mut buf: Vec<u64>) -> Result<Self, Error> {
49        let valid = validate_gpa_ranges(count, buf.as_ref())?;
50        buf.truncate(valid);
51        Ok(MultiPagedRangeBuf {
52            buf: buf.into_boxed_slice(),
53            valid,
54            count,
55        })
56    }
57
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    pub fn iter(&self) -> MultiPagedRangeIter<'_> {
63        MultiPagedRangeIter {
64            buf: self.buf.as_ref(),
65            count: self.count,
66        }
67    }
68
69    pub fn range_count(&self) -> usize {
70        self.count
71    }
72
73    pub fn first(&self) -> Option<PagedRange<'_>> {
74        self.iter().next()
75    }
76
77    /// Validates that this multi range consists of exactly one range that is
78    /// page aligned. Returns that range.
79    pub fn contiguous_aligned(&self) -> Option<PagedRange<'_>> {
80        if self.count != 1 {
81            return None;
82        }
83        let first = self.first()?;
84        if first.offset() != 0 || first.len() % PAGE_SIZE != 0 {
85            return None;
86        }
87        Some(first)
88    }
89
90    pub fn range_buffer(&self) -> &[u64] {
91        &self.buf[..self.valid]
92    }
93
94    /// Clears the buffer and resets the range count to zero.
95    pub fn clear(&mut self) {
96        self.valid = 0;
97        self.count = 0;
98    }
99
100    fn ensure_space(&mut self, additional: usize) -> &mut [u64] {
101        let required = self.valid + additional;
102        if required > self.buf.len() {
103            self.resize_buffer(required);
104        }
105        &mut self.buf[self.valid..required]
106    }
107
108    #[cold]
109    fn resize_buffer(&mut self, new_size: usize) {
110        // Use `Vec`'s resizing logic to get appropriate growth behavior, but
111        // initialize all the data to make updating it easier.
112        let mut buf: Vec<u64> = core::mem::take(&mut self.buf).into();
113        buf.resize(new_size, 0);
114        // Initialize the rest of the capacity that `Vec` allocated.
115        buf.resize(buf.capacity(), 0);
116        self.buf = buf.into_boxed_slice();
117    }
118
119    /// Appends a new paged range to the buffer.
120    pub fn push_range(&mut self, range: PagedRange<'_>) {
121        let len = 1 + range.gpns().len();
122        let buf = self.ensure_space(len);
123        let hdr = GpaRange {
124            len: range.len() as u32,
125            offset: range.offset() as u32,
126        };
127        buf[0] = zerocopy::transmute!(hdr);
128        buf[1..].copy_from_slice(range.gpns());
129        self.count += 1;
130        self.valid += len;
131    }
132
133    /// Attempts to extend the buffer by `count` ranges, requiring `len` u64
134    /// entries in total. `f` is called to fill in the newly allocated
135    /// buffer space.
136    ///
137    /// If `f` returns an error, the buffer is restored to its
138    /// previous state and the error is propagated. If `f` returns `Ok(())`,
139    /// the newly added ranges are validated; if validation fails, the buffer
140    /// is restored and the validation error is returned inside `Ok(Err(_))`.
141    pub fn try_extend_with<E>(
142        &mut self,
143        len: usize,
144        count: usize,
145        f: impl FnOnce(&mut [u64]) -> Result<(), E>,
146    ) -> Result<Result<(), Error>, E> {
147        let buf = self.ensure_space(len);
148        f(buf)?;
149        let valid_len = match validate_gpa_ranges(count, buf) {
150            Ok(v) => v,
151            Err(e) => return Ok(Err(e)),
152        };
153        // Now that validation succeeded, update the buffer state. Failure
154        // before this may have expanded the buffer but did not affect the
155        // visible behavior of this object.
156        self.valid += valid_len;
157        self.count += count;
158        Ok(Ok(()))
159    }
160}
161
162impl<'a> IntoIterator for &'a MultiPagedRangeBuf {
163    type Item = PagedRange<'a>;
164    type IntoIter = MultiPagedRangeIter<'a>;
165
166    fn into_iter(self) -> Self::IntoIter {
167        self.iter()
168    }
169}
170
171impl<'a> FromIterator<PagedRange<'a>> for MultiPagedRangeBuf {
172    fn from_iter<I: IntoIterator<Item = PagedRange<'a>>>(iter: I) -> MultiPagedRangeBuf {
173        let mut this = MultiPagedRangeBuf::new();
174        for range in iter {
175            this.push_range(range);
176        }
177        this
178    }
179}
180
181#[derive(Clone, Debug)]
182pub struct MultiPagedRangeIter<'a> {
183    buf: &'a [u64],
184    count: usize,
185}
186
187impl<'a> Iterator for MultiPagedRangeIter<'a> {
188    type Item = PagedRange<'a>;
189
190    fn next(&mut self) -> Option<Self::Item> {
191        if self.count == 0 {
192            return None;
193        }
194        let hdr = GpaRange::read_from_prefix(self.buf[0].as_bytes())
195            .unwrap()
196            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
197        let page_count = ((hdr.offset + hdr.len) as usize).div_ceil(PAGE_SIZE); // N.B. already validated
198        let (this, rest) = self.buf.split_at(page_count + 1);
199        let range = PagedRange::new(hdr.offset as usize, hdr.len as usize, &this[1..]).unwrap();
200        self.count -= 1;
201        self.buf = rest;
202        Some(range)
203    }
204}
205
206#[derive(Debug, Error)]
207pub enum Error {
208    #[error("empty range")]
209    EmptyRange,
210    #[error("empty byte count")]
211    EmptyByteCount,
212    #[error("range too small")]
213    RangeTooSmall,
214    #[error("byte offset too large")]
215    OffsetTooLarge,
216    #[error("integer overflow")]
217    Overflow,
218}
219
220fn parse(buf: &[u64]) -> Result<(PagedRange<'_>, &[u64]), Error> {
221    let (hdr, gpas) = buf.split_first().ok_or(Error::EmptyRange)?;
222    let byte_count = *hdr as u32;
223    if byte_count == 0 {
224        return Err(Error::EmptyByteCount);
225    }
226    let byte_offset = (*hdr >> 32) as u32;
227    if byte_offset > 0xfff {
228        return Err(Error::OffsetTooLarge);
229    }
230    let pages = (byte_count
231        .checked_add(4095)
232        .ok_or(Error::Overflow)?
233        .checked_add(byte_offset)
234        .ok_or(Error::Overflow)?) as usize
235        / PAGE_SIZE;
236    if gpas.len() < pages {
237        return Err(Error::RangeTooSmall);
238    }
239    let (gpas, rest) = gpas.split_at(pages);
240    assert!(!gpas.is_empty());
241    Ok((
242        PagedRange::new(byte_offset as usize, byte_count as usize, gpas)
243            .expect("already validated"),
244        rest,
245    ))
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn large_offset() {
254        // Encode a header with offset having bits above the 12-bit page offset (0x1000)
255        let hdr = GpaRange {
256            len: 1,
257            offset: 0x1000,
258        };
259        let buf = vec![
260            u64::from_le_bytes(hdr.as_bytes().try_into().unwrap()),
261            0xdead_beef,
262        ];
263
264        // validate() should not accept the buffer
265        let err = MultiPagedRangeBuf::from_range_buffer(1, buf).unwrap_err();
266        assert!(matches!(err, Error::OffsetTooLarge));
267    }
268}