use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::registry::Registry;
use crate::core::space::{RamStore, Region, RegionRef, RomStore, RomWrite};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::machine::realize::{Bindings, Instance};
use crate::machine::validate::{ClassSchema, PropSchema};
const RAM_CLASS_NAME: &str = "ram";
const ROM_CLASS_NAME: &str = "rom";
#[derive(Debug)]
pub struct Ram {
store: Arc<RamStore>,
region: RegionRef,
}
impl Ram {
pub fn new(props: &Props) -> Result<Ram> {
let mut r = props.reader();
let size = r.require_size("size")?;
r.finish()?;
if size == 0 {
return Err(Error::Property(alloc::string::String::from(
"property `size`: a ram object with no bytes in it cannot be mapped",
)));
}
let store = Arc::new(RamStore::new(size));
let region = Arc::new(Region::ram("ram", Arc::clone(&store)));
Ok(Ram { store, region })
}
#[must_use]
pub fn store(&self) -> &Arc<RamStore> {
&self.store
}
#[must_use]
pub fn len(&self) -> u64 {
self.store.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.store.len() == 0
}
}
impl Device for Ram {
fn class(&self) -> &'static DeviceClass {
&RAM_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, kind: ResetKind) {
if kind == ResetKind::Cold {
let _ = self.store.fill(0, self.store.len(), 0);
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let len = usize::try_from(self.store.len())
.map_err(|_| Error::State(alloc::string::String::from("ram larger than this host")))?;
let mut bytes = alloc::vec![0u8; len];
self.store.read_at(0, &mut bytes)?;
w.write_bytes(&bytes)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let bytes: &[u8] = r.read_bytes()?;
if bytes.len() as u64 != self.store.len() {
return Err(Error::State(alloc::format!(
"snapshot has {} byte(s) of ram, this object has {}",
bytes.len(),
self.store.len()
)));
}
self.store.write_at(0, bytes)?;
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
name.is_empty().then(|| Arc::clone(&self.region))
}
}
impl Instance for Ram {}
#[derive(Debug)]
pub struct Rom {
store: Arc<RomStore>,
region: RegionRef,
}
impl Rom {
pub fn new(props: &Props) -> Result<Rom> {
let mut r = props.reader();
let size = r.require_size("size")?;
let image = r
.optional_media("image")?
.map(crate::core::props::Media::to_bytes);
r.finish()?;
if size == 0 {
return Err(Error::Property(alloc::string::String::from(
"property `size`: a rom object with no bytes in it cannot be mapped",
)));
}
let mut bytes = alloc::vec![0u8; usize::try_from(size).map_err(|_| {
Error::Property(alloc::string::String::from(
"property `size`: a rom larger than this host's address space",
))
})?];
if let Some(image) = image {
if image.len() as u64 > size {
return Err(Error::Config {
at: alloc::string::String::from(ROM_CLASS_NAME),
message: alloc::format!(
"the bound image is {} byte(s) and the rom is {size}",
image.len()
),
});
}
bytes[..image.len()].copy_from_slice(&image);
}
let store = Arc::new(RomStore::new(bytes));
let region = Arc::new(Region::rom("rom", Arc::clone(&store), RomWrite::Ignore));
Ok(Rom { store, region })
}
#[must_use]
pub fn store(&self) -> &Arc<RomStore> {
&self.store
}
#[must_use]
pub fn len(&self) -> u64 {
self.store.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.store.len() == 0
}
}
impl Device for Rom {
fn class(&self) -> &'static DeviceClass {
&ROM_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
}
fn region(&self, name: &str) -> Option<RegionRef> {
name.is_empty().then(|| Arc::clone(&self.region))
}
}
impl Instance for Rom {}
pub static ROM_CLASS: DeviceClass = DeviceClass {
name: ROM_CLASS_NAME,
version: 1,
summary: "read-only memory: a block of bytes from a media slot, writes dropped",
properties: &[
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: true,
summary: "how many bytes, as in `size = 64K`",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: false,
summary: "the media slot holding the contents, as in `image = \"firmware\"`",
},
],
construct: |props| Ok(Box::new(Rom::new(props)?)),
};
#[must_use]
pub fn rom_schema() -> ClassSchema {
ClassSchema::new(ROM_CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size).required())
.prop(PropSchema::new("image", ValueKind::Media))
.region("")
}
pub static RAM_CLASS: DeviceClass = DeviceClass {
name: RAM_CLASS_NAME,
version: 1,
summary: "read/write memory: a block of bytes a `map` statement places",
properties: &[PropertySpec {
name: "size",
kind: ValueKind::Size,
required: true,
summary: "how many bytes, as in `size = 2K`",
}],
construct: |props| Ok(Box::new(Ram::new(props)?)),
};
#[must_use]
pub fn ram_schema() -> ClassSchema {
ClassSchema::new(RAM_CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size).required())
.region("")
}
pub fn register(registry: &mut Registry) -> Result<()> {
registry.add(&RAM_CLASS)?;
registry.add(&ROM_CLASS)?;
super::combinator::register(registry)
}
pub fn bind(bindings: &mut Bindings) -> Result<()> {
bindings.bind(RAM_CLASS_NAME, |props| Ok(Arc::new(Ram::new(props)?)))?;
bindings.bind(ROM_CLASS_NAME, |props| Ok(Arc::new(Rom::new(props)?)))?;
super::combinator::bind(bindings)
}
#[must_use]
pub fn schemas() -> Vec<ClassSchema> {
let mut out = alloc::vec![ram_schema(), rom_schema()];
out.extend(super::combinator::schemas());
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::props::Value;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use alloc::string::ToString;
fn ram(size: u64) -> Ram {
Ram::new(&Props::new().with("size", Value::Size(size))).expect("a size is all it takes")
}
#[test]
fn a_size_is_required_and_must_be_usable() {
assert!(Ram::new(&Props::new()).is_err(), "no size at all");
let e = Ram::new(&Props::new().with("size", Value::Size(0)))
.expect_err("zero bytes")
.to_string();
assert!(e.contains("no bytes"), "{e}");
let props = Props::new().with("size", Value::Size(16)).with("sze", 1u64);
assert!(Ram::new(&props).is_err(), "unknown property");
}
#[test]
fn the_whole_block_is_the_only_region() {
let wram = ram(2048);
assert_eq!(wram.len(), 2048);
assert!(!wram.is_empty());
assert!(wram.region("").is_some());
assert!(wram.region("bank0").is_none());
}
#[test]
fn power_clears_memory_and_a_reset_line_does_not() {
let wram = ram(64);
wram.store().write_u8(3, 0xa5).unwrap();
wram.reset(ResetKind::Warm);
assert_eq!(wram.store().read_u8(3).unwrap(), 0xa5);
wram.reset(ResetKind::Cold);
assert_eq!(wram.store().read_u8(3).unwrap(), 0);
}
#[test]
fn a_snapshot_round_trips_to_identical_bytes() {
let saved = ram(256);
for i in 0..256u64 {
saved.store().write_u8(i, (i as u8) ^ 0x5a).unwrap();
}
let mut shape = MachineShape::new();
shape.add_device("wram", RAM_CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("wram", RAM_CLASS.name, RAM_CLASS.version).unwrap();
saved.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let restored = ram(256);
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load(
"wram",
RAM_CLASS.name,
RAM_CLASS.version,
&Migrations::new(),
)
.unwrap();
restored.load(&mut chunk.reader()).unwrap();
for i in 0..256u64 {
assert_eq!(restored.store().read_u8(i).unwrap(), (i as u8) ^ 0x5a);
}
}
#[test]
fn a_snapshot_from_a_differently_sized_object_is_refused() {
let big = ram(256);
let mut shape = MachineShape::new();
shape.add_device("wram", RAM_CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("wram", RAM_CLASS.name, RAM_CLASS.version).unwrap();
big.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let small = ram(128);
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load(
"wram",
RAM_CLASS.name,
RAM_CLASS.version,
&Migrations::new(),
)
.unwrap();
let e = small
.load(&mut chunk.reader())
.expect_err("128 is not 256")
.to_string();
assert!(e.contains("256") && e.contains("128"), "{e}");
}
}