Files
DSA/dsa/emulator/src/io/mod.rs
T

125 lines
4.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use serde::{Deserialize, Serialize};
use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt};
pub mod display;
pub struct MappedDevice {
pub base: u32,
pub device: Box<dyn IoDevice>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct IoAccess(u8);
impl IoAccess {
pub const READ: Self = Self(0b01);
pub const WRITE: Self = Self(0b10);
/// Convenience alias for Read + Write.
pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
pub const NONE: Self = Self(0);
/// Bitwise OR returns a new `IoAccess` that contains all flags from both operands.
#[inline]
pub fn or(self, other: Self) -> Self {
Self(self.0 | other.0)
}
/// Check if a flag is present.
#[inline]
pub fn contains(&self, flag: Self) -> bool {
self.0 & flag.0 != 0
}
}
#[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, writeonly 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) -> Result<u8, Interrupt> {
if self.access().contains(IoAccess::READ) {
Ok(0)
} else {
Err(Interrupt::ReadFromWriteOnly)
}
}
#[inline]
/// Write a single byte to the specified address.
///
/// By default, readonly 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) -> Result<(), Interrupt> {
if self.access().contains(IoAccess::WRITE) {
Ok(())
} else {
Err(Interrupt::WriteToReadOnly)
}
}
/// Read a 32bit word from the specified address in littleendian order.
///
/// The default implementation composes four consecutive byte reads using
/// `read_byte`. If any byte read fails, the whole operation returns `None`.
/// Writeonly devices return `None` immediately. Override this method for
/// more efficient word reads.
#[inline]
fn read_word(&self, offset: u32) -> Result<u32, Interrupt> {
if self.access().contains(IoAccess::READ) {
// 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;
return Ok(b0 | b1 << 8 | b2 << 16 | b3 << 24);
}
Err(Interrupt::ReadFromWriteOnly)
}
/// Write a 32bit word to the specified address in littleendian order.
///
/// The default implementation splits the value into bytes and writes each
/// using `write_byte`. Readonly 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) -> Result<(), Interrupt> {
if self.access().contains(IoAccess::WRITE) {
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]);
return Ok(());
}
Err(Interrupt::WriteToReadOnly)
}
}