use bytemuck::{AnyBitPattern, NoUninit};
use std::{fmt, io::Error as IOError};
#[cfg(all(feature = "device", not(feature = "emulator")))]
use memmap2::{MmapMut, MmapOptions};
#[cfg(all(feature = "device", not(feature = "emulator")))]
use std::fs::OpenOptions;
#[derive(Debug)]
pub enum Error {
CantOpenFile(IOError),
CantMmapFile(IOError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::CantOpenFile(err) => write!(f, "failed to open /dev/mem: {err}"),
Error::CantMmapFile(err) => write!(f, "failed to mmap /dev/mem: {err}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::CantOpenFile(err) | Error::CantMmapFile(err) => Some(err),
}
}
}
impl From<Error> for IOError {
fn from(err: Error) -> IOError {
match err {
Error::CantOpenFile(e) | Error::CantMmapFile(e) => e,
}
}
}
pub struct DevMem {
#[cfg(feature = "emulator")]
mmap: Vec<u8>,
#[cfg(all(feature = "device", not(feature = "emulator")))]
mmap: MmapMut,
address: usize,
}
impl DevMem {
pub unsafe fn new(address: usize, size: Option<usize>) -> Result<Self, Error> {
let page_size = page_size::get();
let size = size.unwrap_or(page_size);
#[cfg(all(feature = "device", not(feature = "emulator")))]
{
let file = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open("/dev/mem")
.map_err(Error::CantOpenFile)?;
let mmap = MmapOptions::new()
.len(size)
.offset(address as u64)
.map_mut(&file)
.map_err(Error::CantMmapFile)?;
Ok(Self { mmap, address })
}
#[cfg(feature = "emulator")]
{
let mmap = vec![0; size];
Ok(Self { mmap, address })
}
}
#[inline(always)]
pub fn address(&self) -> usize {
self.address
}
#[inline(always)]
pub fn len(&self) -> usize {
self.mmap.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.mmap.is_empty()
}
#[inline(always)]
pub fn as_ptr(&self) -> *mut u8 {
self.mmap.as_ptr() as *mut u8
}
#[inline(always)]
pub fn read<T: AnyBitPattern>(&self, offset: usize) -> Option<T> {
if offset + std::mem::size_of::<T>() > self.len() {
return None;
}
Some(unsafe { std::ptr::read_volatile(self.as_ptr().add(offset) as *const T) })
}
#[inline(always)]
pub fn write<T: NoUninit>(&self, offset: usize, value: T) -> Option<()> {
if offset + std::mem::size_of::<T>() > self.len() {
return None;
}
unsafe { std::ptr::write_volatile(self.as_ptr().add(offset) as *mut T, value) };
Some(())
}
#[inline(always)]
pub fn modify<T: AnyBitPattern + NoUninit>(
&self,
offset: usize,
f: impl FnOnce(T) -> T,
) -> Option<()> {
if offset + std::mem::size_of::<T>() > self.len() {
return None;
}
unsafe {
let ptr = self.as_ptr().add(offset);
let val = std::ptr::read_volatile(ptr as *const T);
std::ptr::write_volatile(ptr as *mut T, f(val));
}
Some(())
}
#[inline(always)]
pub fn read_slice<T: AnyBitPattern>(&self, offset: usize, buf: &mut [T]) {
assert!(
offset + std::mem::size_of_val(buf) <= self.len(),
"read_slice: range out of bounds"
);
for (i, slot) in buf.iter_mut().enumerate() {
unsafe {
*slot = std::ptr::read_volatile(self.as_ptr().add(offset).cast::<T>().add(i));
}
}
}
#[inline(always)]
pub fn write_slice<T: NoUninit>(&self, offset: usize, buf: &[T]) {
assert!(
offset + std::mem::size_of_val(buf) <= self.len(),
"write_slice: range out of bounds"
);
for (i, val) in buf.iter().enumerate() {
unsafe {
std::ptr::write_volatile(
self.as_ptr().add(offset).cast::<T>().add(i),
std::ptr::read(val),
);
}
}
}
}
impl fmt::Debug for DevMem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"DevMem({:#X}..{:#X})",
self.address,
self.address + self.len()
)
}
}