diff --git a/dsa-emu/Cargo.toml b/dsa-emu/Cargo.toml index 86f8904..f879708 100644 --- a/dsa-emu/Cargo.toml +++ b/dsa-emu/Cargo.toml @@ -22,7 +22,7 @@ name = "bench_mainstore" harness = false [features] -default = ["mainstore-bulkalloc", "mainstore-arraymap"] +default = ["mainstore-bulkalloc"] # Memory Bank Features mainstore-bulkalloc = [] # Fastest for Writes diff --git a/dsa-emu/benches/bench_mainstore.rs b/dsa-emu/benches/bench_mainstore.rs index 5076c6d..28df431 100644 --- a/dsa-emu/benches/bench_mainstore.rs +++ b/dsa-emu/benches/bench_mainstore.rs @@ -4,7 +4,7 @@ use criterion::{ }; use std::time::Duration; -use dsa::RandomAccessMemory; +use dsa::{Page, RandomAccessMemory}; type PhysAddr = u32; @@ -139,7 +139,7 @@ fn run_write_page( addrs: &[PhysAddr], implementations: &mut [(&str, Box)], ) { - let page_data = [0xABu8; 4096]; + let page_data = Page::from([0xABu8; 4096]); group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096)); for (name, mem) in implementations.iter_mut() { group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { diff --git a/dsa-emu/config/dsa.iomap.ron b/dsa-emu/config/dsa.iomap.ron new file mode 100644 index 0000000..409b0d4 --- /dev/null +++ b/dsa-emu/config/dsa.iomap.ron @@ -0,0 +1,7 @@ +[ + IoMapping( + device: Display, + base: 0x0000_0000, + size: 0x0000_07D0, // 2000 bytes (80x25) + ), +], diff --git a/dsa-emu/src/args.rs b/dsa-emu/src/args.rs index 4e67f98..5b24a1a 100644 --- a/dsa-emu/src/args.rs +++ b/dsa-emu/src/args.rs @@ -1,8 +1,9 @@ use std::{fs, path::PathBuf}; use clap::{Parser, ValueEnum}; +use serde::{Deserialize, Serialize}; -use crate::{MemoryMap, RandomAccessMemory}; +use crate::{MemoryMap, RandomAccessMemory, config::IoMapping, io::DeviceId}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -12,17 +13,38 @@ pub struct DsaArgs { #[arg(long = "mmap")] memory_map: Option, + #[arg(long = "iomap")] + io_map: Option, + #[arg(value_enum, long = "mem", default_value = "bulk-alloc")] pub memory_bank: MemoryBank, } impl DsaArgs { - pub fn get_memory_map(&self) -> Option { - self.memory_map.as_ref().and_then(|m| { - fs::read_to_string(m) - .ok() - .and_then(|map| ron::from_str(&map).ok()) - }) + pub fn get_memory_map(&self) -> MemoryMap { + self.memory_map + .as_ref() + .and_then(|m| { + fs::read_to_string(m) + .ok() + .and_then(|map| ron::from_str(&map).ok()) + }) + .unwrap_or_else(|| { + ron::from_str(include_str!("./config/default/dsa.mmap.ron")).unwrap() + }) + } + + pub fn get_io_map(&self) -> Vec { + self.io_map + .as_ref() + .and_then(|m| { + fs::read_to_string(m) + .ok() + .and_then(|map| ron::from_str(&map).ok()) + }) + .unwrap_or_else(|| { + ron::from_str(include_str!("./config/default/dsa.iomap.ron")).unwrap() + }) } } diff --git a/dsa-emu/src/config/default/dsa.iomap.ron b/dsa-emu/src/config/default/dsa.iomap.ron new file mode 100644 index 0000000..28da39d --- /dev/null +++ b/dsa-emu/src/config/default/dsa.iomap.ron @@ -0,0 +1,7 @@ +[ + IoMapping( + device: Display, + base: 0x0000_0000, + size: 0x0000_07D0, // 2000 bytes (80x25) + ), +] diff --git a/dsa-emu/src/config/dsa.mmap.default.ron b/dsa-emu/src/config/default/dsa.mmap.ron similarity index 100% rename from dsa-emu/src/config/dsa.mmap.default.ron rename to dsa-emu/src/config/default/dsa.mmap.ron diff --git a/dsa-emu/src/config/mod.rs b/dsa-emu/src/config/mod.rs new file mode 100644 index 0000000..5aab930 --- /dev/null +++ b/dsa-emu/src/config/mod.rs @@ -0,0 +1,80 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + Emulator, RandomAccessMemory, + io::DeviceId, + memory::{PhysAddr, mmu::MMU}, +}; + +#[repr(u32)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +pub enum RegionType { + Reserved, + Bootloader, + Kernel, + KernelTables, + Usable, + MMIO, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct Region { + pub base: PhysAddr, + pub size: u32, + pub region_type: RegionType, + + // flags for the emulator + #[serde(default)] + identity_map_on_boot: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MemoryMap { + table_addr: PhysAddr, + pub regions: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct IoMapping { + pub device: DeviceId, + pub base: u32, + pub size: u32, +} + +impl MemoryMap { + pub const DEFAULT_MEMORY_MAP_ADDR: PhysAddr = 0x1000; + + pub fn new() -> Self { + MemoryMap { + table_addr: Self::DEFAULT_MEMORY_MAP_ADDR, + regions: Vec::new(), + } + } + + pub fn apply(&self, cpu: &mut Emulator) { + for reg in &self.regions { + if reg.identity_map_on_boot { + Self::identity_map(cpu.mmu_mut(), reg); + } + } + + let mem = cpu.memory_mut(); + let mut offset = self.table_addr; + for region in &self.regions { + println!("Wrote entry: {region:?} to page table"); + mem.write_word(offset, region.base); + mem.write_word(offset + 4, region.size); + mem.write_word(offset + 8, region.region_type as u32); + offset += 12; + } + } + + pub fn identity_map(mmu: &mut MMU, region: &Region) { + let first_page = region.base >> 12; + let page_count = region.size >> 12; + + for page in (first_page..first_page + page_count).map(|p| p << 12) { + mmu.create_mapping(page, page); + } + } +} diff --git a/dsa-emu/src/io/display.rs b/dsa-emu/src/io/display.rs new file mode 100644 index 0000000..0afd685 --- /dev/null +++ b/dsa-emu/src/io/display.rs @@ -0,0 +1,77 @@ +use std::sync::{ + Arc, + atomic::{AtomicBool, AtomicU8, Ordering}, +}; + +use crate::io::{IoAccess, IoDevice}; + +pub struct DisplayDevice { + buffer: Arc>, + dirty: Arc, +} + +impl DisplayDevice { + pub fn new(width: usize, height: usize) -> (Self, DisplayHandle) { + let buffer = Arc::new( + (0..width * height) + .map(|_| AtomicU8::new(0)) + .collect::>() + .into_boxed_slice(), + ); + let dirty = Arc::new(AtomicBool::new(false)); + + let handle = DisplayHandle { + buffer: Arc::clone(&buffer), + dirty: Arc::clone(&dirty), + }; + + (Self { buffer, dirty }, handle) + } +} + +impl IoDevice for DisplayDevice { + fn size(&self) -> u32 { + (self.buffer.len()) as u32 + } + fn access(&self) -> IoAccess { + IoAccess::WriteOnly + } + + fn id(&self) -> super::DeviceId { + super::DeviceId::Display + } + + fn write_word(&mut self, offset: u32, val: u32) -> bool { + let bytes = val.to_le_bytes(); + self.buffer[offset as usize].store(bytes[0], Ordering::Relaxed); + self.buffer[offset as usize + 1].store(bytes[1], Ordering::Relaxed); + self.buffer[offset as usize + 2].store(bytes[2], Ordering::Relaxed); + self.buffer[offset as usize + 3].store(bytes[3], Ordering::Relaxed); + self.dirty.store(true, Ordering::Relaxed); + true + } + + fn write_byte(&mut self, offset: u32, val: u8) -> bool { + self.buffer[offset as usize].store(val, Ordering::Relaxed); + self.dirty.store(true, Ordering::Relaxed); + true + } +} + +pub struct DisplayHandle { + pub buffer: Arc>, + pub dirty: Arc, +} + +impl DisplayHandle { + pub fn is_dirty(&self) -> bool { + self.dirty.swap(false, Ordering::Relaxed) + } + + pub fn read_all(&self) -> Vec { + self.buffer + .iter() + .map(|b| b.load(Ordering::Relaxed)) + .collect() + } +} diff --git a/dsa-emu/src/io/mod.rs b/dsa-emu/src/io/mod.rs new file mode 100644 index 0000000..46fa1fe --- /dev/null +++ b/dsa-emu/src/io/mod.rs @@ -0,0 +1,106 @@ +use serde::{Deserialize, Serialize}; + +use crate::RandomAccessMemory; + +pub mod display; + +pub struct MappedDevice { + pub base: u32, + pub device: Box, +} + +pub enum IoAccess { + ReadOnly, + WriteOnly, + ReadWrite, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum DeviceId { + Display, + Serial, + Random, + Timer, +} + +/// Trait representing an input/output device that can be mapped into a memory space. +/// +/// - Default methods return dummy data. +pub trait IoDevice: Send { + /// Return the size (in bytes) of the device's addressable region. + fn size(&self) -> u32; + + /// Return the access permissions for the device. + fn access(&self) -> IoAccess; + + /// Return the DeviceId + fn id(&self) -> DeviceId; + + #[inline] + /// Read a single byte from the specified address. + /// + /// By default, write‑only devices return `None`. All other devices + /// return `Some(0)` as placeholder data. Implementations should override + /// this method to provide actual read logic. + fn read_byte(&self, offset: u32) -> Option { + match self.access() { + IoAccess::WriteOnly => None, + _ => Some(0), + } + } + + #[inline] + /// Write a single byte to the specified address. + /// + /// By default, read‑only devices return `false` indicating failure. + /// All other devices return `true`. Implementations should override + /// this method to perform real write operations and return whether + /// the operation succeeded. + fn write_byte(&mut self, offset: u32, val: u8) -> bool { + match self.access() { + IoAccess::ReadOnly => false, + _ => true, + } + } + + /// Read a 32‑bit word from the specified address in little‑endian order. + /// + /// The default implementation composes four consecutive byte reads using + /// `read_byte`. If any byte read fails, the whole operation returns `None`. + /// Write‑only devices return `None` immediately. Override this method for + /// more efficient word reads. + #[inline] + fn read_word(&self, offset: u32) -> Option { + match self.access() { + IoAccess::WriteOnly => None, + _ => { + // default: compose from bytes + let b0 = self.read_byte(offset)? as u32; + let b1 = self.read_byte(offset + 1)? as u32; + let b2 = self.read_byte(offset + 2)? as u32; + let b3 = self.read_byte(offset + 3)? as u32; + Some(b0 | b1 << 8 | b2 << 16 | b3 << 24) + } + } + } + + /// Write a 32‑bit word to the specified address in little‑endian order. + /// + /// The default implementation splits the value into bytes and writes each + /// using `write_byte`. Read‑only devices return `false`; otherwise returns + /// `true` after attempting all byte writes. Override for more efficient + /// word writes or to handle partial failures. + fn write_word(&mut self, offset: u32, val: u32) -> bool { + match self.access() { + IoAccess::ReadOnly => false, + _ => { + let bytes = val.to_le_bytes(); + self.write_byte(offset, bytes[0]); + self.write_byte(offset + 1, bytes[1]); + self.write_byte(offset + 2, bytes[2]); + self.write_byte(offset + 3, bytes[3]); + true + } + } + } +} diff --git a/dsa-emu/src/lib.rs b/dsa-emu/src/lib.rs index 5b6a16f..90f7e75 100644 --- a/dsa-emu/src/lib.rs +++ b/dsa-emu/src/lib.rs @@ -1,11 +1,12 @@ #![feature(likely_unlikely)] -#![feature(ptr_as_ref_unchecked)] pub mod args; mod common; +pub mod config; +pub mod io; mod memory; mod processor; -pub use {memory::MemoryMap, processor::processor::Emulator, processor::state::SharedState}; +pub use {config::MemoryMap, processor::processor::Emulator, processor::state::SharedState}; pub use memory::ram::*; diff --git a/dsa-emu/src/main.rs b/dsa-emu/src/main.rs index bf22912..53ce738 100644 --- a/dsa-emu/src/main.rs +++ b/dsa-emu/src/main.rs @@ -1,32 +1,54 @@ use clap::Parser; -use dsa::{BulkAllocStore, Emulator, MemoryMap, SharedState, args::DsaArgs}; +use dsa::{ + BulkAllocStore, Emulator, MemoryMap, SharedState, + args::DsaArgs, + io::display::{DisplayDevice, DisplayHandle}, +}; use std::{sync::Arc, thread}; fn main() { let args = DsaArgs::parse(); - let mut emulator = Emulator::new(BulkAllocStore::new()); - emulator.apply_memory_map(args.get_memory_map().unwrap_or_default()); + let mmap = args.get_memory_map(); + let iomap = args.get_io_map(); + + let store = BulkAllocStore::new(); + let (display, handle) = DisplayDevice::new(80, 25); + + let mut emulator = Emulator::new(store) + .with_device(display) + .apply_memory_map(mmap) + .apply_io_map(iomap) + .unwrap(); let state = emulator.state_handle(); const STACK_SIZE: usize = 1024 * 1024 * 16; let runner = thread::Builder::new() .stack_size(STACK_SIZE) - .spawn(move || emulator.run()) + .spawn(move || emulator.run().unwrap()) .unwrap(); - let observer = thread::spawn(|| observe(state)); + let observer = thread::spawn(|| observe(state, handle)); runner.join().unwrap(); observer.join().unwrap(); } /// todo: remove this! -fn observe(state: Arc) { +fn observe(state: Arc, handle: DisplayHandle) { loop { thread::sleep(std::time::Duration::from_millis(100)); let state = state.proc.load(); println!("GP Registers: {:?}", state.registers); + + if handle.is_dirty() { + for line in handle.read_all().chunks(80) { + for (i, byte) in line.iter().enumerate() { + print!("{}", *byte as char); + } + println!(); + } + } } } diff --git a/dsa-emu/src/memory/mod.rs b/dsa-emu/src/memory/mod.rs index a1b0485..2bcfbb2 100644 --- a/dsa-emu/src/memory/mod.rs +++ b/dsa-emu/src/memory/mod.rs @@ -13,75 +13,3 @@ pub type PhysAddr = u32; pub enum FaultInfo { PageFault, } - -#[repr(u32)] -#[derive(Serialize, Deserialize, Clone, Copy, Debug)] -enum RegionType { - Reserved, - Bootloader, - Kernel, - KernelTables, - Usable, - MMIO, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] -pub struct Region { - base: PhysAddr, - size: u32, - region_type: RegionType, - - // flags for the emulator - #[serde(default)] - identity_map_on_boot: bool, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct MemoryMap { - table_addr: PhysAddr, - pub regions: Vec, -} - -impl MemoryMap { - pub const DEFAULT_MEMORY_MAP_ADDR: PhysAddr = 0x1000; - - pub fn new() -> Self { - MemoryMap { - table_addr: Self::DEFAULT_MEMORY_MAP_ADDR, - regions: Vec::new(), - } - } - - pub fn apply(&self, cpu: &mut Emulator) { - for reg in &self.regions { - if reg.identity_map_on_boot { - Self::identity_map(cpu.mmu_mut(), reg); - } - } - - let mem = cpu.memory_mut(); - let mut offset = self.table_addr; - for region in &self.regions { - println!("Wrote entry: {region:?} to page table"); - mem.write_word(offset, region.base); - mem.write_word(offset + 4, region.size); - mem.write_word(offset + 8, region.region_type as u32); - offset += 12; - } - } - - pub fn identity_map(mmu: &mut MMU, region: &Region) { - let first_page = region.base >> 12; - let page_count = (region.size >> 12); - - for page in (first_page..first_page + page_count).map(|p| p << 12) { - mmu.create_mapping(page, page); - } - } -} - -impl Default for MemoryMap { - fn default() -> Self { - ron::from_str(include_str!("../config/dsa.mmap.default.ron")).unwrap() - } -} diff --git a/dsa-emu/src/memory/ram/arraymap.rs b/dsa-emu/src/memory/ram/arraymap.rs index 5a2519b..8b3016b 100644 --- a/dsa-emu/src/memory/ram/arraymap.rs +++ b/dsa-emu/src/memory/ram/arraymap.rs @@ -55,15 +55,15 @@ impl RandomAccessMemory for ArrayStore { } #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { + fn read_page(&self, addr: PhysAddr) -> &Page { debug_assert_eq!(addr % 4096, 0); let (idx, _) = idx(addr); if likely(!self.pages[idx].is_null()) { - return unsafe { &*(self.pages[idx] as *const [u8; 4096]) }; + return unsafe { &*(self.pages[idx] as *const Page) }; } - return &[0; 4096]; + return &Page([0; 4096]); } #[inline(always)] @@ -92,7 +92,7 @@ impl RandomAccessMemory for ArrayStore { } #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { + fn write_page(&mut self, addr: PhysAddr, value: &Page) { debug_assert_eq!(addr % 4096, 0); let (idx, _) = idx(addr); @@ -100,6 +100,8 @@ impl RandomAccessMemory for ArrayStore { self.pages[idx] = self.alloc(); } - unsafe { (self.pages[idx] as *mut [u8; 4096]).write(*value) } + unsafe { + (self.pages[idx] as *mut Page).copy_from(value, 1); + } } } diff --git a/dsa-emu/src/memory/ram/bulkalloc.rs b/dsa-emu/src/memory/ram/bulkalloc.rs index 156c891..eb32249 100644 --- a/dsa-emu/src/memory/ram/bulkalloc.rs +++ b/dsa-emu/src/memory/ram/bulkalloc.rs @@ -82,15 +82,15 @@ impl RandomAccessMemory for BulkAllocStore { } #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { + fn read_page(&self, addr: PhysAddr) -> &Page { debug_assert_eq!(addr % 4096, 0); let (idx, _) = idx(addr); if likely(!self.pages[idx].is_null()) { - return unsafe { &*(self.pages[idx] as *const [u8; 4096]) }; + return unsafe { &*(self.pages[idx] as *const Page) }; } - return &[0; 4096]; + return &Page::ZERO; } #[inline(always)] @@ -119,7 +119,7 @@ impl RandomAccessMemory for BulkAllocStore { } #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { + fn write_page(&mut self, addr: PhysAddr, value: &Page) { debug_assert_eq!(addr % 4096, 0); let (idx, _) = idx(addr); @@ -127,6 +127,8 @@ impl RandomAccessMemory for BulkAllocStore { self.pages[idx] = self.alloc(); } - unsafe { (self.pages[idx] as *mut [u8; 4096]).write(*value) } + unsafe { + (self.pages[idx] as *mut Page).copy_from(value, 1); + } } } diff --git a/dsa-emu/src/memory/ram/hashmap.rs b/dsa-emu/src/memory/ram/hashmap.rs index 4fcafab..c73cde6 100644 --- a/dsa-emu/src/memory/ram/hashmap.rs +++ b/dsa-emu/src/memory/ram/hashmap.rs @@ -23,41 +23,44 @@ impl RandomAccessMemory for HashStore { #[inline(always)] fn read_byte(&self, addr: PhysAddr) -> u8 { let (page, offset) = Self::segment_addr(addr); - self.pages.get(&page).map(|p| p[offset]).unwrap_or(0) + self.pages + .get(&page) + .map(|p| p.as_ref()[offset]) + .unwrap_or(0) } #[inline(always)] fn read_word(&self, addr: PhysAddr) -> u32 { let (page, offset) = Self::segment_addr(addr); debug_assert_eq!(offset % 4, 0); - let page = self.pages.get(&page).unwrap_or(&[0; 4096]); + let page = self.pages.get(&page).unwrap_or(&Page::ZERO); u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap()) } #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { + fn read_page(&self, addr: PhysAddr) -> &Page { debug_assert_eq!(addr % 0x1000, 0); - self.pages.get(&addr).unwrap_or(&[0; 4096]) + self.pages.get(&addr).unwrap_or(&Page::ZERO) } #[inline(always)] fn write_byte(&mut self, addr: PhysAddr, value: u8) { let (page, offset) = Self::segment_addr(addr); - self.pages.entry(page).or_insert_with(|| [0; 4096])[offset] = value; + self.pages.entry(page).or_insert_with(|| Page::zeroed())[offset] = value; } #[inline(always)] fn write_word(&mut self, addr: PhysAddr, value: u32) { let (page, offset) = Self::segment_addr(addr); debug_assert_eq!(offset % 4, 0); - let page = self.pages.entry(page).or_insert_with(|| [0; 4096]); + let page = self.pages.entry(page).or_insert_with(|| Page::zeroed()); page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes()); } #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { + fn write_page(&mut self, addr: PhysAddr, value: &Page) { debug_assert_eq!(addr % 0x1000, 0); - let page = self.pages.entry(addr).or_insert_with(|| [0; 4096]); - page.copy_from_slice(value); + let page = self.pages.entry(addr).or_insert_with(|| Page::zeroed()); + page.0 = value.0; } } diff --git a/dsa-emu/src/memory/ram/mod.rs b/dsa-emu/src/memory/ram/mod.rs index eb6986e..c0b212c 100644 --- a/dsa-emu/src/memory/ram/mod.rs +++ b/dsa-emu/src/memory/ram/mod.rs @@ -1,3 +1,5 @@ +use std::ops::{Deref, DerefMut}; + use crate::memory::PhysAddr; #[cfg(feature = "mainstore-arraymap")] @@ -27,7 +29,37 @@ pub use prealloc::PreAllocStore; const NUM_PAGES: usize = 2 << 20; -type Page = [u8; 4096]; +#[repr(transparent)] +#[derive(Clone)] +pub struct Page([u8; 4096]); + +impl Page { + pub const SIZE: usize = 4096; + pub const MASK: u32 = 0xFFF; + pub const ZERO: Self = Page::zeroed(); + + pub const fn zeroed() -> Self { + Self([0; 4096]) + } + + pub const fn from(data: [u8; 4096]) -> Self { + Self(data) + } +} + +impl Deref for Page { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Page { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} #[inline(always)] const fn idx(addr: PhysAddr) -> (usize, usize) { @@ -43,7 +75,7 @@ pub trait RandomAccessMemory { fn write_word(&mut self, addr: PhysAddr, value: u32); - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096]; + fn read_page(&self, addr: PhysAddr) -> &Page; - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]); + fn write_page(&mut self, addr: PhysAddr, value: &Page); } diff --git a/dsa-emu/src/memory/ram/prealloc.rs b/dsa-emu/src/memory/ram/prealloc.rs index 639dfd9..76f0f55 100644 --- a/dsa-emu/src/memory/ram/prealloc.rs +++ b/dsa-emu/src/memory/ram/prealloc.rs @@ -1,4 +1,7 @@ -use crate::memory::{PhysAddr, RandomAccessMemory, ram::NUM_PAGES}; +use crate::memory::{ + PhysAddr, RandomAccessMemory, + ram::{NUM_PAGES, Page}, +}; pub struct PreAllocStore { heap: Box<[u8; NUM_PAGES * 4096]>, @@ -25,9 +28,9 @@ impl RandomAccessMemory for PreAllocStore { } #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { + fn read_page(&self, addr: PhysAddr) -> &Page { debug_assert_eq!(addr % 4096, 0); - unsafe { (self.heap.as_ptr().add(addr as usize) as *const [u8; 4096]).as_ref_unchecked() } + unsafe { (self.heap.as_ptr().add(addr as usize) as *const Page).as_ref_unchecked() } } #[inline(always)] @@ -41,7 +44,7 @@ impl RandomAccessMemory for PreAllocStore { } #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { + fn write_page(&mut self, addr: PhysAddr, value: &Page) { self.heap[addr as usize..addr as usize + 4096].copy_from_slice(value); } } diff --git a/dsa-emu/src/memory/ram/stackarray.rs b/dsa-emu/src/memory/ram/stackarray.rs index 205f10d..61ac1aa 100644 --- a/dsa-emu/src/memory/ram/stackarray.rs +++ b/dsa-emu/src/memory/ram/stackarray.rs @@ -53,15 +53,15 @@ impl RandomAccessMemory for StackArrayStore { } #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { + fn read_page(&self, addr: PhysAddr) -> &Page { debug_assert_eq!(addr % 4096, 0); let (idx, _) = idx(addr); if likely(!self.pages[idx].is_null()) { - return unsafe { &*(self.pages[idx] as *const [u8; 4096]) }; + return unsafe { &*(self.pages[idx] as *const Page) }; } - return &[0; 4096]; + return &Page::ZERO; } #[inline(always)] @@ -90,7 +90,7 @@ impl RandomAccessMemory for StackArrayStore { } #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { + fn write_page(&mut self, addr: PhysAddr, value: &Page) { debug_assert_eq!(addr % 4096, 0); let (idx, _) = idx(addr); @@ -98,6 +98,6 @@ impl RandomAccessMemory for StackArrayStore { self.pages[idx] = self.alloc(); } - unsafe { (self.pages[idx] as *mut [u8; 4096]).write(*value) } + unsafe { (self.pages[idx] as *mut Page).copy_from(value, 1) } } } diff --git a/dsa-emu/src/processor/interrupts.rs b/dsa-emu/src/processor/interrupts.rs index 4171001..1344e96 100644 --- a/dsa-emu/src/processor/interrupts.rs +++ b/dsa-emu/src/processor/interrupts.rs @@ -1,5 +1,52 @@ +#[repr(u8)] +#[derive(Clone, Copy)] pub enum Interrupt { - PageFault, - ProtectionFault, - Generic(u8), + // CPU exceptions 0-31 + InvalidInterrupt = 0, + PageFault = 1, + ProtectionFault = 2, + InvalidOpcode = 3, + DivideByZero = 4, + StackOverflow = 5, + WriteToReadOnly = 6, + ReadFromWriteOnly = 7, + UnmappedIo = 8, + // 8-31 reserved for future CPU exceptions + + // IRQ's (32-63) + Hardware(u8), + + // Syscalls (64-255) + // OS defined, program uses INT 64-127 + Software(u8) = 128, +} + +impl Interrupt { + pub fn code(&self) -> u8 { + match self { + Interrupt::InvalidInterrupt => 0, + Interrupt::PageFault => 1, + Interrupt::ProtectionFault => 2, + Interrupt::InvalidOpcode => 3, + Interrupt::DivideByZero => 4, + Interrupt::StackOverflow => 5, + Interrupt::WriteToReadOnly => 6, + Interrupt::ReadFromWriteOnly => 7, + Interrupt::UnmappedIo => 8, + Interrupt::Hardware(x) => { + if *x < 32 || *x > 63 { + 0 + } else { + *x + } + } + Interrupt::Software(x) => { + if *x < 64 { + 0 + } else { + *x + } + } + } + } } diff --git a/dsa-emu/src/processor/processor.rs b/dsa-emu/src/processor/processor.rs index 942754c..df7c7f4 100644 --- a/dsa-emu/src/processor/processor.rs +++ b/dsa-emu/src/processor/processor.rs @@ -1,27 +1,44 @@ use std::{ hint::unlikely, - sync::{Arc, atomic::Ordering, mpsc}, - thread, + sync::{ + Arc, + atomic::Ordering, + mpsc::{self, TryRecvError}, + }, time::Duration, }; use crate::{ + Page, common::instructions::{InstructionWord, Opcode, Reg}, - memory::{FaultInfo, MemoryMap, mmu::MMU, ram::RandomAccessMemory}, + config::{IoMapping, MemoryMap, RegionType}, + io::{IoDevice, MappedDevice}, + memory::{mmu::MMU, ram::RandomAccessMemory}, processor::{interrupts::Interrupt, state::SharedState}, }; pub struct Emulator { internal_state: ProcessorSnapshot, shared_state: Arc, + + // Interrupts interrupts: mpsc::Receiver, + pending_fault: Option, // memory mmu: MMU, mainstore: Mem, + // IO + mmio: Vec, + mmio_region: Option<(u32, u32)>, // start end end of segment + // optionals - not necessarily set on boot. memory_map: Option, + + // Config params + __mmap_configured: bool, + __io_configured: bool, } unsafe impl Send for Emulator {} @@ -31,14 +48,33 @@ impl Emulator { Self { interrupts: receiver, + pending_fault: None, + internal_state: ProcessorSnapshot::default(), shared_state: Arc::new(SharedState::new(sender)), + + memory_map: None, mmu: MMU::new(), mainstore: mem, - memory_map: None, + + // mmio + mmio: Vec::new(), + mmio_region: None, + + // Config params + __io_configured: false, + __mmap_configured: false, } } + pub fn with_device(mut self, device: impl IoDevice + 'static) -> Self { + self.mmio.push(MappedDevice { + base: 0, + device: Box::new(device), + }); + self + } + pub fn state_handle(&self) -> Arc { self.shared_state.clone() } @@ -53,29 +89,6 @@ impl Emulator { &mut self.mainstore } - #[cold] - pub fn apply_memory_map(&mut self, map: MemoryMap) -> &mut Self { - map.apply(self); - self.memory_map = Some(map); - self - } - - #[cold] - fn boot(&mut self) { - let regions = MemoryMap::default(); - // self.mmu.paging_enabled(true); - MemoryMap::identity_map(&mut self.mmu, regions.regions.get(0).unwrap()); - - self.internal_state.running = true; - } - - #[cold] - fn update(&mut self) { - self.shared_state - .proc - .store(Arc::new(self.internal_state.clone())); - } - #[must_use] #[inline] pub fn reg(&self, reg: Reg) -> u32 { @@ -98,6 +111,146 @@ impl Emulator { } } + #[cold] + pub fn apply_memory_map(mut self, map: MemoryMap) -> Self { + self.mmio_region = map + .regions + .iter() + .find(|r| matches!(r.region_type, RegionType::MMIO)) + .map(|r| (r.base, r.base + r.size)); + + map.apply(&mut self); + self.memory_map = Some(map); + self.__mmap_configured = true; + self + } + + pub fn apply_io_map(mut self, map: Vec) -> Result { + if !self.__mmap_configured { + return Err("You must map memory before applying I/O mappings".to_string()); + } + + for entry in map { + if let Some(idx) = self.mmio.iter().position(|d| d.device.id() == entry.device) { + self.mmio[idx].base = entry.base; + } else { + eprintln!("WARN: no device registgered for {:?}", entry.device); + } + } + + self.__io_configured = true; + Ok(self) + } + + #[cold] + fn update(&mut self) { + self.shared_state + .proc + .store(Arc::new(self.internal_state.clone())); + } + + #[cold] + fn boot(&mut self) -> Result<(), String> { + if !(self.__io_configured && self.__mmap_configured) { + return Err("Processor not configured".to_string()); + } + + if let Some(map) = &self.memory_map { + MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap()); + } + + // self.mmu.paging_enabled(true); + + self.internal_state.running = true; + Ok(()) + } + + #[cold] + fn shutdown(&mut self) { + self.internal_state.running = false; + } + + #[cold] + pub fn idle_wait(&mut self) { + self.internal_state.running = false; + + // Wait for an interrupt or state update to continue. + loop { + // Check for interrupts. + if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) { + self.interrupt(Interrupt::Software(code)); + break; + } + + // If we've received a request to continue running. + if self.shared_state.running.load(Ordering::Relaxed) { + break; + } + + // UI requested a state update. + if self.shared_state.update_req.load(Ordering::Relaxed) { + self.update(); + } + } + + self.internal_state.running = true; + } + + pub fn run(&mut self) -> Result<(), String> { + self.boot()?; + + 'emu: loop { + // Update UI thread (roughly every 512k cycles targeting UPS) + if unlikely(self.internal_state.clock & 0x7FFFF == 0) { + if self.shared_state.update_req.load(Ordering::Relaxed) { + self.update(); + } + } + + // Check for commands or hardware Interrupts every 32k cycles + if self.internal_state.clock % 0x7FFF == 0 { + if self.shared_state.running.load(Ordering::Relaxed) == false { + self.idle_wait(); + } + + match self.interrupts.try_recv() { + Ok(code) => self.interrupt(Interrupt::Software(code)), + Err(TryRecvError::Disconnected) => break 'emu, + Err(TryRecvError::Empty) => {} + } + } + + // let instruction = self.mmu.lookup(self.internal_state.reg(Reg::Pcx)); + let pc = self.reg(Reg::Pcx); + let instruction = self.mem_read_word(pc); + self.execute(InstructionWord(instruction)); + + // Check if executing the interrupt caused a fault. + if let Some(fault) = self.pending_fault { + self.interrupt(fault); + } + + // always increment clock. + self.internal_state.clock += 1; + } + + self.shutdown(); + + Ok(()) + } + + #[inline] + fn interrupt(&mut self, int: Interrupt) { + let idt = self.reg(Reg::Idr); + + *self.mut_reg(Reg::Spr) -= 4; + let spr = self.reg(Reg::Spr); + let pcx = self.reg(Reg::Pcx); + self.mem_write_word(spr, pcx); + + *self.mut_reg(Reg::Pcx) = self.mem_read_word(idt + int.code() as u32 * 4) + } + #[inline] fn execute(&mut self, word: InstructionWord) { match Opcode::from_u8(word.opcode()) { @@ -118,44 +271,37 @@ impl Emulator { Some(Opcode::Ldbs) => todo!(), Some(Opcode::Ldhs) => todo!(), Some(Opcode::Ldb) => { - *self.mut_reg(word.dest()) = u32::from( - self.mainstore - .read_byte(self.reg(word.src1()) + word.imm16() as u32), - ) + *self.mut_reg(word.dest()) = + u32::from(self.mem_read_byte(self.reg(word.src1()) + word.imm16() as u32)) } Some(Opcode::Ldh) => { - *self.mut_reg(word.dest()) = u32::from( - self.mainstore - .read_word(self.reg(word.src1()) + word.imm16() as u32) - >> 16, - ) + *self.mut_reg(word.dest()) = + u32::from(self.mem_read_word(self.reg(word.src1()) + word.imm16() as u32) >> 16) } Some(Opcode::Ldw) => { - *self.mut_reg(word.dest()) = u32::from( - self.mainstore - .read_word(self.reg(word.src1()) + word.imm16() as u32), - ) + *self.mut_reg(word.dest()) = + u32::from(self.mem_read_word(self.reg(word.src1()) + word.imm16() as u32)) } // store Some(Opcode::Stb) => { - self.mainstore.write_byte( + self.mem_write_byte( self.reg(word.dest()) + word.imm16() as u32, self.reg(word.src1()) as u8, ); } Some(Opcode::Sth) => { - self.mainstore.write_byte( + self.mem_write_byte( self.reg(word.dest()) + word.imm16() as u32, (self.reg(word.src1()) as u16 >> 8) as u8, ); - self.mainstore.write_byte( + self.mem_write_byte( self.reg(word.dest()) + word.imm16() as u32 + 1, self.reg(word.src1()) as u8, ); } Some(Opcode::Stw) => { - self.mainstore.write_word( + self.mem_write_word( self.reg(word.dest()) + word.imm16() as u32, self.reg(word.src1()), ); @@ -260,71 +406,132 @@ impl Emulator { } Some(Opcode::Int) => { - self.interrupt(Interrupt::Generic(word.shamt())); + self.interrupt(Interrupt::Software(word.shamt())); } Some(Opcode::Hlt) => self.internal_state.running = false, None => {} } } - pub fn waiting(&mut self) { - self.internal_state.running = false; - - // Wait for an interrupt or state update to continue. - loop { - // Check for interrupts. - if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) { - self.interrupt(Interrupt::Generic(code)); - break; - } - - // UI requested a state update. - if self.shared_state.update_req.load(Ordering::Relaxed) { - self.update(); - } - - // If we've received a request to continue running. - if self.shared_state.running.load(Ordering::Relaxed) { - break; - } + #[inline] + fn mem_read_byte(&mut self, addr: u32) -> u8 { + if unlikely(self.is_mmio(addr)) { + return self.io_read_byte(addr); } - - self.internal_state.running = true; + self.mainstore.read_byte(addr) } - pub fn run(&mut self) { - self.boot(); - - loop { - // Update UI thread (roughly every 512k cycles targeting UPS) - if unlikely(self.internal_state.clock & 0x7FFFF == 0) { - if self.shared_state.update_req.load(Ordering::Relaxed) { - self.update(); - } - } - - // Check for commands or hardware Interrupts every 32k cycles - if self.internal_state.clock % 0x7FFF == 0 { - if self.shared_state.running.load(Ordering::Relaxed) == false { - self.waiting(); - } - - if let Ok(code) = self.interrupts.try_recv() { - self.interrupt(Interrupt::Generic(code)); - } - } - - // let instruction = self.mmu.lookup(self.internal_state.reg(Reg::Pcx)); - let pc = self.reg(Reg::Pcx); - let instruction = self.memory_mut().read_word(pc); - self.execute(InstructionWord(instruction)); - - // always increment clock. - self.internal_state.clock += 1; + #[inline] + fn mem_write_byte(&mut self, addr: u32, val: u8) { + if unlikely(self.is_mmio(addr)) { + self.io_write_byte(addr, val); + return; } + self.mainstore.write_byte(addr, val); } - fn interrupt(&mut self, int: Interrupt) { + #[inline] + fn mem_read_word(&mut self, addr: u32) -> u32 { + if unlikely(self.is_mmio(addr)) { + return self.io_read_word(addr); + } + self.mainstore.read_word(addr) + } + + #[inline] + fn mem_write_word(&mut self, addr: u32, val: u32) { + if unlikely(self.is_mmio(addr)) { + self.io_write_word(addr, val); + return; + } + self.mainstore.write_word(addr, val); + } + + #[inline] + fn mem_read_page(&mut self, addr: u32) -> &Page { + if unlikely(self.is_mmio(addr)) { + // pages spanning MMIO don't really make sense + // treat as fault + self.pending_fault = Some(Interrupt::ProtectionFault); + return &Page::ZERO; + } + self.mainstore.read_page(addr) + } + + #[inline] + fn mem_write_page(&mut self, addr: u32, val: &Page) { + if unlikely(self.is_mmio(addr)) { + self.pending_fault = Some(Interrupt::ProtectionFault); + return; + } + self.mainstore.write_page(addr, val); + } + + // single MMIO check reused by all of the above + #[inline] + fn is_mmio(&self, addr: u32) -> bool { + self.mmio_region + .map(|(base, end)| addr >= base && addr < end) + .unwrap_or(false) + } + + // --- cold IO paths --- + + #[cold] + fn io_read_byte(&mut self, addr: u32) -> u8 { + // if let Some(device) = self.mmio.iter().find(|d| d.contains(addr)) { + // match device.read_byte(addr) { + // Some(val) => val, + // None => { + // self.pending_fault = Some(Interrupt::ReadFromWriteOnly); + // 0 + // } + // } + // } else { + // self.pending_fault = Some(Interrupt::UnmappedIo); + // 0 + // } + todo!() + } + + #[cold] + fn io_read_word(&mut self, addr: u32) -> u32 { + // if let Some(device) = self.mmio.iter().find(|d| d.contains(addr)) { + // match device.read_word(addr) { + // Some(val) => val, + // None => { + // self.pending_fault = Some(Interrupt::ReadFromWriteOnly); + // 0 + // } + // } + // } else { + // self.pending_fault = Some(Interrupt::UnmappedIo); + // 0 + // } + todo!() + } + + #[cold] + fn io_write_byte(&mut self, addr: u32, val: u8) { + // if let Some(device) = self.mmio.iter_mut().find(|d| d.contains(addr)) { + // if !device.write_byte(addr, val) { + // self.pending_fault = Some(Interrupt::WriteToReadOnly); + // } + // } else { + // self.pending_fault = Some(Interrupt::UnmappedIo); + // } + todo!() + } + + #[cold] + fn io_write_word(&mut self, addr: u32, val: u32) { + // if let Some(device) = self.mmio.iter_mut().find(|d| d.contains(addr)) { + // if !device.write_word(addr, val) { + // self.pending_fault = Some(Interrupt::WriteToReadOnly); + // } + // } else { + // self.pending_fault = Some(Interrupt::UnmappedIo); + // } todo!() } } diff --git a/dsa-emu/src/processor/state.rs b/dsa-emu/src/processor/state.rs index c70e0a5..506c7e1 100644 --- a/dsa-emu/src/processor/state.rs +++ b/dsa-emu/src/processor/state.rs @@ -1,8 +1,4 @@ -use std::sync::{ - Arc, - atomic::{AtomicBool, AtomicU8}, - mpsc, -}; +use std::sync::{Arc, atomic::AtomicBool, mpsc}; use crate::processor::processor::ProcessorSnapshot; use arc_swap::ArcSwap;