use qcode::{context::Context, space::MemorySpaceId};
use qcode_emulator::{DomainMemory, DomainValue, EmulatorErrorKind, EmulatorMemory, SizedValue};
use crate::{
flat::FlatSpaces,
mmu::{MemFault, Mmu},
};
#[derive(Default)]
pub struct VmMemory {
pub mmu: Mmu,
flat: FlatSpaces,
ram: Option<MemorySpaceId>,
fault: Option<MemFault>,
}
impl VmMemory {
pub fn new() -> Self {
Self::default()
}
pub fn take_fault(&mut self) -> Option<MemFault> {
self.fault.take()
}
pub fn fault(&self) -> Option<MemFault> {
self.fault
}
pub(crate) fn record_read_fault(&mut self, fault: MemFault) {
self.record(fault, false);
}
pub(crate) fn record_write_fault(&mut self, fault: MemFault) {
self.record(fault, true);
}
fn is_ram(&self, space: MemorySpaceId) -> bool {
self.ram == Some(space)
}
pub fn is_flat(&self, space: MemorySpaceId) -> bool {
!self.is_ram(space)
}
pub fn flat_mut(&mut self) -> &mut FlatSpaces {
&mut self.flat
}
fn record(&mut self, fault: MemFault, writing: bool) -> EmulatorErrorKind {
self.fault = Some(fault);
if writing {
EmulatorErrorKind::MemoryWriteError(fault.addr)
} else {
EmulatorErrorKind::MemoryReadError(fault.addr)
}
}
}
impl DomainMemory for VmMemory {
type V = SizedValue;
fn read(
&self,
space: MemorySpaceId,
addr: Self::V,
size: usize,
) -> Result<Self::V, EmulatorErrorKind> {
if !self.is_ram(space) {
let bits = self.flat.read_u128(space, addr.value()?, size)?;
return Ok(SizedValue::from_bits(bits, size));
}
let addr = addr.value()?;
let mut bytes = vec![0u8; size.min(16)];
self.mmu
.read(addr, &mut bytes)
.map_err(|fault| EmulatorErrorKind::MemoryReadError(fault.addr))?;
let mut bits = 0u128;
for (index, byte) in bytes.iter().enumerate() {
bits |= u128::from(*byte) << (index * 8);
}
Ok(SizedValue::from_bits(bits, size))
}
fn write(
&mut self,
space: MemorySpaceId,
addr: Self::V,
size: usize,
value: Self::V,
) -> Result<(), EmulatorErrorKind> {
if !self.is_ram(space) {
return self
.flat
.entry(space)
.write_u128(addr.value()?, size, value.as_bits());
}
let addr = addr.value()?;
let bits = value.as_bits();
let width = size.min(16);
let bytes: Vec<u8> = (0..width).map(|i| (bits >> (i * 8)) as u8).collect();
self.mmu
.write(addr, &bytes)
.map_err(|fault| self.record(fault, true))
}
}
impl EmulatorMemory for VmMemory {
fn configure_spaces(&mut self, ctx: &Context<'_>) {
self.ram = Some(MemorySpaceId::Shared(ctx.shared.default_space));
self.flat.configure(ctx);
}
fn read_bytes(
&self,
space: MemorySpaceId,
addr: u64,
size: usize,
) -> Result<Vec<u8>, EmulatorErrorKind> {
if !self.is_ram(space) {
return self.flat.read_bytes(space, addr, size);
}
let mut bytes = vec![0u8; size];
self.mmu
.read(addr, &mut bytes)
.map_err(|fault| EmulatorErrorKind::MemoryReadError(fault.addr))?;
Ok(bytes)
}
fn write_bytes(
&mut self,
space: MemorySpaceId,
addr: u64,
bytes: &[u8],
) -> Result<(), EmulatorErrorKind> {
if !self.is_ram(space) {
return self.flat.entry(space).write_bytes(addr, bytes);
}
self.mmu
.write(addr, bytes)
.map_err(|fault| self.record(fault, true))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mmu::{FaultKind, PAGE_SIZE, perm};
use qcode::space::SpaceId;
fn context() -> Context<'static> {
Context::default()
}
fn configured() -> (Context<'static>, VmMemory) {
let ctx = context();
let mut memory = VmMemory::new();
memory.configure_spaces(&ctx);
(ctx, memory)
}
fn ram(ctx: &Context<'_>) -> MemorySpaceId {
MemorySpaceId::Shared(ctx.shared.default_space)
}
#[test]
fn ram_accesses_go_through_the_mmu() {
let (ctx, mut memory) = configured();
memory.mmu.map(0x1000, PAGE_SIZE, perm::RW_INIT).unwrap();
let addr = SizedValue::from_u64(0x1000);
memory
.write(ram(&ctx), addr, 4, SizedValue::from_bits(0xdead_beef, 4))
.unwrap();
let read = memory.read(ram(&ctx), addr, 4).unwrap();
assert_eq!(read.as_bits(), 0xdead_beef);
let mut raw = [0; 4];
memory.mmu.read(0x1000, &mut raw).unwrap();
assert_eq!(raw, [0xef, 0xbe, 0xad, 0xde]);
}
#[test]
fn unmapped_ram_write_faults_and_is_recorded() {
let (ctx, mut memory) = configured();
let addr = SizedValue::from_u64(0x1000);
let error = memory
.write(ram(&ctx), addr, 4, SizedValue::from_bits(1, 4))
.unwrap_err();
assert!(matches!(error, EmulatorErrorKind::MemoryWriteError(0x1000)));
assert_eq!(
memory.take_fault(),
Some(MemFault {
kind: FaultKind::WriteUnmapped,
addr: 0x1000
})
);
assert_eq!(memory.take_fault(), None);
}
#[test]
fn read_only_ram_refuses_a_write() {
let (ctx, mut memory) = configured();
memory.mmu.map(0x1000, PAGE_SIZE, perm::RX_INIT).unwrap();
let addr = SizedValue::from_u64(0x1000);
memory
.write(ram(&ctx), addr, 1, SizedValue::from_bits(1, 1))
.unwrap_err();
assert_eq!(
memory.take_fault().map(|f| f.kind),
Some(FaultKind::WritePerm)
);
}
#[test]
fn non_ram_spaces_bypass_the_mmu_entirely() {
let ctx = context();
let mut memory = VmMemory::new();
memory.configure_spaces(&ctx);
let register = MemorySpaceId::Shared(SpaceId::from(1));
let addr = SizedValue::from_u64(0x40);
memory
.write(register, addr, 8, SizedValue::from_bits(0x1234, 8))
.unwrap();
assert_eq!(memory.read(register, addr, 8).unwrap().as_bits(), 0x1234);
assert_eq!(memory.mmu.resident_pages(), 0);
assert_eq!(memory.take_fault(), None);
}
#[test]
fn byte_level_access_routes_the_same_way() {
let (ctx, mut memory) = configured();
memory.mmu.map(0x2000, PAGE_SIZE, perm::RW_INIT).unwrap();
memory
.write_bytes(ram(&ctx), 0x2000, &[1, 2, 3, 4])
.unwrap();
assert_eq!(
memory.read_bytes(ram(&ctx), 0x2000, 4).unwrap(),
vec![1, 2, 3, 4]
);
}
#[test]
fn before_configuration_nothing_is_routed_to_the_mmu() {
let mut memory = VmMemory::new();
let addr = SizedValue::from_u64(0x1000);
let space = MemorySpaceId::Shared(SpaceId::from(0));
memory
.write(space, addr, 4, SizedValue::from_bits(7, 4))
.unwrap();
assert_eq!(memory.mmu.resident_pages(), 0);
}
}