diff options
Diffstat (limited to 'data_model')
-rw-r--r-- | data_model/Cargo.toml | 1 | ||||
-rw-r--r-- | data_model/src/volatile_memory.rs | 252 |
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!( |