summary refs log tree commit diff
path: root/data_model
diff options
context:
space:
mode:
Diffstat (limited to 'data_model')
-rw-r--r--data_model/Cargo.toml1
-rw-r--r--data_model/src/volatile_memory.rs252
2 files changed, 149 insertions, 104 deletions
diff --git a/data_model/Cargo.toml b/data_model/Cargo.toml
index b1447a6..3909956 100644
--- a/data_model/Cargo.toml
+++ b/data_model/Cargo.toml
@@ -7,5 +7,6 @@ include = ["src/**/*", "Cargo.toml"]
 
 [dependencies]
 assertions = { path = "../assertions" } # provided by ebuild
+libc = "*"
 
 [workspace]
diff --git a/data_model/src/volatile_memory.rs b/data_model/src/volatile_memory.rs
index d834f0b..026be71 100644
--- a/data_model/src/volatile_memory.rs
+++ b/data_model/src/volatile_memory.rs
@@ -20,21 +20,25 @@
 //! not reordered or elided the access.
 
 use std::cmp::min;
-use std::fmt::{self, Display};
+use std::ffi::c_void;
+use std::fmt::{self, Debug, Display};
 use std::marker::PhantomData;
 use std::mem::size_of;
 use std::ptr::{copy, null_mut, read_volatile, write_bytes, write_volatile};
 use std::result;
+use std::slice;
 use std::usize;
 
+use libc::iovec;
+
 use crate::DataInit;
 
 #[derive(Eq, PartialEq, Debug)]
 pub enum VolatileMemoryError {
     /// `addr` is out of bounds of the volatile memory slice.
-    OutOfBounds { addr: u64 },
-    /// Taking a slice at `base` with `offset` would overflow `u64`.
-    Overflow { base: u64, offset: u64 },
+    OutOfBounds { addr: usize },
+    /// Taking a slice at `base` with `offset` would overflow `usize`.
+    Overflow { base: usize, offset: usize },
 }
 
 impl Display for VolatileMemoryError {
@@ -65,7 +69,7 @@ type Result<T> = VolatileMemoryResult<T>;
 ///
 /// ```
 /// # use data_model::*;
-/// # fn get_slice(offset: u64, count: u64) -> VolatileMemoryResult<()> {
+/// # fn get_slice(offset: usize, count: usize) -> VolatileMemoryResult<()> {
 ///   let mem_end = calc_offset(offset, count)?;
 ///   if mem_end > 100 {
 ///       return Err(VolatileMemoryError::OutOfBounds{addr: mem_end});
@@ -73,7 +77,7 @@ type Result<T> = VolatileMemoryResult<T>;
 /// # Ok(())
 /// # }
 /// ```
-pub fn calc_offset(base: u64, offset: u64) -> Result<u64> {
+pub fn calc_offset(base: usize, offset: usize) -> Result<usize> {
     match base.checked_add(offset) {
         None => Err(Error::Overflow { base, offset }),
         Some(m) => Ok(m),
@@ -84,109 +88,149 @@ pub fn calc_offset(base: u64, offset: u64) -> Result<u64> {
 pub trait VolatileMemory {
     /// Gets a slice of memory at `offset` that is `count` bytes in length and supports volatile
     /// access.
-    fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice>;
+    fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice>;
 
     /// Gets a `VolatileRef` at `offset`.
-    fn get_ref<T: DataInit>(&self, offset: u64) -> Result<VolatileRef<T>> {
-        let slice = self.get_slice(offset, size_of::<T>() as u64)?;
+    fn get_ref<T: DataInit>(&self, offset: usize) -> Result<VolatileRef<T>> {
+        let slice = self.get_slice(offset, size_of::<T>())?;
         Ok(VolatileRef {
-            addr: slice.addr as *mut T,
+            addr: slice.as_mut_ptr() as *mut T,
             phantom: PhantomData,
         })
     }
 }
 
-impl<'a> VolatileMemory for &'a mut [u8] {
-    fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> {
-        let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.len() as u64 {
-            return Err(Error::OutOfBounds { addr: mem_end });
-        }
-        Ok(unsafe { VolatileSlice::new((self.as_ptr() as u64 + offset) as *mut _, count) })
-    }
-}
-
-/// A slice of raw memory that supports volatile access.
-#[derive(Copy, Clone, Debug)]
+/// A slice of raw memory that supports volatile access. Like `std::io::IoSliceMut`, this type is
+/// guaranteed to be ABI-compatible with `libc::iovec` but unlike `IoSliceMut`, it doesn't
+/// automatically deref to `&mut [u8]`.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
 pub struct VolatileSlice<'a> {
-    addr: *mut u8,
-    size: u64,
-    phantom: PhantomData<&'a u8>,
+    iov: iovec,
+    phantom: PhantomData<&'a mut [u8]>,
 }
 
 impl<'a> Default for VolatileSlice<'a> {
     fn default() -> VolatileSlice<'a> {
         VolatileSlice {
-            addr: null_mut(),
-            size: 0,
+            iov: iovec {
+                iov_base: null_mut(),
+                iov_len: 0,
+            },
             phantom: PhantomData,
         }
     }
 }
 
+struct DebugIovec(iovec);
+impl Debug for DebugIovec {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("iovec")
+            .field("iov_base", &self.0.iov_base)
+            .field("iov_len", &self.0.iov_len)
+            .finish()
+    }
+}
+
+impl<'a> Debug for VolatileSlice<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_struct("VolatileSlice")
+            .field("iov", &DebugIovec(self.iov))
+            .field("phantom", &self.phantom)
+            .finish()
+    }
+}
+
 impl<'a> VolatileSlice<'a> {
     /// Creates a slice of raw memory that must support volatile access.
+    pub fn new(buf: &mut [u8]) -> VolatileSlice {
+        VolatileSlice {
+            iov: iovec {
+                iov_base: buf.as_mut_ptr() as *mut c_void,
+                iov_len: buf.len(),
+            },
+            phantom: PhantomData,
+        }
+    }
+
+    /// Creates a `VolatileSlice` from a pointer and a length.
     ///
-    /// To use this safely, the caller must guarantee that the memory at `addr` is `size` bytes long
-    /// and is available for the duration of the lifetime of the new `VolatileSlice`. The caller
-    /// must also guarantee that all other users of the given chunk of memory are using volatile
-    /// accesses.
-    pub unsafe fn new(addr: *mut u8, size: u64) -> VolatileSlice<'a> {
+    /// # Safety
+    ///
+    /// In order to use this method safely, `addr` must be valid for reads and writes of `len` bytes
+    /// and should live for the entire duration of lifetime `'a`.
+    pub unsafe fn from_raw_parts(addr: *mut u8, len: usize) -> VolatileSlice<'a> {
         VolatileSlice {
-            addr,
-            size,
+            iov: iovec {
+                iov_base: addr as *mut c_void,
+                iov_len: len,
+            },
             phantom: PhantomData,
         }
     }
 
-    /// Gets the address of this slice's memory.
-    pub fn as_ptr(&self) -> *mut u8 {
-        self.addr
+    /// Gets a const pointer to this slice's memory.
+    pub fn as_ptr(&self) -> *const u8 {
+        self.iov.iov_base as *const u8
+    }
+
+    /// Gets a mutable pointer to this slice's memory.
+    pub fn as_mut_ptr(&self) -> *mut u8 {
+        self.iov.iov_base as *mut u8
     }
 
     /// Gets the size of this slice.
-    pub fn size(&self) -> u64 {
-        self.size
+    pub fn size(&self) -> usize {
+        self.iov.iov_len
+    }
+
+    /// Returns this `VolatileSlice` as an iovec.
+    pub fn as_iovec(&self) -> iovec {
+        self.iov
+    }
+
+    /// Converts a slice of `VolatileSlice`s into a slice of `iovec`s.
+    pub fn as_iovecs<'slice>(iovs: &'slice [VolatileSlice<'_>]) -> &'slice [iovec] {
+        // Safe because `VolatileSlice` is ABI-compatible with `iovec`.
+        unsafe { slice::from_raw_parts(iovs.as_ptr() as *const iovec, iovs.len()) }
     }
 
     /// Creates a copy of this slice with the address increased by `count` bytes, and the size
     /// reduced by `count` bytes.
-    pub fn offset(self, count: u64) -> Result<VolatileSlice<'a>> {
-        let new_addr =
-            (self.addr as u64)
-                .checked_add(count)
-                .ok_or(VolatileMemoryError::Overflow {
-                    base: self.addr as u64,
-                    offset: count,
-                })?;
-        if new_addr > usize::MAX as u64 {
-            return Err(VolatileMemoryError::Overflow {
-                base: self.addr as u64,
+    pub fn offset(self, count: usize) -> Result<VolatileSlice<'a>> {
+        let new_addr = (self.as_mut_ptr() as usize).checked_add(count).ok_or(
+            VolatileMemoryError::Overflow {
+                base: self.as_mut_ptr() as usize,
                 offset: count,
-            });
-        }
+            },
+        )?;
         let new_size = self
-            .size
+            .size()
             .checked_sub(count)
             .ok_or(VolatileMemoryError::OutOfBounds { addr: new_addr })?;
+
         // Safe because the memory has the same lifetime and points to a subset of the memory of the
         // original slice.
-        unsafe { Ok(VolatileSlice::new(new_addr as *mut u8, new_size)) }
+        unsafe { Ok(VolatileSlice::from_raw_parts(new_addr as *mut u8, new_size)) }
     }
 
     /// Similar to `get_slice` but the returned slice outlives this slice.
     ///
     /// The returned slice's lifetime is still limited by the underlying data's lifetime.
-    pub fn sub_slice(self, offset: u64, count: u64) -> Result<VolatileSlice<'a>> {
+    pub fn sub_slice(self, offset: usize, count: usize) -> Result<VolatileSlice<'a>> {
         let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.size {
+        if mem_end > self.size() {
             return Err(Error::OutOfBounds { addr: mem_end });
         }
-        Ok(VolatileSlice {
-            addr: (self.addr as u64 + offset) as *mut _,
-            size: count,
-            phantom: PhantomData,
-        })
+        let new_addr = (self.as_mut_ptr() as usize).checked_add(offset).ok_or(
+            VolatileMemoryError::Overflow {
+                base: self.as_mut_ptr() as usize,
+                offset,
+            },
+        )?;
+
+        // Safe because we have verified that the new memory is a subset of the original slice.
+        Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
     }
 
     /// Sets each byte of this slice with the given byte, similar to `memset`.
@@ -196,13 +240,12 @@ impl<'a> VolatileSlice<'a> {
     /// # Examples
     ///
     /// ```
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::VolatileSlice;
     /// # fn test_write_45() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// vslice.write_bytes(45);
-    /// for &mut v in mem_ref {
+    /// for &v in &mem[..] {
     ///     assert_eq!(v, 45);
     /// }
     /// # Ok(())
@@ -210,7 +253,7 @@ impl<'a> VolatileSlice<'a> {
     pub fn write_bytes(&self, value: u8) {
         // Safe because the memory is valid and needs only byte alignment.
         unsafe {
-            write_bytes(self.as_ptr(), value, self.size as usize);
+            write_bytes(self.as_mut_ptr(), value, self.size());
         }
     }
 
@@ -224,11 +267,10 @@ impl<'a> VolatileSlice<'a> {
     /// ```
     /// # use std::fs::File;
     /// # use std::path::Path;
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::VolatileSlice;
     /// # fn test_write_null() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// let mut buf = [5u8; 16];
     /// vslice.copy_to(&mut buf[..]);
     /// for v in &buf[..] {
@@ -241,8 +283,8 @@ impl<'a> VolatileSlice<'a> {
     where
         T: DataInit,
     {
-        let mut addr = self.addr;
-        for v in buf.iter_mut().take(self.size as usize / size_of::<T>()) {
+        let mut addr = self.as_mut_ptr() as *const u8;
+        for v in buf.iter_mut().take(self.size() / size_of::<T>()) {
             unsafe {
                 *v = read_volatile(addr as *const T);
                 addr = addr.add(size_of::<T>());
@@ -256,18 +298,21 @@ impl<'a> VolatileSlice<'a> {
     /// # Examples
     ///
     /// ```
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::{VolatileMemory, VolatileSlice};
     /// # fn test_write_null() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// vslice.copy_to_volatile_slice(vslice.get_slice(16, 16).map_err(|_| ())?);
     /// # Ok(())
     /// # }
     /// ```
     pub fn copy_to_volatile_slice(&self, slice: VolatileSlice) {
         unsafe {
-            copy(self.addr, slice.addr, min(self.size, slice.size) as usize);
+            copy(
+                self.as_mut_ptr() as *const u8,
+                slice.as_mut_ptr(),
+                min(self.size(), slice.size()),
+            );
         }
     }
 
@@ -281,11 +326,10 @@ impl<'a> VolatileSlice<'a> {
     /// ```
     /// # use std::fs::File;
     /// # use std::path::Path;
-    /// # use data_model::VolatileMemory;
+    /// # use data_model::{VolatileMemory, VolatileSlice};
     /// # fn test_write_null() -> Result<(), ()> {
     /// let mut mem = [0u8; 32];
-    /// let mem_ref = &mut mem[..];
-    /// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
+    /// let vslice = VolatileSlice::new(&mut mem[..]);
     /// let buf = [5u8; 64];
     /// vslice.copy_from(&buf[..]);
     /// for i in 0..4 {
@@ -298,8 +342,8 @@ impl<'a> VolatileSlice<'a> {
     where
         T: DataInit,
     {
-        let mut addr = self.addr;
-        for &v in buf.iter().take(self.size as usize / size_of::<T>()) {
+        let mut addr = self.as_mut_ptr();
+        for &v in buf.iter().take(self.size() / size_of::<T>()) {
             unsafe {
                 write_volatile(addr as *mut T, v);
                 addr = addr.add(size_of::<T>());
@@ -309,16 +353,8 @@ impl<'a> VolatileSlice<'a> {
 }
 
 impl<'a> VolatileMemory for VolatileSlice<'a> {
-    fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> {
-        let mem_end = calc_offset(offset, count)?;
-        if mem_end > self.size {
-            return Err(Error::OutOfBounds { addr: mem_end });
-        }
-        Ok(VolatileSlice {
-            addr: (self.addr as u64 + offset) as *mut _,
-            size: count,
-            phantom: PhantomData,
-        })
+    fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
+        self.sub_slice(offset, count)
     }
 }
 
@@ -358,7 +394,7 @@ impl<'a, T: DataInit> VolatileRef<'a, T> {
     }
 
     /// Gets the address of this slice's memory.
-    pub fn as_ptr(&self) -> *mut T {
+    pub fn as_mut_ptr(&self) -> *mut T {
         self.addr
     }
 
@@ -370,10 +406,10 @@ impl<'a, T: DataInit> VolatileRef<'a, T> {
     /// # use std::mem::size_of;
     /// # use data_model::VolatileRef;
     ///   let v_ref = unsafe { VolatileRef::new(0 as *mut u32) };
-    ///   assert_eq!(v_ref.size(), size_of::<u32>() as u64);
+    ///   assert_eq!(v_ref.size(), size_of::<u32>());
     /// ```
-    pub fn size(&self) -> u64 {
-        size_of::<T>() as u64
+    pub fn size(&self) -> usize {
+        size_of::<T>()
     }
 
     /// Does a volatile write of the value `v` to the address of this ref.
@@ -393,7 +429,7 @@ impl<'a, T: DataInit> VolatileRef<'a, T> {
 
     /// Converts this `T` reference to a raw slice with the same size and address.
     pub fn to_slice(&self) -> VolatileSlice<'a> {
-        unsafe { VolatileSlice::new(self.addr as *mut u8, size_of::<T>() as u64) }
+        unsafe { VolatileSlice::from_raw_parts(self.as_mut_ptr() as *mut u8, self.size()) }
     }
 }
 
@@ -418,19 +454,27 @@ mod tests {
     }
 
     impl VolatileMemory for VecMem {
-        fn get_slice(&self, offset: u64, count: u64) -> Result<VolatileSlice> {
+        fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
             let mem_end = calc_offset(offset, count)?;
-            if mem_end > self.mem.len() as u64 {
+            if mem_end > self.mem.len() {
                 return Err(Error::OutOfBounds { addr: mem_end });
             }
-            Ok(unsafe { VolatileSlice::new((self.mem.as_ptr() as u64 + offset) as *mut _, count) })
+
+            let new_addr = (self.mem.as_ptr() as usize).checked_add(offset).ok_or(
+                VolatileMemoryError::Overflow {
+                    base: self.mem.as_ptr() as usize,
+                    offset,
+                },
+            )?;
+
+            Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
         }
     }
 
     #[test]
     fn ref_store() {
         let mut a = [0u8; 1];
-        let a_ref = &mut a[..];
+        let a_ref = VolatileSlice::new(&mut a[..]);
         let v_ref = a_ref.get_ref(0).unwrap();
         v_ref.store(2u8);
         assert_eq!(a[0], 2);
@@ -440,7 +484,7 @@ mod tests {
     fn ref_load() {
         let mut a = [5u8; 1];
         {
-            let a_ref = &mut a[..];
+            let a_ref = VolatileSlice::new(&mut a[..]);
             let c = {
                 let v_ref = a_ref.get_ref::<u8>(0).unwrap();
                 assert_eq!(v_ref.load(), 5u8);
@@ -457,11 +501,11 @@ mod tests {
     #[test]
     fn ref_to_slice() {
         let mut a = [1u8; 5];
-        let a_ref = &mut a[..];
+        let a_ref = VolatileSlice::new(&mut a[..]);
         let v_ref = a_ref.get_ref(1).unwrap();
         v_ref.store(0x12345678u32);
         let ref_slice = v_ref.to_slice();
-        assert_eq!(v_ref.as_ptr() as u64, ref_slice.as_ptr() as u64);
+        assert_eq!(v_ref.as_mut_ptr() as usize, ref_slice.as_mut_ptr() as usize);
         assert_eq!(v_ref.size(), ref_slice.size());
     }
 
@@ -506,7 +550,7 @@ mod tests {
 
     #[test]
     fn slice_overflow_error() {
-        use std::u64::MAX;
+        use std::usize::MAX;
         let a = VecMem::new(1);
         let res = a.get_slice(MAX, 1).unwrap_err();
         assert_eq!(
@@ -528,7 +572,7 @@ mod tests {
 
     #[test]
     fn ref_overflow_error() {
-        use std::u64::MAX;
+        use std::usize::MAX;
         let a = VecMem::new(1);
         let res = a.get_ref::<u8>(MAX).unwrap_err();
         assert_eq!(