commit ebb9489410a45a1ad5914703d9e2b2fabe65e283 Author: zxq5 Date: Tue Mar 3 10:26:19 2026 +0000 initial commit diff --git a/dsa-emu/.gitignore b/dsa-emu/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/dsa-emu/.gitignore @@ -0,0 +1 @@ +/target diff --git a/dsa-emu/Cargo.lock b/dsa-emu/Cargo.lock new file mode 100644 index 0000000..4af1e7b --- /dev/null +++ b/dsa-emu/Cargo.lock @@ -0,0 +1,143 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arc-swap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "dsa-emu" +version = "0.1.0" +dependencies = [ + "arc-swap", + "fxhash", + "ron", + "serde", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/dsa-emu/Cargo.toml b/dsa-emu/Cargo.toml new file mode 100644 index 0000000..f1cbc85 --- /dev/null +++ b/dsa-emu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "dsa-emu" +version = "0.1.0" +edition = "2024" + +[dependencies] +arc-swap = "1.8.2" +fxhash = "0.2.1" +ron = "0.12.0" +serde = { version = "1.0.228", features = ["derive"] } diff --git a/dsa-emu/config/dsa.boot.ron b/dsa-emu/config/dsa.boot.ron new file mode 100644 index 0000000..aaa5c58 --- /dev/null +++ b/dsa-emu/config/dsa.boot.ron @@ -0,0 +1,7 @@ +LoadConfig( + // defined as the bootloader in dsa.mmap.ron + start_address: 0x1000, + + // maximum allowed bootloader size (as to not overlap MMIO) + max_size: 0x4000, +) diff --git a/dsa-emu/config/dsa.mmap.ron b/dsa-emu/config/dsa.mmap.ron new file mode 100644 index 0000000..a589872 --- /dev/null +++ b/dsa-emu/config/dsa.mmap.ron @@ -0,0 +1,41 @@ +MemoryMap( + table_addr: 0x1000, + regions: [ + // 0000-1000 = reserved + Region( + base: 0x0, + size: 0x1000, + region_type: Reserved + ), + // 1000-4000 = bootloader + Region( + base: 0x1000, + size: 0x3000, + region_type: Bootloader + ), + // 5000-0FFFFFFF = MMIO + Region( + base: 0x5000, + size: 0x0FFF_FFFF, + region_type: MMIO + ), + // 10000000-BFFFFFFF = userspace + Region( + base: 0x1000_0000, + size: 0xBFFF_FFFF, + region_type: Usable + ), + // C0000000-C0FFFFFF = kernel page tables + Region( + base: 0xC000_0000, + size: 0x100_0000, + region_type: KernelTables + ), + // C1000000-FFFFFFFF = kernel + Region( + base: 0xC100_0000, + size: 0x3EFF_FFFF, + region_type: Kernel + ) + ] +) diff --git a/dsa-emu/src/main.rs b/dsa-emu/src/main.rs new file mode 100644 index 0000000..e3e32a5 --- /dev/null +++ b/dsa-emu/src/main.rs @@ -0,0 +1,32 @@ +use arc_swap::ArcSwap; +use std::{ + sync::{ + Arc, + atomic::{AtomicBool, AtomicU8}, + }, + thread, + time::Duration, +}; + +use crate::processor::{processor::Emulator, state::SharedState}; + +mod memory; +mod processor; + +fn main() { + let mut emulator = Emulator::new(); + let state = emulator.state_handle(); + let runner = thread::spawn(move || emulator.run()); + let observer = thread::spawn(|| observe(state)); + + runner.join().unwrap(); + observer.join().unwrap(); +} + +fn observe(state: Arc) { + loop { + thread::sleep(std::time::Duration::from_millis(100)); + let state = state.proc.load(); + println!("GP Registers: {:?}", state.gp_registers); + } +} diff --git a/dsa-emu/src/memory/cache.rs b/dsa-emu/src/memory/cache.rs new file mode 100644 index 0000000..e69de29 diff --git a/dsa-emu/src/memory/mainstore.rs b/dsa-emu/src/memory/mainstore.rs new file mode 100644 index 0000000..3981d29 --- /dev/null +++ b/dsa-emu/src/memory/mainstore.rs @@ -0,0 +1,60 @@ +use fxhash::FxHashMap; + +use crate::memory::PhysAddr; + +type Page = [u8; 4096]; + +pub struct MainStore { + pages: FxHashMap, +} + +impl MainStore { + pub fn new() -> Self { + Self { + pages: FxHashMap::default(), + } + } + + #[inline(always)] + const fn segment_addr(addr: PhysAddr) -> (PhysAddr, usize) { + (addr & !(0xFFF), (addr & 0xFFF) as usize) + } + + #[inline] + pub 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) + } + + #[inline] + pub fn read_word(&self, addr: PhysAddr) -> u32 { + let (page, offset) = Self::segment_addr(addr); + let page = self.pages.get(&page).unwrap_or(&[0; 4096]); + u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap()) + } + + #[inline] + pub fn read_page(&self, addr: PhysAddr) -> &[u8] { + self.pages.get(&addr).unwrap_or(&[0; 4096]) + } + + #[inline] + pub 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; + } + + #[inline] + pub fn write_word(&mut self, addr: PhysAddr, value: u32) { + let (page, offset) = Self::segment_addr(addr); + let page = self.pages.entry(page).or_insert_with(|| [0; 4096]); + page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes()); + } + + #[inline] + pub fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { + let (page, offset) = Self::segment_addr(addr); + let page = self.pages.entry(page).or_insert_with(|| [0; 4096]); + page[offset..offset + value.len()].copy_from_slice(value); + } +} diff --git a/dsa-emu/src/memory/mmu.rs b/dsa-emu/src/memory/mmu.rs new file mode 100644 index 0000000..761634d --- /dev/null +++ b/dsa-emu/src/memory/mmu.rs @@ -0,0 +1,109 @@ +use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB}; + +pub struct MMU { + buffer: TLB, + enable_paging: bool, + + // Memory Management Register (MMR) Tells MMU where to find page tables + mmr_value: u32, + + page_table: Box<[Option; 1024]>, +} + +impl MMU { + pub fn new() -> Self { + MMU { + buffer: TLB::new(), + enable_paging: false, + mmr_value: 0, + page_table: Box::new([const { None }; 1024]), + } + } + + pub fn paging_enabled(&mut self, enable: bool) { + self.enable_paging = enable; + } + + pub fn tlb_insert(&mut self, virtual_address: VirtAddr, physical_address: PhysAddr) { + self.buffer.insert(virtual_address, physical_address); + } + + pub fn create_mapping(&mut self, virtual_address: VirtAddr, physical_address: PhysAddr) { + let table = (virtual_address >> 22) as usize; + let page = ((virtual_address >> 12) & 0x3FF) as usize; + + self.page_table[table].get_or_insert_default().entries[page] = + Some(PageTableEntry::new(physical_address, true, true, false)); + } + + #[inline(always)] + pub fn lookup(&mut self, virtual_address: VirtAddr) -> Result { + if !self.enable_paging { + return Ok(virtual_address); + } + + if let Some(addr) = self.buffer.lookup(virtual_address) { + return Ok(addr); + } + + self.walk(virtual_address) + } + + #[cold] + #[inline(never)] + fn walk(&mut self, virtual_address: VirtAddr) -> Result { + let phys_addr = self.walk_page_table(virtual_address)?; + self.buffer.insert(virtual_address, phys_addr); + Ok(phys_addr) + } + + fn walk_page_table(&mut self, virtual_address: VirtAddr) -> Result { + let table = (virtual_address >> 22) as usize; + let page = ((virtual_address >> 12) & 0x3FF) as usize; + + if let Some(table) = &self.page_table[table] { + return table.entries[page] + .map(|entry| entry.physical_addr) + .ok_or(FaultInfo::PageFault); + } else { + Err(FaultInfo::PageFault) + } + } +} + +#[derive(Debug, Clone)] +struct PageDir { + entries: Box<[PageTable; 1024]>, +} + +#[derive(Debug, Clone)] +struct PageTable { + entries: Box<[Option; 1024]>, +} + +impl Default for PageTable { + fn default() -> Self { + Self { + entries: Box::new([None; 1024]), + } + } +} + +#[derive(Debug, Clone, Copy)] +struct PageTableEntry { + physical_addr: PhysAddr, + writable: bool, + present: bool, + user: bool, +} + +impl PageTableEntry { + fn new(physical_addr: PhysAddr, writable: bool, present: bool, user: bool) -> Self { + Self { + physical_addr, + writable, + present, + user, + } + } +} diff --git a/dsa-emu/src/memory/mod.rs b/dsa-emu/src/memory/mod.rs new file mode 100644 index 0000000..ccd5e15 --- /dev/null +++ b/dsa-emu/src/memory/mod.rs @@ -0,0 +1,65 @@ +use serde::{Deserialize, Serialize}; + +use crate::memory::mmu::MMU; + +mod cache; +pub mod mainstore; +pub mod mmu; +mod tlb; + +pub type VirtAddr = u32; +pub type PhysAddr = u32; + +pub enum FaultInfo { + PageFault, +} + +#[repr(u32)] +#[derive(Serialize, Deserialize, Clone, Copy)] +enum RegionType { + Reserved, + Bootloader, + Kernel, + KernelTables, + Usable, + MMIO, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +pub struct Region { + base: PhysAddr, + size: usize, + region_type: RegionType, +} + +#[derive(Serialize, Deserialize)] +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 identity_map(mmu: &mut MMU, region: &Region) { + let first_page = region.base >> 12; + let page_count = (region.size >> 12) as u32; + + 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.ron")).unwrap() + } +} diff --git a/dsa-emu/src/memory/tlb.rs b/dsa-emu/src/memory/tlb.rs new file mode 100644 index 0000000..2a04745 --- /dev/null +++ b/dsa-emu/src/memory/tlb.rs @@ -0,0 +1,54 @@ +use crate::memory::{PhysAddr, VirtAddr}; + +const TLB_SIZE: usize = 256; +const TLB_MASK: u32 = (TLB_SIZE - 1) as u32; + +pub struct TLB { + entries: [TlbEntry; TLB_SIZE], +} + +#[derive(Debug, Clone, Copy)] +struct TlbEntry { + virtual_address: VirtAddr, + physical_address: PhysAddr, + valid: bool, +} + +impl TLB { + pub fn new() -> Self { + TLB { + entries: [TlbEntry { + virtual_address: 0, + physical_address: 0, + valid: false, + }; TLB_SIZE], + } + } + + #[inline(always)] + fn index(page: u32) -> usize { + let hash = page ^ (page >> 12); + (hash & TLB_MASK) as usize + } + + #[inline(always)] + pub fn lookup(&self, virtual_address: VirtAddr) -> Option { + let page = virtual_address >> 12; // for 4KB pages + let entry = self.entries[Self::index(page)]; + if entry.valid && entry.virtual_address == virtual_address { + Some(entry.physical_address) + } else { + None + } + } + + #[inline(always)] + pub fn insert(&mut self, virt: VirtAddr, phys: PhysAddr) { + let idx = Self::index(virt) as usize; + self.entries[idx] = TlbEntry { + virtual_address: virt, + physical_address: phys, + valid: true, + }; + } +} diff --git a/dsa-emu/src/processor/interrupts.rs b/dsa-emu/src/processor/interrupts.rs new file mode 100644 index 0000000..80bf748 --- /dev/null +++ b/dsa-emu/src/processor/interrupts.rs @@ -0,0 +1,4 @@ +pub enum Interrupt { + PageFault, + ProtectionFault, +} diff --git a/dsa-emu/src/processor/mod.rs b/dsa-emu/src/processor/mod.rs new file mode 100644 index 0000000..8f75033 --- /dev/null +++ b/dsa-emu/src/processor/mod.rs @@ -0,0 +1,3 @@ +mod interrupts; +pub mod processor; +pub mod state; diff --git a/dsa-emu/src/processor/processor.rs b/dsa-emu/src/processor/processor.rs new file mode 100644 index 0000000..fb76771 --- /dev/null +++ b/dsa-emu/src/processor/processor.rs @@ -0,0 +1,67 @@ +use std::{sync::Arc, thread, time::Duration}; + +use crate::{ + memory::{FaultInfo, MemoryMap, mainstore::MainStore, mmu::MMU}, + processor::{ + interrupts::Interrupt, + state::{ProcessorSnapshot, SharedState}, + }, +}; + +pub struct Emulator { + internal_state: ProcessorSnapshot, + shared_state: Arc, + + mmu: MMU, + mainstore: MainStore, +} + +impl Emulator { + pub fn new() -> Self { + Self { + internal_state: ProcessorSnapshot::default(), + shared_state: Arc::new(SharedState::new()), + mmu: MMU::new(), + mainstore: MainStore::new(), + } + } + + pub fn state_handle(&self) -> Arc { + self.shared_state.clone() + } + + pub fn run(&mut self) { + let mut clock = 0; + + let regions = MemoryMap::default(); + + self.mmu.paging_enabled(true); + MemoryMap::identity_map(&mut self.mmu, regions.regions.get(0).unwrap()); + + loop { + thread::sleep(Duration::from_micros(1000)); + + match self.mmu.lookup(clock) { + Ok(addr) => { + let x = self.mainstore.read_word(addr); + println!("{}", x); + self.mainstore.write_word(addr, x + 1); + } + Err(FaultInfo::PageFault) => self.interrupt(Interrupt::PageFault), + } + + self.internal_state.gp_registers[0] += 10; + + if clock % 1000 == 0 { + self.shared_state + .proc + .store(Arc::new(self.internal_state.clone())); + } + clock += 1; + } + } + + fn interrupt(&mut self, int: Interrupt) { + todo!() + } +} diff --git a/dsa-emu/src/processor/state.rs b/dsa-emu/src/processor/state.rs new file mode 100644 index 0000000..8b4aabb --- /dev/null +++ b/dsa-emu/src/processor/state.rs @@ -0,0 +1,37 @@ +use std::sync::{ + Arc, + atomic::{AtomicBool, AtomicU8}, +}; + +use arc_swap::ArcSwap; + +pub struct SharedState { + pub proc: ArcSwap, + + pub running: AtomicBool, + pub interrupt: AtomicU8, +} + +impl SharedState { + pub fn new() -> Self { + Self { + proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())), + + running: AtomicBool::new(true), + interrupt: AtomicU8::new(0), + } + } +} + +#[derive(Debug, Clone)] +pub struct ProcessorSnapshot { + pub gp_registers: [u32; 16], +} + +impl Default for ProcessorSnapshot { + fn default() -> Self { + Self { + gp_registers: [0; 16], + } + } +}