euphrates_virtual_memory 0.1.0

Allows the Euphrates game console emulator library to use native virtual memory to emulate memory bank switching.
Documentation
use std::mem::size_of;
use std::ptr;

use rand::{FromEntropy, Rng, XorShiftRng};

use super::*;
use traits::{Protect, UnsafeMemory, VirtualMemory};

const PAGE_SIZE: usize = 4096;

unsafe fn check_error<T: Eq>(val: T, err_val: T, s: &str) -> T {
    use std::mem::transmute;
    if val == err_val {
        libc::perror(transmute(s.as_ptr()));
        panic!("C error");
    }
    val
}

pub struct PhysicalMemory<T> {
    fd: i32,
    mem: LogicalMemory<T>,
}

impl<T: Copy> UnsafeMemory<T> for PhysicalMemory<T> {
    #[inline(always)]
    unsafe fn as_ptr(&self) -> *const T {
        self.mem.as_ptr()
    }

    #[inline(always)]
    unsafe fn as_mut_ptr(&mut self) -> *mut T {
        self.mem.as_mut_ptr()
    }

    #[inline(always)]
    unsafe fn get_unchecked(&self, index: usize) -> T {
        self.mem.get_unchecked(index)
    }

    #[inline(always)]
    unsafe fn set_unchecked(&mut self, index: usize, val: T) {
        self.mem.set_unchecked(index, val);
    }

    #[inline(always)]
    fn len(&self) -> usize {
        self.mem.len()
    }

    #[inline(always)]
    fn pages(&self) -> usize {
        self.mem.pages()
    }
}

impl<T> Drop for PhysicalMemory<T> {
    #[inline(always)]
    fn drop(&mut self) {
        unsafe {
            check_error(libc::close(self.fd), -1, "drop physical memory\0");
        }
    }
}

pub struct LogicalMemory<T> {
    ptr: *mut T,
    len: usize,
}

impl<T> Protect for LogicalMemory<T> {
    #[inline]
    unsafe fn set_protection(&mut self, write: bool, execute: bool) {
        use std::mem::transmute;
        let mut prot = libc::PROT_READ;
        if write {
            prot |= libc::PROT_WRITE;
        }
        if execute {
            prot |= libc::PROT_EXEC;
        }
        check_error(
            libc::mprotect(transmute(self.ptr), self.len * size_of::<T>(), prot),
            -1,
            "set_protection\0",
        );
    }
}

impl<T: Copy> UnsafeMemory<T> for LogicalMemory<T> {
    #[inline(always)]
    unsafe fn as_ptr(&self) -> *const T {
        self.ptr
    }

    #[inline(always)]
    unsafe fn as_mut_ptr(&mut self) -> *mut T {
        self.ptr
    }

    #[inline(always)]
    unsafe fn get_unchecked(&self, index: usize) -> T {
        *self.ptr.offset(index as isize)
    }

    unsafe fn set_unchecked(&mut self, index: usize, val: T) {
        *self.ptr.offset(index as isize) = val;
    }

    #[inline(always)]
    fn len(&self) -> usize {
        self.len
    }

    #[inline(always)]
    fn pages(&self) -> usize {
        self.len * size_of::<T>() / PAGE_SIZE
    }
}

impl<T> Drop for LogicalMemory<T> {
    #[inline]
    fn drop(&mut self) {
        use std::mem::transmute;
        unsafe {
            check_error(
                libc::munmap(transmute(self.ptr), self.len * size_of::<T>()),
                -1,
                "drop logical memory\0",
            );
        }
    }
}

#[inline]
fn allocate_logical<T: Copy>(
    len: usize,
    write: bool,
    execute: bool,
    flags: i32,
    fd: i32,
) -> LogicalMemory<T> {
    use std::mem::transmute;

    let mut prot: i32 = libc::PROT_READ;
    if write {
        prot |= libc::PROT_WRITE;
    }
    if execute {
        prot |= libc::PROT_EXEC;
    }
    let len2 = len * size_of::<T>();
    let ptr = unsafe {
        transmute(check_error(
            libc::mmap(ptr::null_mut(), len2, prot, flags, fd, 0),
            libc::MAP_FAILED,
            "allocate_logical\0",
        ))
    };
    LogicalMemory { ptr, len }
}

pub struct Main;

impl<T: Copy> VirtualMemory<T> for Main {
    const PAGE_SIZE: usize = PAGE_SIZE;

    type PhysicalMemory = PhysicalMemory<T>;

    type LogicalMemory = LogicalMemory<T>;

    unsafe fn allocate_physical(len: usize, write: bool, execute: bool) -> PhysicalMemory<T> {
        let mut oflag = libc::O_CREAT | libc::O_EXCL;
        if write {
            oflag |= libc::O_RDWR;
        } else {
            oflag |= libc::O_RDONLY;
        }
        let mut rand = XorShiftRng::from_entropy();
        let name: [i8; 9] = [
            47,
            rand.gen(),
            rand.gen(),
            rand.gen(),
            rand.gen(),
            rand.gen(),
            rand.gen(),
            rand.gen(),
            0,
        ];
        let fd = check_error(
            libc::shm_open(
                name.as_ptr(),
                oflag,
                (libc::S_IRUSR | libc::S_IWUSR) as libc::c_uint,
            ),
            -1,
            "allocate_physical\0",
        );
        let len2 = len * size_of::<T>();
        check_error(libc::shm_unlink(name.as_ptr()), -1, "allocate_physical\0");
        check_error(libc::ftruncate(fd, len2 as i64), -1, "allocate_physical\0");
        let logical = allocate_logical(len, write, execute, libc::MAP_SHARED, fd);
        PhysicalMemory { fd, mem: logical }
    }

    #[inline]
    unsafe fn allocate_logical(len: usize, write: bool, execute: bool) -> Self::LogicalMemory {
        allocate_logical(len, write, execute, libc::MAP_SHARED | libc::MAP_ANON, -1)
    }

    #[inline]
    unsafe fn map(
        logical: &mut Self::LogicalMemory,
        logical_offset: usize,
        physical: &Self::PhysicalMemory,
        physical_offset: usize,
        len: usize,
        write: bool,
        execute: bool,
    ) {
        use std::mem::transmute;
        let sz = size_of::<T>();
        let logical_ptr = logical.ptr.offset(logical_offset as isize);
        let mut prot = libc::PROT_READ;
        if write {
            prot |= libc::PROT_WRITE;
        }
        if execute {
            prot |= libc::PROT_READ;
        }
        check_error(
            libc::mmap(
                transmute(logical_ptr),
                len * sz,
                prot,
                libc::MAP_SHARED | libc::MAP_FIXED,
                physical.fd,
                (physical_offset * sz) as i64,
            ),
            libc::MAP_FAILED,
            "map\0",
        );
    }
}