Incorporate 8;7;3 Firmware
This change adds version 8;7;3 along side version 8;7;5 firmware, so that the simulated DMD 5620 can run either Version 1.1 or Version 2 DMD software. keeping 8;7;5 firmware.
This commit is contained in:
parent
11703e05fd
commit
26abbce067
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "dmd_core"
|
||||
description = "AT&T / Teletype DMD 5620 Terminal Emulator - Core Library"
|
||||
version = "0.6.5"
|
||||
version = "0.7.0"
|
||||
authors = ["Seth Morabito <web@loomcom.com>"]
|
||||
homepage = "https://github.com/sethm/dmd_core"
|
||||
repository = "https://github.com/sethm/dmd_core"
|
||||
|
@ -11,8 +11,11 @@ license = "MIT"
|
|||
categories = ["simulation"]
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
env_logger = "0.9.0"
|
||||
lazy_static = "~1.4"
|
||||
libc = "~0.2"
|
||||
ringbuffer = "0.8.5"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
45
src/bus.rs
45
src/bus.rs
|
@ -4,9 +4,9 @@ use crate::duart::Duart;
|
|||
use crate::err::BusError;
|
||||
use crate::mem::Mem;
|
||||
use crate::mouse::Mouse;
|
||||
use std::io::Write;
|
||||
use std::{fmt::Debug, fs::OpenOptions};
|
||||
use std::{fs::File, ops::Range};
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
|
||||
const NVRAM_SIZE: usize = 8192;
|
||||
|
||||
|
@ -63,7 +63,6 @@ pub struct Bus {
|
|||
vid: Mem, // TODO: Figure out what device this really is
|
||||
bbram: Mem, // TODO: change to BBRAM when implemented
|
||||
ram: Mem,
|
||||
trace_log: Option<File>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
|
@ -75,7 +74,6 @@ impl Bus {
|
|||
vid: Mem::new(0x500000, 0x2, false),
|
||||
bbram: Mem::new(0x600000, 0x2000, false),
|
||||
ram: Mem::new(0x700000, mem_size, false),
|
||||
trace_log: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,27 +105,6 @@ impl Bus {
|
|||
Err(BusError::NoDevice(address))
|
||||
}
|
||||
|
||||
pub fn trace_on(&mut self, name: &str) -> std::io::Result<()> {
|
||||
let mut out = OpenOptions::new().create(true).write(true).open(name)?;
|
||||
writeln!(out, "TRACE START")?;
|
||||
self.trace_log = Some(out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trace_enabled(&self) -> bool {
|
||||
self.trace_log.is_some()
|
||||
}
|
||||
|
||||
pub fn trace_off(&mut self) {
|
||||
self.trace_log = None;
|
||||
}
|
||||
|
||||
pub fn trace(&mut self, step: u64, line: &str) {
|
||||
if let Some(trace_log) = &mut self.trace_log {
|
||||
let _ = writeln!(trace_log, "{:08}: {}", step, line);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_byte(&mut self, address: usize, access: AccessCode) -> Result<u8, BusError> {
|
||||
self.get_device(address)?.read_byte(address, access)
|
||||
}
|
||||
|
@ -212,20 +189,20 @@ impl Bus {
|
|||
self.duart.mouse_up(button);
|
||||
}
|
||||
|
||||
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
|
||||
self.duart.rs232_tx_poll()
|
||||
pub fn rs232_tx(&mut self) -> Option<u8> {
|
||||
self.duart.rs232_tx()
|
||||
}
|
||||
|
||||
pub fn kb_tx_poll(&mut self) -> Option<u8> {
|
||||
self.duart.kb_tx_poll()
|
||||
pub fn keyboard_tx(&mut self) -> Option<u8> {
|
||||
self.duart.keyboard_tx()
|
||||
}
|
||||
|
||||
pub fn rx_char(&mut self, char: u8) {
|
||||
self.duart.rx_char(char);
|
||||
pub fn rs232_rx(&mut self, c: u8) {
|
||||
self.duart.rs232_rx(c);
|
||||
}
|
||||
|
||||
pub fn rx_keyboard(&mut self, keycode: u8) {
|
||||
self.duart.rx_keyboard(keycode);
|
||||
pub fn keyboard_rx(&mut self, keycode: u8) {
|
||||
self.duart.keyboard_rx(keycode);
|
||||
}
|
||||
|
||||
pub fn duart_output(&self) -> u8 {
|
||||
|
|
37
src/cpu.rs
37
src/cpu.rs
|
@ -3,6 +3,8 @@
|
|||
use crate::bus::{AccessCode, Bus};
|
||||
use crate::err::*;
|
||||
use crate::instr::*;
|
||||
|
||||
use log::{debug, trace};
|
||||
use std::fmt;
|
||||
|
||||
///
|
||||
|
@ -48,14 +50,6 @@ const IPL_TABLE: [u32; 64] = [
|
|||
const WE32100_VERSION: u32 = 0x1a;
|
||||
const HALFWORD_MNEMONIC_COUNT: usize = 11;
|
||||
|
||||
macro_rules! trace {
|
||||
($bus:ident, $steps:expr, $msg:expr) => {
|
||||
if ($bus.trace_enabled()) {
|
||||
$bus.trace($steps, $msg)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub enum ExceptionType {
|
||||
ExternalMemory,
|
||||
}
|
||||
|
@ -640,7 +634,6 @@ pub struct Cpu {
|
|||
//
|
||||
pub r: [u32; 16],
|
||||
error_context: ErrorContext,
|
||||
steps: u64,
|
||||
ir: Instruction,
|
||||
}
|
||||
|
||||
|
@ -655,7 +648,6 @@ impl Cpu {
|
|||
Cpu {
|
||||
r: [0; 16],
|
||||
error_context: ErrorContext::None,
|
||||
steps: 0,
|
||||
ir: Instruction {
|
||||
opcode: 0,
|
||||
name: "???",
|
||||
|
@ -1034,18 +1026,17 @@ impl Cpu {
|
|||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn dispatch(&mut self, bus: &mut Bus) -> Result<i32, CpuError> {
|
||||
self.steps = self.steps.wrapping_add(1);
|
||||
|
||||
// Update anything that needs updating.
|
||||
bus.service();
|
||||
|
||||
if let Some(val) = bus.get_interrupts() {
|
||||
let cpu_ipl = (self.r[R_PSW]) >> 13 & 0xf;
|
||||
if cpu_ipl < IPL_TABLE[(val & 0x3f) as usize] {
|
||||
trace!(
|
||||
bus,
|
||||
self.steps,
|
||||
&format!("[{:08x}] INTERRUPT 0x{:x}", &self.r[R_PC], (!val) & 0x3f)
|
||||
debug!(
|
||||
"[PC={:08x} PSW={:08x}] INTERRUPT 0x{:04x}",
|
||||
&self.r[R_PC],
|
||||
&self.r[R_PSW],
|
||||
(!val) & 0x3f
|
||||
);
|
||||
self.on_interrupt(bus, (!val) & 0x3f);
|
||||
}
|
||||
|
@ -2011,11 +2002,7 @@ impl Cpu {
|
|||
match self.dispatch(bus) {
|
||||
Ok(i) => {
|
||||
// We should have the necessary information to trace after dispatch.
|
||||
trace!(
|
||||
bus,
|
||||
self.steps,
|
||||
&format!("[PC={:08x} PSW={:08x}] {}", &self.r[R_PC], &self.r[R_PSW], &self.ir)
|
||||
);
|
||||
trace!("[PC={:08x} PSW={:08x}] {}", &self.r[R_PC], &self.r[R_PSW], &self.ir);
|
||||
self.r[R_PC] = (self.r[R_PC] as i32 + i) as u32
|
||||
}
|
||||
Err(CpuError::Bus(BusError::NoDevice(_)))
|
||||
|
@ -2526,11 +2513,11 @@ impl Cpu {
|
|||
}
|
||||
Data::Half | Data::UHalf => {
|
||||
self.set_n_flag((val & 0x8000) != 0);
|
||||
self.set_z_flag(val.trailing_zeros() >= 16);
|
||||
self.set_z_flag(val as u16 == 0);
|
||||
}
|
||||
Data::Byte | Data::SByte => {
|
||||
self.set_n_flag((val & 0x80) != 0);
|
||||
self.set_z_flag(val.trailing_zeros() >= 8);
|
||||
self.set_z_flag(val as u8 == 0);
|
||||
}
|
||||
Data::None => {
|
||||
// Intentionally ignored
|
||||
|
@ -2650,10 +2637,6 @@ impl Cpu {
|
|||
pub fn get_psw(&self) -> u32 {
|
||||
self.r[R_PSW]
|
||||
}
|
||||
|
||||
pub fn get_steps(&self) -> u64 {
|
||||
self.steps
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
145
src/dmd.rs
145
src/dmd.rs
|
@ -3,15 +3,15 @@
|
|||
use crate::bus::{AccessCode, Bus};
|
||||
use crate::cpu::Cpu;
|
||||
use crate::err::BusError;
|
||||
use crate::rom_hi::HI_ROM;
|
||||
use crate::rom_lo::LO_ROM;
|
||||
use crate::rom_hi::{HI_ROM_V1, HI_ROM_V2};
|
||||
use crate::rom_lo::{LO_ROM_V1, LO_ROM_V1_LEN, LO_ROM_V2, LO_ROM_V2_LEN};
|
||||
|
||||
use libc::*;
|
||||
use std::sync::Mutex;
|
||||
use std::{ffi::CStr, ptr};
|
||||
use std::ptr;
|
||||
use std::sync::{Mutex, Once};
|
||||
|
||||
lazy_static! {
|
||||
static ref DMD: Mutex<Dmd> = Mutex::new(Dmd::new());
|
||||
pub static ref DMD: Mutex<Dmd> = Mutex::new(Dmd::new());
|
||||
}
|
||||
|
||||
// Return vlaues for the C library
|
||||
|
@ -19,6 +19,8 @@ const SUCCESS: c_int = 0;
|
|||
const ERROR: c_int = 1;
|
||||
const BUSY: c_int = 2;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
pub struct Dmd {
|
||||
cpu: Cpu,
|
||||
bus: Bus,
|
||||
|
@ -26,12 +28,17 @@ pub struct Dmd {
|
|||
|
||||
impl Default for Dmd {
|
||||
fn default() -> Self {
|
||||
Dmd::new()
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Dmd {
|
||||
pub fn new() -> Dmd {
|
||||
// Never re-init logging
|
||||
INIT.call_once(|| {
|
||||
env_logger::init();
|
||||
});
|
||||
|
||||
let cpu = Cpu::new();
|
||||
let bus = Bus::new(0x100000);
|
||||
Dmd {
|
||||
|
@ -40,17 +47,18 @@ impl Dmd {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn trace_on(&mut self, name: &str) -> std::io::Result<()> {
|
||||
self.bus.trace_on(name)
|
||||
}
|
||||
pub fn reset(&mut self, version: u8) -> Result<(), BusError> {
|
||||
match version {
|
||||
1 => {
|
||||
self.bus.load(0, &LO_ROM_V1)?;
|
||||
self.bus.load(LO_ROM_V1_LEN, &HI_ROM_V1)?;
|
||||
}
|
||||
_ => {
|
||||
self.bus.load(0, &LO_ROM_V2)?;
|
||||
self.bus.load(LO_ROM_V2_LEN, &HI_ROM_V2)?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace_off(&mut self) {
|
||||
self.bus.trace_off()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), BusError> {
|
||||
self.bus.load(0, &LO_ROM)?;
|
||||
self.bus.load(0x10000, &HI_ROM)?;
|
||||
self.cpu.reset(&mut self.bus)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -100,20 +108,20 @@ impl Dmd {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
|
||||
self.bus.rs232_tx_poll()
|
||||
pub fn rs232_tx(&mut self) -> Option<u8> {
|
||||
self.bus.rs232_tx()
|
||||
}
|
||||
|
||||
pub fn kb_tx_poll(&mut self) -> Option<u8> {
|
||||
self.bus.kb_tx_poll()
|
||||
pub fn keyboard_tx(&mut self) -> Option<u8> {
|
||||
self.bus.keyboard_tx()
|
||||
}
|
||||
|
||||
pub fn rx_char(&mut self, character: u8) {
|
||||
self.bus.rx_char(character);
|
||||
pub fn rs232_rx(&mut self, c: u8) {
|
||||
self.bus.rs232_rx(c);
|
||||
}
|
||||
|
||||
pub fn rx_keyboard(&mut self, keycode: u8) {
|
||||
self.bus.rx_keyboard(keycode);
|
||||
pub fn keyboard_rx(&mut self, keycode: u8) {
|
||||
self.bus.keyboard_rx(keycode);
|
||||
}
|
||||
|
||||
pub fn mouse_move(&mut self, x: u16, y: u16) {
|
||||
|
@ -146,9 +154,9 @@ impl Dmd {
|
|||
//
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_reset() -> c_int {
|
||||
fn dmd_init(version: u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.reset() {
|
||||
Ok(mut dmd) => match dmd.reset(version) {
|
||||
Ok(()) => SUCCESS,
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
|
@ -175,37 +183,6 @@ fn dmd_step() -> c_int {
|
|||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Uses a raw pointer.
|
||||
///
|
||||
#[no_mangle]
|
||||
pub unsafe fn dmd_trace_on(file_name: *const c_char) -> c_int {
|
||||
let file_name = CStr::from_ptr(file_name);
|
||||
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match file_name.to_str() {
|
||||
Ok(file_name) => match dmd.trace_on(file_name) {
|
||||
Ok(()) => SUCCESS,
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn dmd_trace_off() -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => {
|
||||
dmd.trace_off();
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_step_loop(steps: usize) -> c_int {
|
||||
match DMD.lock() {
|
||||
|
@ -278,28 +255,6 @@ fn dmd_get_duart_output_port(oport: &mut u8) -> c_int {
|
|||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_rx_char(c: u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => {
|
||||
dmd.rx_char(c as u8);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_rx_keyboard(c: u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => {
|
||||
dmd.rx_keyboard(c);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_mouse_move(x: u16, y: u16) -> c_int {
|
||||
match DMD.lock() {
|
||||
|
@ -334,9 +289,31 @@ fn dmd_mouse_up(button: u8) -> c_int {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_rs232_tx_poll(tx_char: &mut u8) -> c_int {
|
||||
fn dmd_rs232_rx(c: u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.rs232_tx_poll() {
|
||||
Ok(mut dmd) => {
|
||||
dmd.rs232_rx(c as u8);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_keyboard_rx(c: u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => {
|
||||
dmd.keyboard_rx(c);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_rs232_tx(tx_char: &mut u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.rs232_tx() {
|
||||
Some(c) => {
|
||||
*tx_char = c;
|
||||
SUCCESS
|
||||
|
@ -348,9 +325,9 @@ fn dmd_rs232_tx_poll(tx_char: &mut u8) -> c_int {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_kb_tx_poll(tx_char: &mut u8) -> c_int {
|
||||
fn dmd_keyboard_tx(tx_char: &mut u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.kb_tx_poll() {
|
||||
Ok(mut dmd) => match dmd.keyboard_tx() {
|
||||
Some(c) => {
|
||||
*tx_char = c;
|
||||
SUCCESS
|
||||
|
@ -390,7 +367,7 @@ mod tests {
|
|||
#[test]
|
||||
fn creates_dmd() {
|
||||
let mut dmd = Dmd::new();
|
||||
dmd.reset().unwrap();
|
||||
dmd.reset(2).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
686
src/duart.rs
686
src/duart.rs
|
@ -1,7 +1,47 @@
|
|||
use crate::bus::AccessCode;
|
||||
use crate::bus::Device;
|
||||
/// The 2681 DUART has two I/O UARTs, each one represented by the
|
||||
/// `Port` struct. The first is used only for the EIA RS232 serial
|
||||
/// port. The second is duplexed between keyboard I/O and a write-only
|
||||
/// parallel printer port.
|
||||
///
|
||||
/// Each of the two UARTs simulates the following hardware registers:
|
||||
///
|
||||
/// - Two MODE registers, duplexed at the same PIO address, with a
|
||||
/// pointer that auto-increments on write.
|
||||
///
|
||||
/// - One STATUS register.
|
||||
///
|
||||
/// - One CONFIGURATION register.
|
||||
///
|
||||
/// - One RECEIVE FIFO with 3 slots. Reading from PIO reads from this
|
||||
/// FIFO.
|
||||
///
|
||||
/// - One RECEIVE shift register that holds bits as they come in,
|
||||
/// before they are pushed onto the FIFO.
|
||||
///
|
||||
/// - One TRANSMIT holding register. Writing to PIO writes to this
|
||||
/// register.
|
||||
///
|
||||
/// - One TRANSMIT shift register that latches the data from the
|
||||
/// holding register and shifts them out onto the serial line.
|
||||
///
|
||||
/// In addition to simulating these hardware registers, this
|
||||
/// implementation has a TX queue and an RX queue. These queues do not
|
||||
/// exist in hardware. They are used to buffer data sent and received
|
||||
/// by users of this library for maximum flexibility, since polling for
|
||||
/// I/O may take place at any unknown rate. The simulated UART will
|
||||
/// poll the RX queue whenever the RX FIFO is not full. It will push
|
||||
/// data onto the TX queue as soon as possible after it has been moved
|
||||
/// into the TX shift register.
|
||||
///
|
||||
/// The 2681 DUART is well documented in its datasheet.
|
||||
///
|
||||
use crate::bus::{AccessCode, Device};
|
||||
use crate::err::BusError;
|
||||
|
||||
use log::debug;
|
||||
use ringbuffer::{
|
||||
ConstGenericRingBuffer, RingBuffer, RingBufferExt, RingBufferRead, RingBufferWrite,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Error;
|
||||
|
@ -46,21 +86,23 @@ const OPBITS_RESET: u8 = 0x3f;
|
|||
//
|
||||
// Port Configuration Bits
|
||||
//
|
||||
const CNF_ETX: u8 = 0x01;
|
||||
const CNF_ERX: u8 = 0x02;
|
||||
const CNF_ETX: u8 = 0x01; // TX Enabled
|
||||
const CNF_ERX: u8 = 0x02; // RX Enabled
|
||||
|
||||
//
|
||||
// Status Flags
|
||||
//
|
||||
const STS_RXR: u8 = 0x01;
|
||||
const STS_TXR: u8 = 0x04;
|
||||
const STS_TXE: u8 = 0x08;
|
||||
const STS_OER: u8 = 0x10;
|
||||
const STS_PER: u8 = 0x20;
|
||||
const STS_FER: u8 = 0x40;
|
||||
const STS_RXR: u8 = 0x01; // Rx Ready
|
||||
const STS_FFL: u8 = 0x02; // FIFO Full
|
||||
const STS_TXR: u8 = 0x04; // Tx Ready
|
||||
const STS_TXE: u8 = 0x08; // Tx Register Empty
|
||||
const STS_OER: u8 = 0x10; // Overflow Error
|
||||
const STS_PER: u8 = 0x20; // Parity Error
|
||||
const STS_FER: u8 = 0x40; // Framing Error
|
||||
const STS_RXB: u8 = 0x80; // Received Break
|
||||
|
||||
//
|
||||
// Commands
|
||||
// Command Register Commands
|
||||
//
|
||||
const CMD_ERX: u8 = 0x01;
|
||||
const CMD_DRX: u8 = 0x02;
|
||||
|
@ -68,57 +110,245 @@ const CMD_ETX: u8 = 0x04;
|
|||
const CMD_DTX: u8 = 0x08;
|
||||
|
||||
//
|
||||
// Interrupt Status Register
|
||||
// Control Register Commands
|
||||
//
|
||||
const ISTS_TAI: u8 = 0x01;
|
||||
const ISTS_RAI: u8 = 0x02;
|
||||
const ISTS_TBI: u8 = 0x10;
|
||||
const ISTS_RBI: u8 = 0x20;
|
||||
const ISTS_IPC: u8 = 0x80;
|
||||
const CR_RST_MR: u8 = 0x01;
|
||||
const CR_RST_RX: u8 = 0x02;
|
||||
const CR_RST_TX: u8 = 0x03;
|
||||
const CR_RST_ERR: u8 = 0x04;
|
||||
const CR_RST_BRK: u8 = 0x05;
|
||||
const CR_START_BRK: u8 = 0x06;
|
||||
const CR_STOP_BRK: u8 = 0x07;
|
||||
|
||||
//
|
||||
// Interrupt Masks
|
||||
// Interrupt Status Register
|
||||
//
|
||||
const ISTS_TAI: u8 = 0x01; // Transmitter A Ready Interrupt
|
||||
const ISTS_RAI: u8 = 0x02; // Receiver A Ready Interrupt
|
||||
const ISTS_DBA: u8 = 0x04; // Delta Break A
|
||||
const ISTS_TBI: u8 = 0x10; // Transmitter B Ready Interrupt
|
||||
const ISTS_RBI: u8 = 0x20; // Receiver B Ready Interrupt
|
||||
const ISTS_DBB: u8 = 0x40; // Delta Break B
|
||||
const ISTS_IPC: u8 = 0x80; // Interrupt Port Change
|
||||
|
||||
//
|
||||
// CPU Interrupt Vectors.
|
||||
//
|
||||
const KEYBOARD_INT: u8 = 0x04;
|
||||
const MOUSE_BLANK_INT: u8 = 0x02;
|
||||
const TX_INT: u8 = 0x10;
|
||||
const RX_INT: u8 = 0x20;
|
||||
|
||||
// NB: The terrible Ring Buffer library requries capacities with a
|
||||
// power of 2, so we have to have extra logic to deal with this!
|
||||
//
|
||||
// TODO: Pick a new ring buffer library or just implement our own
|
||||
// 3-slot FIFO.
|
||||
const RX_FIFO_LEN: usize = 3;
|
||||
const RX_FIFO_INIT_LEN: usize = 4;
|
||||
|
||||
struct Port {
|
||||
// Mode, Status, and Configuration registers
|
||||
mode: [u8; 2],
|
||||
mode_ptr: usize,
|
||||
stat: u8,
|
||||
conf: u8,
|
||||
rx_data: u8,
|
||||
tx_data: Option<u8>,
|
||||
mode_ptr: usize,
|
||||
rx_queue: VecDeque<u8>,
|
||||
tx_queue: VecDeque<u8>,
|
||||
// State used by the RX and TX state machines.
|
||||
rx_fifo: ConstGenericRingBuffer<u8, RX_FIFO_INIT_LEN>,
|
||||
rx_shift_reg: Option<u8>,
|
||||
tx_holding_reg: Option<u8>,
|
||||
tx_shift_reg: Option<u8>,
|
||||
// Buffers to hold TX and RX characters so that they can be
|
||||
// processed by the user of this library in chunks.
|
||||
rx_deque: VecDeque<u8>,
|
||||
tx_deque: VecDeque<u8>,
|
||||
// Service timing info
|
||||
char_delay: Duration,
|
||||
next_rx: Instant,
|
||||
next_tx: Instant,
|
||||
next_tx_service: Instant,
|
||||
next_rx_service: Instant,
|
||||
}
|
||||
|
||||
impl Port {
|
||||
fn new() -> Port {
|
||||
Port {
|
||||
mode: [0; 2],
|
||||
mode_ptr: 0,
|
||||
stat: 0,
|
||||
conf: 0,
|
||||
rx_data: 0,
|
||||
tx_data: None,
|
||||
mode_ptr: 0,
|
||||
rx_queue: VecDeque::new(),
|
||||
tx_queue: VecDeque::new(),
|
||||
rx_fifo: ConstGenericRingBuffer::<u8, RX_FIFO_INIT_LEN>::new(),
|
||||
rx_shift_reg: None,
|
||||
tx_holding_reg: None,
|
||||
tx_shift_reg: None,
|
||||
rx_deque: VecDeque::new(),
|
||||
tx_deque: VecDeque::new(),
|
||||
char_delay: Duration::new(0, 1_000_000),
|
||||
next_rx: Instant::now(),
|
||||
next_tx: Instant::now(),
|
||||
next_tx_service: Instant::now(),
|
||||
next_rx_service: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Port {
|
||||
fn default() -> Self {
|
||||
Port::new()
|
||||
/// Read a single character out of the RX channel.
|
||||
///
|
||||
/// The character is read out of the FIFO, which is drained of one
|
||||
/// slot. If a character is currently in the shift register, it is
|
||||
/// moved into the FIFO now that there is room.
|
||||
fn rx_read_char(&mut self) -> Option<u8> {
|
||||
if self.rx_enabled() {
|
||||
let val = self.rx_fifo.dequeue();
|
||||
|
||||
// The FIFO is no longer full (even if only temporarily)
|
||||
self.stat &= !STS_FFL;
|
||||
|
||||
// If the RX shift register held a character, it's time
|
||||
// to move it into the FIFO.
|
||||
if let Some(c) = self.rx_shift_reg {
|
||||
self.rx_fifo.push(c);
|
||||
self.stat |= STS_RXR;
|
||||
if self.rx_fifo.len() >= RX_FIFO_LEN {
|
||||
self.stat |= STS_FFL;
|
||||
}
|
||||
}
|
||||
|
||||
// If the FIFO is now empty, mark it as such.
|
||||
if self.rx_fifo.is_empty() {
|
||||
self.stat &= !STS_RXR;
|
||||
}
|
||||
|
||||
// Reset Parity Error and Received Break on read
|
||||
self.stat &= !(STS_PER | STS_RXB);
|
||||
|
||||
val
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Receiver a single character.
|
||||
///
|
||||
/// If there is room in the FIFO, the character is immediately
|
||||
/// stored there. If not, it is stored in the shift register until
|
||||
/// room becomes available. The shift register is overwitten if a
|
||||
/// new character is received while the FIFO is full.
|
||||
fn rx_char(&mut self, c: u8) {
|
||||
debug!("rx_char: {:02x}", c);
|
||||
if self.rx_fifo.len() < RX_FIFO_LEN {
|
||||
self.rx_fifo.push(c);
|
||||
if self.rx_fifo.len() >= RX_FIFO_LEN {
|
||||
debug!("FIFO FULL.");
|
||||
self.stat |= STS_FFL;
|
||||
}
|
||||
} else {
|
||||
// The FIFO is full, and now we're going to have to
|
||||
// hold a character in the shift register until space
|
||||
// is available.
|
||||
//
|
||||
// If the register already had data, it's going to be
|
||||
// overwritten, so we have to set the overflow flag.
|
||||
if self.rx_shift_reg.is_some() {
|
||||
self.stat |= STS_OER;
|
||||
}
|
||||
|
||||
self.rx_shift_reg = Some(c);
|
||||
}
|
||||
|
||||
// In either case, RxRDY is now true.
|
||||
self.stat |= STS_RXR;
|
||||
}
|
||||
|
||||
/// Move the receiver state machine
|
||||
fn rx_service(&mut self) {
|
||||
let rx_service_needed = self.rx_enabled()
|
||||
&& !self.rx_deque.is_empty()
|
||||
&& Instant::now() >= self.next_rx_service;
|
||||
|
||||
if !rx_service_needed {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.loopback() {
|
||||
if let Some(c) = self.rx_deque.pop_back() {
|
||||
self.rx_char(c);
|
||||
}
|
||||
}
|
||||
|
||||
self.next_rx_service = Instant::now() + self.char_delay;
|
||||
}
|
||||
|
||||
/// Move the transmitter state machine.
|
||||
fn tx_service(&mut self, keyboard: bool) {
|
||||
let tx_service_needed = self.tx_enabled()
|
||||
&& (self.tx_holding_reg.is_some() || self.tx_shift_reg.is_some())
|
||||
&& Instant::now() >= self.next_tx_service;
|
||||
|
||||
if !tx_service_needed {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for data in the transmitter shift register that's
|
||||
// ready to go out to the RS232 output buffer
|
||||
if let Some(c) = self.tx_shift_reg {
|
||||
debug!("RS232 TX: {:02x}", c);
|
||||
if self.loopback() {
|
||||
debug!("RS232 TX: Port is in loopback.");
|
||||
self.rx_char(c);
|
||||
} else {
|
||||
if keyboard && c == 0x02 {
|
||||
self.stat |= STS_PER;
|
||||
}
|
||||
self.tx_deque.push_front(c);
|
||||
}
|
||||
|
||||
self.tx_shift_reg = None;
|
||||
self.stat |= STS_TXR | STS_TXE;
|
||||
}
|
||||
|
||||
// Check for data in the transmitter holding register that's
|
||||
// ready to go out to the transmitter shift register.
|
||||
if let Some(c) = self.tx_holding_reg {
|
||||
self.tx_shift_reg = Some(c);
|
||||
// Clear the holding register
|
||||
self.tx_holding_reg = None;
|
||||
self.next_tx_service = Instant::now() + self.char_delay;
|
||||
}
|
||||
}
|
||||
|
||||
fn loopback(&self) -> bool {
|
||||
(self.mode[1] & 0xc0) == 0x80
|
||||
}
|
||||
|
||||
fn tx_enabled(&self) -> bool {
|
||||
(self.conf & CNF_ETX) != 0
|
||||
}
|
||||
|
||||
fn rx_enabled(&self) -> bool {
|
||||
(self.conf & CNF_ERX) != 0
|
||||
}
|
||||
|
||||
fn enable_tx(&mut self) {
|
||||
self.conf |= CNF_ETX;
|
||||
if self.tx_holding_reg.is_none() {
|
||||
self.stat |= STS_TXR;
|
||||
self.stat |= STS_TXE;
|
||||
}
|
||||
}
|
||||
|
||||
fn disable_tx(&mut self) {
|
||||
self.conf &= !CNF_ETX;
|
||||
self.stat &= !(STS_TXR | STS_TXE);
|
||||
self.next_rx_service = Instant::now() + Duration::new(0, 10_000_000);
|
||||
}
|
||||
|
||||
fn enable_rx(&mut self) {
|
||||
self.conf |= CNF_ERX;
|
||||
self.stat &= !STS_RXR;
|
||||
self.next_rx_service = Instant::now() + Duration::new(0, 10_000_000);
|
||||
}
|
||||
|
||||
fn disable_rx(&mut self) {
|
||||
self.conf &= !CNF_ERX;
|
||||
self.stat &= !STS_RXR;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,8 +358,11 @@ pub struct Duart {
|
|||
ipcr: u8,
|
||||
inprt: u8,
|
||||
outprt: u8,
|
||||
istat: u8,
|
||||
isr: u8,
|
||||
imr: u8,
|
||||
// TODO: Interrupts are set manually by tweaking this vector,
|
||||
// which doesn't actually exist on the real duart. We should fix
|
||||
// that, because DAMN.
|
||||
ivec: u8,
|
||||
next_vblank: Instant,
|
||||
}
|
||||
|
@ -163,7 +396,7 @@ impl Duart {
|
|||
ipcr: 0x40,
|
||||
inprt: 0xb,
|
||||
outprt: 0,
|
||||
istat: 0,
|
||||
isr: 0,
|
||||
imr: 0,
|
||||
ivec: 0,
|
||||
next_vblank: Instant::now() + Duration::new(0, VERTICAL_BLANK_DELAY),
|
||||
|
@ -176,6 +409,22 @@ impl Duart {
|
|||
self.vertical_blank();
|
||||
}
|
||||
|
||||
// Mask in keyboard and RS232 RX interrupts
|
||||
if (self.ports[PORT_0].stat & STS_RXR) != 0 {
|
||||
self.ivec |= RX_INT;
|
||||
self.isr |= ISTS_RAI;
|
||||
}
|
||||
|
||||
if (self.ports[PORT_1].stat & STS_RXR) != 0 {
|
||||
self.ivec |= KEYBOARD_INT;
|
||||
self.isr |= ISTS_RBI;
|
||||
}
|
||||
|
||||
if (self.ports[PORT_0].stat & STS_TXR) != 0 {
|
||||
self.ivec |= TX_INT;
|
||||
self.isr |= ISTS_TAI;
|
||||
}
|
||||
|
||||
let val = self.ivec;
|
||||
|
||||
if val == 0 {
|
||||
|
@ -185,83 +434,17 @@ impl Duart {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_rx(&mut self, port: usize) {
|
||||
let mut ctx = &mut self.ports[port];
|
||||
|
||||
let (istat, ivec) = match port {
|
||||
0 => (ISTS_RAI, RX_INT),
|
||||
_ => (ISTS_RBI, KEYBOARD_INT),
|
||||
};
|
||||
|
||||
if !ctx.rx_queue.is_empty() && Instant::now() >= ctx.next_rx {
|
||||
if let Some(c) = ctx.rx_queue.pop_back() {
|
||||
if ctx.conf & CNF_ERX != 0 {
|
||||
ctx.rx_data = c;
|
||||
ctx.stat |= STS_RXR;
|
||||
self.istat |= istat;
|
||||
self.ivec |= ivec;
|
||||
}
|
||||
}
|
||||
|
||||
if !ctx.rx_queue.is_empty() {
|
||||
ctx.next_rx = Instant::now() + ctx.char_delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_tx(&mut self, port: usize) {
|
||||
let mut ctx = &mut self.ports[port];
|
||||
|
||||
let (tx_istat, rx_istat) = match port {
|
||||
0 => (ISTS_TAI, ISTS_RAI),
|
||||
_ => (ISTS_TBI, ISTS_RBI),
|
||||
};
|
||||
|
||||
match ctx.tx_data {
|
||||
Some(c) => if Instant::now() >= ctx.next_tx {
|
||||
if (ctx.conf & CNF_ETX) != 0 {
|
||||
ctx.stat |= STS_TXR;
|
||||
ctx.stat |= STS_TXE;
|
||||
}
|
||||
self.istat |= tx_istat;
|
||||
// Only RS232 transmit generates an interrupt.
|
||||
if port == 0 {
|
||||
self.ivec |= TX_INT;
|
||||
}
|
||||
if (ctx.mode[1] >> 6) & 3 == 0x2 {
|
||||
// Loopback Mode.
|
||||
ctx.rx_data = c;
|
||||
ctx.stat |= STS_RXR;
|
||||
self.istat |= rx_istat;
|
||||
self.ivec |= RX_INT;
|
||||
} else {
|
||||
ctx.tx_queue.push_front(c);
|
||||
}
|
||||
ctx.tx_data = None;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service(&mut self) {
|
||||
// Deal with RS-232 Transmit
|
||||
self.handle_tx(PORT_0);
|
||||
|
||||
// Deal with RS-232 Receive
|
||||
self.handle_rx(PORT_0);
|
||||
|
||||
// Deal with Keyboard Transmit
|
||||
// (note: This is used only for keyboard beeps!)
|
||||
self.handle_tx(PORT_1);
|
||||
|
||||
// Deal with Keyboard Receive
|
||||
self.handle_rx(PORT_1);
|
||||
self.ports[PORT_0].tx_service(false);
|
||||
self.ports[PORT_0].rx_service();
|
||||
self.ports[PORT_1].tx_service(true);
|
||||
self.ports[PORT_1].rx_service();
|
||||
}
|
||||
|
||||
pub fn vertical_blank(&mut self) {
|
||||
self.ivec |= MOUSE_BLANK_INT;
|
||||
self.ipcr |= 0x40;
|
||||
self.istat |= ISTS_IPC;
|
||||
self.isr |= ISTS_IPC;
|
||||
|
||||
if self.inprt & 0x04 == 0 {
|
||||
self.ipcr |= 0x40;
|
||||
|
@ -275,18 +458,28 @@ impl Duart {
|
|||
!self.outprt
|
||||
}
|
||||
|
||||
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
|
||||
self.ports[PORT_0].tx_queue.pop_back()
|
||||
/// Queue a single character for processing by the rs232 port.
|
||||
pub fn rs232_rx(&mut self, c: u8) {
|
||||
self.ports[PORT_0].rx_deque.push_front(c);
|
||||
}
|
||||
|
||||
pub fn kb_tx_poll(&mut self) -> Option<u8> {
|
||||
self.ports[PORT_1].tx_queue.pop_back()
|
||||
/// Queue a single character for processing by the keyboard port
|
||||
pub fn keyboard_rx(&mut self, c: u8) {
|
||||
self.ports[PORT_1].rx_deque.push_front(c);
|
||||
}
|
||||
|
||||
pub fn rs232_tx(&mut self) -> Option<u8> {
|
||||
self.ports[PORT_0].tx_deque.pop_back()
|
||||
}
|
||||
|
||||
pub fn keyboard_tx(&mut self) -> Option<u8> {
|
||||
self.ports[PORT_1].tx_deque.pop_back()
|
||||
}
|
||||
|
||||
pub fn mouse_down(&mut self, button: u8) {
|
||||
self.ipcr = 0;
|
||||
self.inprt |= 0xb;
|
||||
self.istat |= ISTS_IPC;
|
||||
self.isr |= ISTS_IPC;
|
||||
self.ivec |= MOUSE_BLANK_INT;
|
||||
match button {
|
||||
0 => {
|
||||
|
@ -308,7 +501,7 @@ impl Duart {
|
|||
pub fn mouse_up(&mut self, button: u8) {
|
||||
self.ipcr = 0;
|
||||
self.inprt |= 0xb;
|
||||
self.istat |= ISTS_IPC;
|
||||
self.isr |= ISTS_IPC;
|
||||
self.ivec |= MOUSE_BLANK_INT;
|
||||
match button {
|
||||
0 => {
|
||||
|
@ -324,83 +517,104 @@ impl Duart {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn rx_keyboard(&mut self, c: u8) {
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
fn handle_command(&mut self, cmd: u8, port_no: usize) {
|
||||
let mut port = &mut self.ports[port_no];
|
||||
|
||||
if ctx.rx_queue.is_empty() {
|
||||
ctx.next_rx = Instant::now() + ctx.char_delay;
|
||||
}
|
||||
let (tx_ists, rx_ists, dbk_ists) = match port_no {
|
||||
PORT_0 => (ISTS_TAI, ISTS_RAI, ISTS_DBA),
|
||||
_ => (ISTS_TBI, ISTS_RBI, ISTS_DBB),
|
||||
};
|
||||
|
||||
ctx.rx_queue.push_front(c);
|
||||
}
|
||||
|
||||
pub fn rx_char(&mut self, c: u8) {
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
|
||||
if ctx.rx_queue.is_empty() {
|
||||
ctx.next_rx = Instant::now() + ctx.char_delay;
|
||||
}
|
||||
|
||||
ctx.rx_queue.push_front(c);
|
||||
}
|
||||
|
||||
pub fn handle_command(&mut self, cmd: u8, port: usize) {
|
||||
if cmd == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut ctx = &mut self.ports[port];
|
||||
|
||||
// Enable or disable transmitter
|
||||
// Enable or disable transmitter. Disable always wins if both
|
||||
// are set.
|
||||
if cmd & CMD_DTX != 0 {
|
||||
ctx.conf &= !CNF_ETX;
|
||||
ctx.stat &= !STS_TXR;
|
||||
ctx.stat &= !STS_TXE;
|
||||
if port == PORT_0 {
|
||||
self.ivec &= !TX_INT;
|
||||
self.istat &= !ISTS_TAI;
|
||||
}
|
||||
debug!("Command: Disable TX");
|
||||
port.disable_tx();
|
||||
self.ivec &= !TX_INT; // XXX: Rethink interrupt vector setting.
|
||||
self.isr &= !tx_ists;
|
||||
} else if cmd & CMD_ETX != 0 {
|
||||
ctx.conf |= CNF_ETX;
|
||||
if ctx.tx_data.is_none() {
|
||||
ctx.stat |= STS_TXR;
|
||||
ctx.stat |= STS_TXE;
|
||||
}
|
||||
if port == PORT_0 {
|
||||
self.istat |= ISTS_TAI;
|
||||
self.ivec |= TX_INT;
|
||||
}
|
||||
debug!("Command: Enable TX");
|
||||
port.enable_tx();
|
||||
self.ivec |= TX_INT; // XXX: Rethink interrupt vector setting
|
||||
self.isr |= tx_ists;
|
||||
}
|
||||
|
||||
// Enable or disable receiver
|
||||
// Enable or disable receiver. Disable always wins, if both are set.
|
||||
if cmd & CMD_DRX != 0 {
|
||||
ctx.conf &= !CNF_ERX;
|
||||
ctx.stat &= !STS_RXR;
|
||||
if port == PORT_0 {
|
||||
self.ivec &= !RX_INT;
|
||||
self.istat &= !ISTS_RAI;
|
||||
debug!("Command: Disable RX");
|
||||
port.disable_rx();
|
||||
self.isr &= !rx_ists;
|
||||
if port_no == PORT_0 {
|
||||
self.ivec &= !RX_INT; // XXX: Rethink interrupt vector setting
|
||||
self.isr &= !ISTS_RAI;
|
||||
} else {
|
||||
self.ivec &= !KEYBOARD_INT;
|
||||
self.istat &= !ISTS_RBI;
|
||||
self.ivec &= !KEYBOARD_INT; // XXX: Rethink interrupt vector setting
|
||||
self.isr &= !ISTS_RBI;
|
||||
}
|
||||
} else if cmd & CMD_ERX != 0 {
|
||||
ctx.conf |= CNF_ERX;
|
||||
ctx.stat |= STS_RXR;
|
||||
debug!("Command: Enable RX");
|
||||
port.enable_rx();
|
||||
}
|
||||
|
||||
// Extra commands
|
||||
match (cmd >> 4) & 7 {
|
||||
1 => ctx.mode_ptr = 0,
|
||||
2 => {
|
||||
ctx.stat |= STS_RXR;
|
||||
ctx.conf |= CNF_ERX;
|
||||
CR_RST_MR => {
|
||||
debug!("PORT{}: Reset MR Pointer.", port_no);
|
||||
port.mode_ptr = 0
|
||||
}
|
||||
3 => {
|
||||
ctx.stat |= STS_TXR;
|
||||
ctx.stat |= STS_TXE;
|
||||
ctx.conf &= !CNF_ETX;
|
||||
CR_RST_RX => {
|
||||
// Reset the channel's receiver as if a hardware reset
|
||||
// had been performed. The receiver is disabled and
|
||||
// the FIFO is flushed.
|
||||
debug!("PORT{}: Reset RX.", port_no);
|
||||
port.stat &= !STS_RXR;
|
||||
port.conf &= !CNF_ERX;
|
||||
port.rx_fifo.clear();
|
||||
port.rx_shift_reg = None;
|
||||
}
|
||||
CR_RST_TX => {
|
||||
// Reset the channel's transmitter as if a hardware reset
|
||||
// had been performed.
|
||||
debug!("PORT{}: Reset TX.", port_no);
|
||||
port.stat &= !(STS_TXR | STS_TXE);
|
||||
port.conf &= !CNF_ETX;
|
||||
port.tx_holding_reg = None;
|
||||
port.tx_shift_reg = None;
|
||||
}
|
||||
CR_RST_ERR => {
|
||||
debug!("PORT{}: Reset Error.", port_no);
|
||||
port.stat &= !(STS_RXB | STS_FER | STS_PER | STS_OER);
|
||||
}
|
||||
CR_RST_BRK => {
|
||||
// Reset the channel's Delta Break interrupt. Causes
|
||||
// the channel's break detect change bit in the
|
||||
// interrupt status register to be cleared to 0.
|
||||
debug!("PORT{}: Reset Break Interrupt.", port_no);
|
||||
self.isr &= !dbk_ists;
|
||||
}
|
||||
CR_START_BRK => {
|
||||
debug!("PORT{}: Start Break.", port_no);
|
||||
if port.loopback() {
|
||||
// We only care about a BREAK condition if it's
|
||||
// looping back to te receiver.
|
||||
//
|
||||
// TODO: We may want to expose a BREAK condition
|
||||
// to the outside world at some point.
|
||||
port.stat |= STS_RXB | STS_PER;
|
||||
// Set "Delta Break"
|
||||
self.isr |= dbk_ists;
|
||||
}
|
||||
}
|
||||
CR_STOP_BRK => {
|
||||
debug!("PORT{}: Stop Break.", port_no);
|
||||
if port.loopback() {
|
||||
// We only care about a BREAK condition if it's
|
||||
// looping back to te receiver.
|
||||
//
|
||||
// Set "Delta Break"
|
||||
self.isr |= dbk_ists;
|
||||
}
|
||||
}
|
||||
4 => ctx.stat &= !(STS_FER | STS_PER | STS_OER),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -431,40 +645,72 @@ impl Device for Duart {
|
|||
let mut ctx = &mut self.ports[PORT_0];
|
||||
let val = ctx.mode[ctx.mode_ptr];
|
||||
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
||||
debug!("READ : MR12A val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
CSRA => {
|
||||
let val = self.ports[PORT_0].stat;
|
||||
debug!("READ : CSRA val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
CSRA => Ok(self.ports[PORT_0].stat),
|
||||
THRA => {
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
ctx.stat &= !STS_RXR;
|
||||
self.istat &= !ISTS_RAI;
|
||||
let ctx = &mut self.ports[PORT_0];
|
||||
self.isr &= !ISTS_RAI;
|
||||
self.ivec &= !RX_INT;
|
||||
Ok(ctx.rx_data)
|
||||
let val = if let Some(c) = ctx.rx_read_char() {
|
||||
c
|
||||
} else {
|
||||
0
|
||||
};
|
||||
debug!("READ : RHRA val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
IPCR_ACR => {
|
||||
let result = self.ipcr;
|
||||
let val = self.ipcr;
|
||||
self.ipcr &= !0x0f;
|
||||
self.ivec = 0;
|
||||
self.istat &= !ISTS_IPC;
|
||||
Ok(result)
|
||||
self.isr &= !ISTS_IPC;
|
||||
debug!("READ : IPCR_ACR val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
ISR_MASK => {
|
||||
let val = self.isr;
|
||||
debug!("READ : ISR_MASK val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
ISR_MASK => Ok(self.istat),
|
||||
MR12B => {
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
let val = ctx.mode[ctx.mode_ptr];
|
||||
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
||||
debug!("READ : MR12B val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
CSRB => Ok(self.ports[PORT_1].stat),
|
||||
THRB => {
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
ctx.stat &= !STS_RXR;
|
||||
self.istat &= !ISTS_RBI;
|
||||
self.ivec &= !KEYBOARD_INT;
|
||||
Ok(ctx.rx_data)
|
||||
CSRB => {
|
||||
let val = self.ports[PORT_1].stat;
|
||||
debug!("READ : CSRB val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
THRB => {
|
||||
let ctx = &mut self.ports[PORT_1];
|
||||
self.isr &= !ISTS_RAI;
|
||||
self.ivec &= !KEYBOARD_INT;
|
||||
let val = if let Some(c) = ctx.rx_read_char() {
|
||||
c
|
||||
} else {
|
||||
0
|
||||
};
|
||||
debug!("READ : RHRB val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
IP_OPCR => {
|
||||
let val = self.inprt;
|
||||
debug!("READ : IP_OPCR val={:02x}", val);
|
||||
Ok(val)
|
||||
}
|
||||
_ => {
|
||||
debug!("READ : UNHANDLED. ADDRESS={:08x}", address);
|
||||
Err(BusError::NoDevice(address))
|
||||
}
|
||||
IP_OPCR => Ok(self.inprt),
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -481,70 +727,88 @@ impl Device for Duart {
|
|||
fn write_byte(&mut self, address: usize, val: u8, _access: AccessCode) -> Result<(), BusError> {
|
||||
match (address - START_ADDR) as u8 {
|
||||
MR12A => {
|
||||
debug!("WRITE: MR12A, val={:02x}", val);
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
ctx.mode[ctx.mode_ptr] = val;
|
||||
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
||||
}
|
||||
CSRA => {
|
||||
debug!("WRITE: CSRA, val={:02x}", val);
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
ctx.char_delay = Duration::new(0, delay_rate(val, self.acr));
|
||||
}
|
||||
CRA => {
|
||||
debug!("WRITE: CRA, val={:02x}", val);
|
||||
self.handle_command(val, PORT_0);
|
||||
}
|
||||
THRA => {
|
||||
debug!("WRITE: THRA, val={:02x}", val);
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
ctx.tx_data = Some(val);
|
||||
ctx.tx_holding_reg = Some(val);
|
||||
// Update state. Since we're transmitting, the
|
||||
// transmitter buffer is not empty. The actual
|
||||
// transmit will happen in the 'service' function.
|
||||
ctx.next_tx = Instant::now() + ctx.char_delay;
|
||||
ctx.next_tx_service = Instant::now() + ctx.char_delay;
|
||||
ctx.stat &= !(STS_TXE | STS_TXR);
|
||||
self.istat &= !ISTS_TAI;
|
||||
self.isr &= !ISTS_TAI;
|
||||
self.ivec &= !TX_INT;
|
||||
}
|
||||
IPCR_ACR => {
|
||||
debug!("WRITE: IPCR_ACR, val={:02x}", val);
|
||||
self.acr = val;
|
||||
// If we're changing the baud rate generator
|
||||
// selection, update the character delays.
|
||||
if (val & 0x80) != 0 {
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
ctx.char_delay = Duration::new(0, delay_rate(val, self.acr));
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
ctx.char_delay = Duration::new(0, delay_rate(val, self.acr));
|
||||
}
|
||||
}
|
||||
ISR_MASK => {
|
||||
debug!("WRITE: ISR_MASK, val={:02x}", val);
|
||||
self.imr = val;
|
||||
}
|
||||
MR12B => {
|
||||
debug!("WRITE: MR12B, val={:02x}", val);
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
ctx.mode[ctx.mode_ptr] = val;
|
||||
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
||||
}
|
||||
CSRB => {
|
||||
debug!("WRITE: CSRB, val={:02x}", val);
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
ctx.char_delay = Duration::new(0, delay_rate(val, self.acr));
|
||||
}
|
||||
CRB => {
|
||||
debug!("WRITE: CRB, val={:02x}", val);
|
||||
self.handle_command(val, PORT_1);
|
||||
}
|
||||
THRB => {
|
||||
debug!("WRITE: THRB, val={:02x}", val);
|
||||
// TODO: When OP3 is low, do not send data to
|
||||
// the keyboard! It's meant for the printer.
|
||||
|
||||
// Keyboard transmit requires special handling,
|
||||
// because the only things the terminal transmits to
|
||||
// the keyboard are status requests, or keyboard beep
|
||||
// requests. We ignore status requests, and only
|
||||
// put beep requests into the queue.
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
|
||||
if (val & 0x08) != 0 {
|
||||
ctx.tx_data = Some(val);
|
||||
ctx.next_tx = Instant::now() + ctx.char_delay;
|
||||
ctx.stat &= !(STS_TXE | STS_TXR);
|
||||
self.istat &= !ISTS_TBI;
|
||||
}
|
||||
ctx.tx_holding_reg = Some(val);
|
||||
ctx.next_tx_service = Instant::now() + ctx.char_delay;
|
||||
ctx.stat &= !(STS_TXE | STS_TXR);
|
||||
self.isr &= !ISTS_TBI;
|
||||
}
|
||||
IP_OPCR => {
|
||||
debug!("WRITE: IP_OPCR, val={:02x}", val);
|
||||
// Not implemented
|
||||
}
|
||||
OPBITS_SET => {
|
||||
debug!("WRITE: OPBITS_SET, val={:02x}", val);
|
||||
self.outprt |= val;
|
||||
}
|
||||
OPBITS_RESET => {
|
||||
debug!("WRITE: OPBITS_RESET, val={:02x}", val);
|
||||
self.outprt &= !val;
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
debug!("WRITE: UNHANDLED. ADDRESS={:08x}", address);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -12,3 +12,4 @@ pub mod rom_lo;
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate libc;
|
||||
extern crate log;
|
||||
|
|
|
@ -5,6 +5,8 @@ use crate::bus::Device;
|
|||
use crate::err::BusError;
|
||||
use std::ops::Range;
|
||||
|
||||
use log::debug;
|
||||
|
||||
const START_ADDRESS: usize = 0x400000;
|
||||
const END_ADDRESS: usize = 0x4000004;
|
||||
const ADDRESS_RANGE: Range<usize> = START_ADDRESS..END_ADDRESS;
|
||||
|
@ -48,6 +50,7 @@ impl Device for Mouse {
|
|||
}
|
||||
|
||||
fn read_half(&mut self, address: usize, _access: AccessCode) -> Result<u16, BusError> {
|
||||
debug!("Mouse Read, address={:08x}", address);
|
||||
match address - START_ADDRESS {
|
||||
0 => Ok(self.y),
|
||||
2 => Ok(self.x),
|
||||
|
|
2054
src/rom_hi.rs
2054
src/rom_hi.rs
File diff suppressed because it is too large
Load Diff
2054
src/rom_lo.rs
2054
src/rom_lo.rs
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue