progress, still some TODO's before we can start running instructions. need to finish up the initial IO/mem then port the old assembler to DSAv2
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ name = "bench_mainstore"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mainstore-bulkalloc", "mainstore-arraymap"]
|
default = ["mainstore-bulkalloc"]
|
||||||
|
|
||||||
# Memory Bank Features
|
# Memory Bank Features
|
||||||
mainstore-bulkalloc = [] # Fastest for Writes
|
mainstore-bulkalloc = [] # Fastest for Writes
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use criterion::{
|
|||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use dsa::RandomAccessMemory;
|
use dsa::{Page, RandomAccessMemory};
|
||||||
|
|
||||||
type PhysAddr = u32;
|
type PhysAddr = u32;
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ fn run_write_page(
|
|||||||
addrs: &[PhysAddr],
|
addrs: &[PhysAddr],
|
||||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||||
) {
|
) {
|
||||||
let page_data = [0xABu8; 4096];
|
let page_data = Page::from([0xABu8; 4096]);
|
||||||
group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096));
|
group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096));
|
||||||
for (name, mem) in implementations.iter_mut() {
|
for (name, mem) in implementations.iter_mut() {
|
||||||
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
IoMapping(
|
||||||
|
device: Display,
|
||||||
|
base: 0x0000_0000,
|
||||||
|
size: 0x0000_07D0, // 2000 bytes (80x25)
|
||||||
|
),
|
||||||
|
],
|
||||||
+25
-3
@@ -1,8 +1,9 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{MemoryMap, RandomAccessMemory};
|
use crate::{MemoryMap, RandomAccessMemory, config::IoMapping, io::DeviceId};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
@@ -12,17 +13,38 @@ pub struct DsaArgs {
|
|||||||
#[arg(long = "mmap")]
|
#[arg(long = "mmap")]
|
||||||
memory_map: Option<PathBuf>,
|
memory_map: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long = "iomap")]
|
||||||
|
io_map: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(value_enum, long = "mem", default_value = "bulk-alloc")]
|
#[arg(value_enum, long = "mem", default_value = "bulk-alloc")]
|
||||||
pub memory_bank: MemoryBank,
|
pub memory_bank: MemoryBank,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DsaArgs {
|
impl DsaArgs {
|
||||||
pub fn get_memory_map(&self) -> Option<MemoryMap> {
|
pub fn get_memory_map(&self) -> MemoryMap {
|
||||||
self.memory_map.as_ref().and_then(|m| {
|
self.memory_map
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|m| {
|
||||||
fs::read_to_string(m)
|
fs::read_to_string(m)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|map| ron::from_str(&map).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<IoMapping> {
|
||||||
|
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()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
IoMapping(
|
||||||
|
device: Display,
|
||||||
|
base: 0x0000_0000,
|
||||||
|
size: 0x0000_07D0, // 2000 bytes (80x25)
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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<Region>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<impl RandomAccessMemory>) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
use std::sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::io::{IoAccess, IoDevice};
|
||||||
|
|
||||||
|
pub struct DisplayDevice {
|
||||||
|
buffer: Arc<Box<[AtomicU8]>>,
|
||||||
|
dirty: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayDevice {
|
||||||
|
pub fn new(width: usize, height: usize) -> (Self, DisplayHandle) {
|
||||||
|
let buffer = Arc::new(
|
||||||
|
(0..width * height)
|
||||||
|
.map(|_| AtomicU8::new(0))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.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<Box<[AtomicU8]>>,
|
||||||
|
pub dirty: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayHandle {
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty.swap(false, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_all(&self) -> Vec<u8> {
|
||||||
|
self.buffer
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.load(Ordering::Relaxed))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::RandomAccessMemory;
|
||||||
|
|
||||||
|
pub mod display;
|
||||||
|
|
||||||
|
pub struct MappedDevice {
|
||||||
|
pub base: u32,
|
||||||
|
pub device: Box<dyn IoDevice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<u8> {
|
||||||
|
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<u32> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-2
@@ -1,11 +1,12 @@
|
|||||||
#![feature(likely_unlikely)]
|
#![feature(likely_unlikely)]
|
||||||
#![feature(ptr_as_ref_unchecked)]
|
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
mod common;
|
mod common;
|
||||||
|
pub mod config;
|
||||||
|
pub mod io;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod processor;
|
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::*;
|
pub use memory::ram::*;
|
||||||
|
|||||||
+28
-6
@@ -1,32 +1,54 @@
|
|||||||
use clap::Parser;
|
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};
|
use std::{sync::Arc, thread};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = DsaArgs::parse();
|
let args = DsaArgs::parse();
|
||||||
|
|
||||||
let mut emulator = Emulator::new(BulkAllocStore::new());
|
let mmap = args.get_memory_map();
|
||||||
emulator.apply_memory_map(args.get_memory_map().unwrap_or_default());
|
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();
|
let state = emulator.state_handle();
|
||||||
|
|
||||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||||
let runner = thread::Builder::new()
|
let runner = thread::Builder::new()
|
||||||
.stack_size(STACK_SIZE)
|
.stack_size(STACK_SIZE)
|
||||||
.spawn(move || emulator.run())
|
.spawn(move || emulator.run().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let observer = thread::spawn(|| observe(state));
|
let observer = thread::spawn(|| observe(state, handle));
|
||||||
|
|
||||||
runner.join().unwrap();
|
runner.join().unwrap();
|
||||||
observer.join().unwrap();
|
observer.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// todo: remove this!
|
/// todo: remove this!
|
||||||
fn observe(state: Arc<SharedState>) {
|
fn observe(state: Arc<SharedState>, handle: DisplayHandle) {
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(std::time::Duration::from_millis(100));
|
thread::sleep(std::time::Duration::from_millis(100));
|
||||||
let state = state.proc.load();
|
let state = state.proc.load();
|
||||||
println!("GP Registers: {:?}", state.registers);
|
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!();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,75 +13,3 @@ pub type PhysAddr = u32;
|
|||||||
pub enum FaultInfo {
|
pub enum FaultInfo {
|
||||||
PageFault,
|
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<Region>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<impl RandomAccessMemory>) {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,15 +55,15 @@ impl RandomAccessMemory for ArrayStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] {
|
fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
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)]
|
#[inline(always)]
|
||||||
@@ -92,7 +92,7 @@ impl RandomAccessMemory for ArrayStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
@@ -100,6 +100,8 @@ impl RandomAccessMemory for ArrayStore {
|
|||||||
self.pages[idx] = self.alloc();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,15 +82,15 @@ impl RandomAccessMemory for BulkAllocStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] {
|
fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
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)]
|
#[inline(always)]
|
||||||
@@ -119,7 +119,7 @@ impl RandomAccessMemory for BulkAllocStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
@@ -127,6 +127,8 @@ impl RandomAccessMemory for BulkAllocStore {
|
|||||||
self.pages[idx] = self.alloc();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,41 +23,44 @@ impl RandomAccessMemory for HashStore {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
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)]
|
#[inline(always)]
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32 {
|
fn read_word(&self, addr: PhysAddr) -> u32 {
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
let (page, offset) = Self::segment_addr(addr);
|
||||||
debug_assert_eq!(offset % 4, 0);
|
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())
|
u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] {
|
fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 0x1000, 0);
|
debug_assert_eq!(addr % 0x1000, 0);
|
||||||
self.pages.get(&addr).unwrap_or(&[0; 4096])
|
self.pages.get(&addr).unwrap_or(&Page::ZERO)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
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)]
|
#[inline(always)]
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
let (page, offset) = Self::segment_addr(addr);
|
||||||
debug_assert_eq!(offset % 4, 0);
|
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());
|
page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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);
|
debug_assert_eq!(addr % 0x1000, 0);
|
||||||
let page = self.pages.entry(addr).or_insert_with(|| [0; 4096]);
|
let page = self.pages.entry(addr).or_insert_with(|| Page::zeroed());
|
||||||
page.copy_from_slice(value);
|
page.0 = value.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::memory::PhysAddr;
|
use crate::memory::PhysAddr;
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-arraymap")]
|
#[cfg(feature = "mainstore-arraymap")]
|
||||||
@@ -27,7 +29,37 @@ pub use prealloc::PreAllocStore;
|
|||||||
|
|
||||||
const NUM_PAGES: usize = 2 << 20;
|
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)]
|
#[inline(always)]
|
||||||
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
||||||
@@ -43,7 +75,7 @@ pub trait RandomAccessMemory {
|
|||||||
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::memory::{PhysAddr, RandomAccessMemory, ram::NUM_PAGES};
|
use crate::memory::{
|
||||||
|
PhysAddr, RandomAccessMemory,
|
||||||
|
ram::{NUM_PAGES, Page},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct PreAllocStore {
|
pub struct PreAllocStore {
|
||||||
heap: Box<[u8; NUM_PAGES * 4096]>,
|
heap: Box<[u8; NUM_PAGES * 4096]>,
|
||||||
@@ -25,9 +28,9 @@ impl RandomAccessMemory for PreAllocStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] {
|
fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
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)]
|
#[inline(always)]
|
||||||
@@ -41,7 +44,7 @@ impl RandomAccessMemory for PreAllocStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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);
|
self.heap[addr as usize..addr as usize + 4096].copy_from_slice(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,15 +53,15 @@ impl RandomAccessMemory for StackArrayStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] {
|
fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
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)]
|
#[inline(always)]
|
||||||
@@ -90,7 +90,7 @@ impl RandomAccessMemory for StackArrayStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
let (idx, _) = idx(addr);
|
let (idx, _) = idx(addr);
|
||||||
|
|
||||||
@@ -98,6 +98,6 @@ impl RandomAccessMemory for StackArrayStore {
|
|||||||
self.pages[idx] = self.alloc();
|
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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,52 @@
|
|||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum Interrupt {
|
pub enum Interrupt {
|
||||||
PageFault,
|
// CPU exceptions 0-31
|
||||||
ProtectionFault,
|
InvalidInterrupt = 0,
|
||||||
Generic(u8),
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,44 @@
|
|||||||
use std::{
|
use std::{
|
||||||
hint::unlikely,
|
hint::unlikely,
|
||||||
sync::{Arc, atomic::Ordering, mpsc},
|
sync::{
|
||||||
thread,
|
Arc,
|
||||||
|
atomic::Ordering,
|
||||||
|
mpsc::{self, TryRecvError},
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Page,
|
||||||
common::instructions::{InstructionWord, Opcode, Reg},
|
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},
|
processor::{interrupts::Interrupt, state::SharedState},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Emulator<Mem: RandomAccessMemory> {
|
pub struct Emulator<Mem: RandomAccessMemory> {
|
||||||
internal_state: ProcessorSnapshot,
|
internal_state: ProcessorSnapshot,
|
||||||
shared_state: Arc<SharedState>,
|
shared_state: Arc<SharedState>,
|
||||||
|
|
||||||
|
// Interrupts
|
||||||
interrupts: mpsc::Receiver<u8>,
|
interrupts: mpsc::Receiver<u8>,
|
||||||
|
pending_fault: Option<Interrupt>,
|
||||||
|
|
||||||
// memory
|
// memory
|
||||||
mmu: MMU,
|
mmu: MMU,
|
||||||
mainstore: Mem,
|
mainstore: Mem,
|
||||||
|
|
||||||
|
// IO
|
||||||
|
mmio: Vec<MappedDevice>,
|
||||||
|
mmio_region: Option<(u32, u32)>, // start end end of segment
|
||||||
|
|
||||||
// optionals - not necessarily set on boot.
|
// optionals - not necessarily set on boot.
|
||||||
memory_map: Option<MemoryMap>,
|
memory_map: Option<MemoryMap>,
|
||||||
|
|
||||||
|
// Config params
|
||||||
|
__mmap_configured: bool,
|
||||||
|
__io_configured: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
|
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
|
||||||
@@ -31,14 +48,33 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
interrupts: receiver,
|
interrupts: receiver,
|
||||||
|
pending_fault: None,
|
||||||
|
|
||||||
internal_state: ProcessorSnapshot::default(),
|
internal_state: ProcessorSnapshot::default(),
|
||||||
shared_state: Arc::new(SharedState::new(sender)),
|
shared_state: Arc::new(SharedState::new(sender)),
|
||||||
|
|
||||||
|
memory_map: None,
|
||||||
mmu: MMU::new(),
|
mmu: MMU::new(),
|
||||||
mainstore: mem,
|
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<SharedState> {
|
pub fn state_handle(&self) -> Arc<SharedState> {
|
||||||
self.shared_state.clone()
|
self.shared_state.clone()
|
||||||
}
|
}
|
||||||
@@ -53,29 +89,6 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
&mut self.mainstore
|
&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]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reg(&self, reg: Reg) -> u32 {
|
pub fn reg(&self, reg: Reg) -> u32 {
|
||||||
@@ -98,6 +111,146 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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<IoMapping>) -> Result<Self, String> {
|
||||||
|
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]
|
#[inline]
|
||||||
fn execute(&mut self, word: InstructionWord) {
|
fn execute(&mut self, word: InstructionWord) {
|
||||||
match Opcode::from_u8(word.opcode()) {
|
match Opcode::from_u8(word.opcode()) {
|
||||||
@@ -118,44 +271,37 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
Some(Opcode::Ldbs) => todo!(),
|
Some(Opcode::Ldbs) => todo!(),
|
||||||
Some(Opcode::Ldhs) => todo!(),
|
Some(Opcode::Ldhs) => todo!(),
|
||||||
Some(Opcode::Ldb) => {
|
Some(Opcode::Ldb) => {
|
||||||
*self.mut_reg(word.dest()) = u32::from(
|
*self.mut_reg(word.dest()) =
|
||||||
self.mainstore
|
u32::from(self.mem_read_byte(self.reg(word.src1()) + word.imm16() as u32))
|
||||||
.read_byte(self.reg(word.src1()) + word.imm16() as u32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Some(Opcode::Ldh) => {
|
Some(Opcode::Ldh) => {
|
||||||
*self.mut_reg(word.dest()) = u32::from(
|
*self.mut_reg(word.dest()) =
|
||||||
self.mainstore
|
u32::from(self.mem_read_word(self.reg(word.src1()) + word.imm16() as u32) >> 16)
|
||||||
.read_word(self.reg(word.src1()) + word.imm16() as u32)
|
|
||||||
>> 16,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Some(Opcode::Ldw) => {
|
Some(Opcode::Ldw) => {
|
||||||
*self.mut_reg(word.dest()) = u32::from(
|
*self.mut_reg(word.dest()) =
|
||||||
self.mainstore
|
u32::from(self.mem_read_word(self.reg(word.src1()) + word.imm16() as u32))
|
||||||
.read_word(self.reg(word.src1()) + word.imm16() as u32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// store
|
// store
|
||||||
Some(Opcode::Stb) => {
|
Some(Opcode::Stb) => {
|
||||||
self.mainstore.write_byte(
|
self.mem_write_byte(
|
||||||
self.reg(word.dest()) + word.imm16() as u32,
|
self.reg(word.dest()) + word.imm16() as u32,
|
||||||
self.reg(word.src1()) as u8,
|
self.reg(word.src1()) as u8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(Opcode::Sth) => {
|
Some(Opcode::Sth) => {
|
||||||
self.mainstore.write_byte(
|
self.mem_write_byte(
|
||||||
self.reg(word.dest()) + word.imm16() as u32,
|
self.reg(word.dest()) + word.imm16() as u32,
|
||||||
(self.reg(word.src1()) as u16 >> 8) as u8,
|
(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.dest()) + word.imm16() as u32 + 1,
|
||||||
self.reg(word.src1()) as u8,
|
self.reg(word.src1()) as u8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(Opcode::Stw) => {
|
Some(Opcode::Stw) => {
|
||||||
self.mainstore.write_word(
|
self.mem_write_word(
|
||||||
self.reg(word.dest()) + word.imm16() as u32,
|
self.reg(word.dest()) + word.imm16() as u32,
|
||||||
self.reg(word.src1()),
|
self.reg(word.src1()),
|
||||||
);
|
);
|
||||||
@@ -260,71 +406,132 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Some(Opcode::Int) => {
|
Some(Opcode::Int) => {
|
||||||
self.interrupt(Interrupt::Generic(word.shamt()));
|
self.interrupt(Interrupt::Software(word.shamt()));
|
||||||
}
|
}
|
||||||
Some(Opcode::Hlt) => self.internal_state.running = false,
|
Some(Opcode::Hlt) => self.internal_state.running = false,
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn waiting(&mut self) {
|
#[inline]
|
||||||
self.internal_state.running = false;
|
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
// Wait for an interrupt or state update to continue.
|
return self.io_read_byte(addr);
|
||||||
loop {
|
}
|
||||||
// Check for interrupts.
|
self.mainstore.read_byte(addr)
|
||||||
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
|
||||||
self.interrupt(Interrupt::Generic(code));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI requested a state update.
|
#[inline]
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||||
self.update();
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
self.io_write_byte(addr, val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.mainstore.write_byte(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've received a request to continue running.
|
#[inline]
|
||||||
if self.shared_state.running.load(Ordering::Relaxed) {
|
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
||||||
break;
|
if unlikely(self.is_mmio(addr)) {
|
||||||
|
return self.io_read_word(addr);
|
||||||
}
|
}
|
||||||
|
self.mainstore.read_word(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.internal_state.running = true;
|
#[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);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
#[inline]
|
||||||
self.boot();
|
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
||||||
|
if unlikely(self.is_mmio(addr)) {
|
||||||
loop {
|
// pages spanning MMIO don't really make sense
|
||||||
// Update UI thread (roughly every 512k cycles targeting UPS)
|
// treat as fault
|
||||||
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
|
self.pending_fault = Some(Interrupt::ProtectionFault);
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
return &Page::ZERO;
|
||||||
self.update();
|
|
||||||
}
|
}
|
||||||
|
self.mainstore.read_page(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for commands or hardware Interrupts every 32k cycles
|
#[inline]
|
||||||
if self.internal_state.clock % 0x7FFF == 0 {
|
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
||||||
if self.shared_state.running.load(Ordering::Relaxed) == false {
|
if unlikely(self.is_mmio(addr)) {
|
||||||
self.waiting();
|
self.pending_fault = Some(Interrupt::ProtectionFault);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.mainstore.write_page(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(code) = self.interrupts.try_recv() {
|
// single MMIO check reused by all of the above
|
||||||
self.interrupt(Interrupt::Generic(code));
|
#[inline]
|
||||||
}
|
fn is_mmio(&self, addr: u32) -> bool {
|
||||||
|
self.mmio_region
|
||||||
|
.map(|(base, end)| addr >= base && addr < end)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// let instruction = self.mmu.lookup(self.internal_state.reg(Reg::Pcx));
|
// --- cold IO paths ---
|
||||||
let pc = self.reg(Reg::Pcx);
|
|
||||||
let instruction = self.memory_mut().read_word(pc);
|
|
||||||
self.execute(InstructionWord(instruction));
|
|
||||||
|
|
||||||
// always increment clock.
|
#[cold]
|
||||||
self.internal_state.clock += 1;
|
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!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interrupt(&mut self, int: Interrupt) {
|
#[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!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
use std::sync::{
|
use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
||||||
Arc,
|
|
||||||
atomic::{AtomicBool, AtomicU8},
|
|
||||||
mpsc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::processor::processor::ProcessorSnapshot;
|
use crate::processor::processor::ProcessorSnapshot;
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
|||||||
Reference in New Issue
Block a user