use super::attrs::MemResult;
use crate::core::error::BusError;
use alloc::vec::Vec;
use core::fmt;
use core::sync::atomic::{AtomicU8, AtomicU64, Ordering};
pub const DEFAULT_PAGE_BITS: u32 = 12;
pub struct RamStore {
cells: Vec<AtomicU8>,
dirty: Vec<AtomicU64>,
page_bits: u32,
len: u64,
}
impl fmt::Debug for RamStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RamStore")
.field("len", &self.len)
.field("page_size", &self.page_size())
.finish_non_exhaustive()
}
}
impl RamStore {
#[must_use]
pub fn new(len: u64) -> Self {
Self::with_page_bits(len, DEFAULT_PAGE_BITS)
}
#[must_use]
pub fn with_page_bits(len: u64, page_bits: u32) -> Self {
assert!(page_bits > 0 && page_bits < 64, "implausible page size");
let n = usize::try_from(len).expect("guest RAM larger than the host address space");
let pages = len.div_ceil(1u64 << page_bits);
let words = usize::try_from(pages.div_ceil(64)).expect("dirty bitmap too large");
let mut cells = Vec::new();
cells.resize_with(n, || AtomicU8::new(0));
let mut dirty = Vec::new();
dirty.resize_with(words, || AtomicU64::new(0));
RamStore {
cells,
dirty,
page_bits,
len,
}
}
#[inline]
#[must_use]
pub fn len(&self) -> u64 {
self.len
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
#[must_use]
pub fn page_size(&self) -> u64 {
1u64 << self.page_bits
}
#[inline]
#[must_use]
pub fn page_count(&self) -> u64 {
self.len.div_ceil(self.page_size())
}
#[inline]
fn range(&self, offset: u64, len: u64) -> MemResult<usize> {
let end = offset.checked_add(len).ok_or(BusError::BadAccess)?;
if end > self.len {
return Err(BusError::BadAccess);
}
usize::try_from(offset).map_err(|_| BusError::BadAccess)
}
#[inline]
pub fn read_at(&self, offset: u64, dst: &mut [u8]) -> MemResult {
let base = self.range(offset, dst.len() as u64)?;
for (i, b) in dst.iter_mut().enumerate() {
*b = self.cells[base + i].load(Ordering::Relaxed);
}
Ok(())
}
#[inline]
pub fn write_at(&self, offset: u64, src: &[u8]) -> MemResult {
let base = self.range(offset, src.len() as u64)?;
for (i, b) in src.iter().enumerate() {
self.cells[base + i].store(*b, Ordering::Relaxed);
}
self.mark_dirty(offset, src.len() as u64);
Ok(())
}
#[inline]
pub fn read_u8(&self, offset: u64) -> MemResult<u8> {
let base = self.range(offset, 1)?;
Ok(self.cells[base].load(Ordering::Relaxed))
}
#[inline]
pub fn write_u8(&self, offset: u64, value: u8) -> MemResult {
let base = self.range(offset, 1)?;
self.cells[base].store(value, Ordering::Relaxed);
self.mark_dirty(offset, 1);
Ok(())
}
pub fn fill(&self, offset: u64, len: u64, value: u8) -> MemResult {
let base = self.range(offset, len)?;
let n = usize::try_from(len).map_err(|_| BusError::BadAccess)?;
for cell in &self.cells[base..base + n] {
cell.store(value, Ordering::Relaxed);
}
self.mark_dirty(offset, len);
Ok(())
}
pub fn mark_dirty(&self, offset: u64, len: u64) {
if len == 0 {
return;
}
let first = offset >> self.page_bits;
let last = offset.saturating_add(len - 1) >> self.page_bits;
for page in first..=last.min(self.page_count().saturating_sub(1)) {
let (word, bit) = (page / 64, page % 64);
if let Some(w) = self.dirty.get(word as usize) {
w.fetch_or(1u64 << bit, Ordering::Relaxed);
}
}
}
#[must_use]
pub fn is_page_dirty(&self, page: u64) -> bool {
let (word, bit) = (page / 64, page % 64);
self.dirty
.get(word as usize)
.is_some_and(|w| w.load(Ordering::Relaxed) & (1u64 << bit) != 0)
}
pub fn take_page_dirty(&self, page: u64) -> bool {
let (word, bit) = (page / 64, page % 64);
match self.dirty.get(word as usize) {
Some(w) => w.fetch_and(!(1u64 << bit), Ordering::Relaxed) & (1u64 << bit) != 0,
None => false,
}
}
pub fn clear_dirty(&self) {
for w in &self.dirty {
w.store(0, Ordering::Relaxed);
}
}
pub fn for_each_dirty_page(&self, mut f: impl FnMut(u64)) {
for (i, w) in self.dirty.iter().enumerate() {
let mut bits = w.load(Ordering::Relaxed);
while bits != 0 {
let bit = bits.trailing_zeros() as u64;
bits &= bits - 1;
let page = (i as u64) * 64 + bit;
if page < self.page_count() {
f(page);
}
}
}
}
#[must_use]
pub fn dirty_page_count(&self) -> u64 {
let mut n = 0;
self.for_each_dirty_page(|_| n += 1);
n
}
}
pub struct RomStore {
bytes: Vec<u8>,
}
impl fmt::Debug for RomStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RomStore")
.field("len", &self.bytes.len())
.finish_non_exhaustive()
}
}
impl RomStore {
#[must_use]
pub fn new(bytes: Vec<u8>) -> Self {
RomStore { bytes }
}
#[must_use]
pub fn zeroed(len: u64) -> Self {
let n = usize::try_from(len).expect("ROM larger than the host address space");
RomStore {
bytes: alloc::vec![0u8; n],
}
}
#[inline]
#[must_use]
pub fn len(&self) -> u64 {
self.bytes.len() as u64
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[inline]
pub fn read_at(&self, offset: u64, dst: &mut [u8]) -> MemResult {
let end = offset
.checked_add(dst.len() as u64)
.ok_or(BusError::BadAccess)?;
if end > self.len() {
return Err(BusError::BadAccess);
}
let base = usize::try_from(offset).map_err(|_| BusError::BadAccess)?;
dst.copy_from_slice(&self.bytes[base..base + dst.len()]);
Ok(())
}
}