#![deny(missing_docs)]
use std::ops::Range;
use std::ptr::copy_nonoverlapping;
use base::error;
use base::linux::MemoryMappingUnix;
use base::MemoryMapping;
use base::MemoryMappingBuilder;
use base::MmapError;
use base::SharedMemory;
use base::VolatileMemory;
use base::VolatileMemoryError;
use base::VolatileSlice;
use thiserror::Error as ThisError;
use crate::pagesize::pages_to_bytes;
use crate::present_list::PresentList;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(ThisError, Debug)]
pub enum Error {
#[error("failed to mmap operation: {0}")]
Mmap(MmapError),
#[error("failed to volatile memory operation: {0}")]
VolatileMemory(VolatileMemoryError),
#[error("index is out of range")]
OutOfRange,
}
impl From<MmapError> for Error {
fn from(e: MmapError) -> Self {
Self::Mmap(e)
}
}
impl From<VolatileMemoryError> for Error {
fn from(e: VolatileMemoryError) -> Self {
Self::VolatileMemory(e)
}
}
pub struct CopyOp {
src_addr: *const u8,
dst_addr: *mut u8,
size: usize,
}
unsafe impl Send for CopyOp {}
impl CopyOp {
pub fn execute(self) {
unsafe {
copy_nonoverlapping(self.src_addr, self.dst_addr, self.size);
}
}
}
pub struct StagingMemory {
mmap: MemoryMapping,
present_list: PresentList,
}
impl StagingMemory {
pub fn new(shmem: &SharedMemory, offset_bytes: u64, num_of_pages: usize) -> Result<Self> {
let mmap = MemoryMappingBuilder::new(pages_to_bytes(num_of_pages))
.from_shared_memory(shmem)
.offset(offset_bytes)
.build()?;
Ok(Self {
mmap,
present_list: PresentList::new(num_of_pages),
})
}
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe fn copy(&mut self, src_addr: *const u8, idx: usize, pages: usize) -> Result<CopyOp> {
let idx_range = idx..idx + pages;
let dst_slice = self.get_slice(idx_range.clone())?;
let copy_op = CopyOp {
src_addr,
dst_addr: dst_slice.as_mut_ptr(),
size: dst_slice.size(),
};
if !self.present_list.mark_as_present(idx_range) {
unreachable!("idx_range is already validated by get_slice().");
}
Ok(copy_op)
}
pub fn page_content(&self, idx: usize) -> Result<Option<VolatileSlice>> {
match self.present_list.get(idx) {
Some(is_present) => {
if *is_present {
Ok(Some(self.get_slice(idx..idx + 1)?))
} else {
Ok(None)
}
}
None => Err(Error::OutOfRange),
}
}
pub fn clear_range(&mut self, idx_range: Range<usize>) -> Result<()> {
if !self.present_list.clear_range(idx_range.clone()) {
return Err(Error::OutOfRange);
}
self.mmap.remove_range(
pages_to_bytes(idx_range.start),
pages_to_bytes(idx_range.end - idx_range.start),
)?;
Ok(())
}
pub fn first_data_range(&mut self, max_pages: usize) -> Option<Range<usize>> {
self.present_list.first_data_range(max_pages)
}
pub fn get_slice(&self, idx_range: Range<usize>) -> Result<VolatileSlice> {
match self.mmap.get_slice(
pages_to_bytes(idx_range.start),
pages_to_bytes(idx_range.end - idx_range.start),
) {
Ok(slice) => Ok(slice),
Err(VolatileMemoryError::OutOfBounds { .. }) => Err(Error::OutOfRange),
Err(e) => Err(e.into()),
}
}
pub fn present_pages(&self) -> usize {
self.present_list.all_present_pages()
}
}
#[cfg(test)]
mod tests {
use base::pagesize;
use base::MappedRegion;
use super::*;
#[test]
fn new_success() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
assert!(StagingMemory::new(&shmem, 0, 200).is_ok());
}
fn create_mmap(value: u8, pages: usize) -> MemoryMapping {
let size = pages_to_bytes(pages);
let mmap = MemoryMappingBuilder::new(size).build().unwrap();
for i in 0..size {
mmap.write_obj(value, i).unwrap();
}
mmap
}
#[test]
fn copy_marks_as_present() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mmap = create_mmap(1, 4);
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
let src_addr = mmap.as_ptr();
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
staging_memory.copy(src_addr, 1, 4).unwrap();
staging_memory.copy(src_addr, 10, 0).unwrap();
staging_memory.copy(src_addr, 12, 1).unwrap();
}
assert!(staging_memory.page_content(0).unwrap().is_none());
for i in 1..5 {
assert!(staging_memory.page_content(i).unwrap().is_some());
}
for i in 5..12 {
assert!(staging_memory.page_content(i).unwrap().is_none());
}
assert!(staging_memory.page_content(12).unwrap().is_some());
for i in 13..200 {
assert!(staging_memory.page_content(i).unwrap().is_none());
}
}
#[test]
fn page_content_default_is_none() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
assert!(staging_memory.page_content(0).unwrap().is_none());
}
#[test]
fn page_content_returns_content() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mmap = create_mmap(1, 1);
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
staging_memory.copy(mmap.as_ptr(), 0, 1).unwrap().execute();
}
let page = staging_memory.page_content(0).unwrap().unwrap();
#[allow(clippy::undocumented_unsafe_blocks)]
let result = unsafe { std::slice::from_raw_parts(page.as_ptr(), page.size()) };
assert_eq!(result, &vec![1; pagesize()]);
}
#[test]
fn page_content_out_of_range() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
assert!(staging_memory.page_content(199).is_ok());
match staging_memory.page_content(200) {
Err(Error::OutOfRange) => {}
_ => unreachable!("not out of range"),
}
}
#[test]
fn clear_range() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mmap = create_mmap(1, 5);
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
staging_memory.copy(mmap.as_ptr(), 0, 5).unwrap();
}
staging_memory.clear_range(1..3).unwrap();
assert!(staging_memory.page_content(0).unwrap().is_some());
assert!(staging_memory.page_content(1).unwrap().is_none());
assert!(staging_memory.page_content(2).unwrap().is_none());
assert!(staging_memory.page_content(3).unwrap().is_some());
assert!(staging_memory.page_content(4).unwrap().is_some());
}
#[test]
fn clear_range_out_of_range() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
assert!(staging_memory.clear_range(199..200).is_ok());
match staging_memory.clear_range(199..201) {
Err(Error::OutOfRange) => {}
_ => unreachable!("not out of range"),
};
}
#[test]
fn first_data_range() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mmap = create_mmap(1, 2);
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
let src_addr = mmap.as_ptr();
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
staging_memory.copy(src_addr, 1, 2).unwrap();
staging_memory.copy(src_addr, 3, 1).unwrap();
}
assert_eq!(staging_memory.first_data_range(200).unwrap(), 1..4);
assert_eq!(staging_memory.first_data_range(2).unwrap(), 1..3);
staging_memory.clear_range(1..3).unwrap();
assert_eq!(staging_memory.first_data_range(2).unwrap(), 3..4);
staging_memory.clear_range(3..4).unwrap();
assert!(staging_memory.first_data_range(2).is_none());
}
#[test]
fn get_slice() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mmap1 = create_mmap(1, 1);
let mmap2 = create_mmap(2, 1);
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
let src_addr1 = mmap1.as_ptr();
let src_addr2 = mmap2.as_ptr();
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
staging_memory.copy(src_addr1, 1, 1).unwrap().execute();
staging_memory.copy(src_addr2, 2, 1).unwrap().execute();
}
let slice = staging_memory.get_slice(1..3).unwrap();
assert_eq!(slice.size(), 2 * pagesize());
for i in 0..pagesize() {
let mut byte = [0u8; 1];
slice.get_slice(i, 1).unwrap().copy_to(&mut byte);
assert_eq!(byte[0], 1);
}
for i in pagesize()..2 * pagesize() {
let mut byte = [0u8; 1];
slice.get_slice(i, 1).unwrap().copy_to(&mut byte);
assert_eq!(byte[0], 2);
}
}
#[test]
fn get_slice_out_of_range() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
match staging_memory.get_slice(200..201) {
Err(Error::OutOfRange) => {}
other => {
unreachable!("unexpected result {:?}", other);
}
}
}
#[test]
fn present_pages() {
let shmem = SharedMemory::new("test staging memory", 200 * pagesize() as u64).unwrap();
let mmap = create_mmap(1, 5);
let mut staging_memory = StagingMemory::new(&shmem, 0, 200).unwrap();
let src_addr = mmap.as_ptr();
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
staging_memory.copy(src_addr, 1, 4).unwrap();
staging_memory.copy(src_addr, 12, 1).unwrap();
}
assert_eq!(staging_memory.present_pages(), 5);
}
}