parent
4131b2262c
commit
c9161324b5
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "dmd_core"
|
||||
description = "AT&T / Teletype DMD 5620 Terminal Emulator - Core Library"
|
||||
version = "0.7.0"
|
||||
version = "0.6.5"
|
||||
authors = ["Seth Morabito <web@loomcom.com>"]
|
||||
homepage = "https://github.com/sethm/dmd_core"
|
||||
repository = "https://github.com/sethm/dmd_core"
|
||||
|
|
12
src/bus.rs
12
src/bus.rs
|
@ -41,10 +41,6 @@ pub trait Device: Send + Sync + Debug {
|
|||
fn write_half(&mut self, address: usize, val: u16, access: AccessCode) -> Result<(), BusError>;
|
||||
fn write_word(&mut self, address: usize, val: u32, access: AccessCode) -> Result<(), BusError>;
|
||||
fn load(&mut self, address: usize, data: &[u8]) -> Result<(), BusError>;
|
||||
fn dirty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn clear_dirty(&mut self) {}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -171,14 +167,6 @@ impl Bus {
|
|||
self.ram.as_slice(start..end)
|
||||
}
|
||||
|
||||
pub fn video_ram_dirty(&self) -> bool {
|
||||
self.vid.dirty()
|
||||
}
|
||||
|
||||
pub fn video_ram_clear_dirty(&mut self) {
|
||||
self.vid.clear_dirty()
|
||||
}
|
||||
|
||||
pub fn service(&mut self) {
|
||||
self.duart.service();
|
||||
}
|
||||
|
|
133
src/dmd.rs
133
src/dmd.rs
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use crate::bus::{AccessCode, Bus};
|
||||
use crate::bus::{Bus, AccessCode};
|
||||
use crate::cpu::Cpu;
|
||||
use crate::err::BusError;
|
||||
use crate::rom_hi::HI_ROM;
|
||||
|
@ -52,14 +52,6 @@ impl Dmd {
|
|||
self.bus.video_ram()
|
||||
}
|
||||
|
||||
pub fn video_ram_dirty(&self) -> bool {
|
||||
self.bus.video_ram_dirty()
|
||||
}
|
||||
|
||||
pub fn video_ram_clear_dirty(&mut self) {
|
||||
self.bus.video_ram_clear_dirty()
|
||||
}
|
||||
|
||||
pub fn get_pc(&self) -> u32 {
|
||||
self.cpu.get_pc()
|
||||
}
|
||||
|
@ -148,34 +140,23 @@ impl Dmd {
|
|||
#[no_mangle]
|
||||
fn dmd_reset() -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.reset() {
|
||||
Ok(()) => SUCCESS,
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
Ok(mut dmd) => {
|
||||
match dmd.reset() {
|
||||
Ok(()) => SUCCESS,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_video_ram() -> *const u8 {
|
||||
match DMD.lock() {
|
||||
Ok(dmd) => dmd.video_ram().as_ptr(),
|
||||
Err(_) => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_video_ram_dirty() -> bool {
|
||||
match DMD.lock() {
|
||||
Ok(dmd) => dmd.video_ram_dirty(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_video_ram_clear_dirty() {
|
||||
if let Ok(mut dmd) = DMD.lock() {
|
||||
dmd.video_ram_clear_dirty();
|
||||
Ok(dmd) => {
|
||||
dmd.video_ram().as_ptr()
|
||||
}
|
||||
Err(_) => ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +167,7 @@ fn dmd_step() -> c_int {
|
|||
dmd.step();
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,8 +177,8 @@ fn dmd_step_loop(steps: usize) -> c_int {
|
|||
Ok(mut dmd) => {
|
||||
dmd.run(steps);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,8 +188,8 @@ fn dmd_get_pc(pc: &mut u32) -> c_int {
|
|||
Ok(dmd) => {
|
||||
*pc = dmd.get_pc();
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,36 +199,40 @@ fn dmd_get_register(reg: u8, val: &mut u32) -> c_int {
|
|||
Ok(dmd) => {
|
||||
*val = dmd.get_register(reg);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
},
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_read_word(addr: u32, val: &mut u32) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.read_word(addr as usize) {
|
||||
Some(word) => {
|
||||
*val = word;
|
||||
SUCCESS
|
||||
Ok(mut dmd) => {
|
||||
match dmd.read_word(addr as usize) {
|
||||
Some(word) => {
|
||||
*val = word;
|
||||
SUCCESS
|
||||
},
|
||||
None => ERROR
|
||||
}
|
||||
None => ERROR,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_read_byte(addr: u32, val: &mut u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.read_byte(addr as usize) {
|
||||
Some(byte) => {
|
||||
*val = byte;
|
||||
SUCCESS
|
||||
Ok(mut dmd) => {
|
||||
match dmd.read_byte(addr as usize) {
|
||||
Some(byte) => {
|
||||
*val = byte;
|
||||
SUCCESS
|
||||
},
|
||||
None => ERROR
|
||||
}
|
||||
None => ERROR,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,7 +243,7 @@ fn dmd_get_duart_output_port(oport: &mut u8) -> c_int {
|
|||
*oport = dmd.duart_output();
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +254,7 @@ fn dmd_rx_char(c: u8) -> c_int {
|
|||
dmd.rx_char(c as u8);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +265,7 @@ fn dmd_rx_keyboard(c: u8) -> c_int {
|
|||
dmd.rx_keyboard(c);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +276,7 @@ fn dmd_mouse_move(x: u16, y: u16) -> c_int {
|
|||
dmd.mouse_move(x, y);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,7 +287,7 @@ fn dmd_mouse_down(button: u8) -> c_int {
|
|||
dmd.mouse_down(button);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,35 +298,39 @@ fn dmd_mouse_up(button: u8) -> c_int {
|
|||
dmd.mouse_up(button);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_rs232_tx_poll(tx_char: &mut u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.rs232_tx_poll() {
|
||||
Some(c) => {
|
||||
*tx_char = c;
|
||||
SUCCESS
|
||||
Ok(mut dmd) => {
|
||||
match dmd.rs232_tx_poll() {
|
||||
Some(c) => {
|
||||
*tx_char = c;
|
||||
SUCCESS
|
||||
}
|
||||
None => BUSY
|
||||
}
|
||||
None => BUSY,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn dmd_kb_tx_poll(tx_char: &mut u8) -> c_int {
|
||||
match DMD.lock() {
|
||||
Ok(mut dmd) => match dmd.kb_tx_poll() {
|
||||
Some(c) => {
|
||||
*tx_char = c;
|
||||
SUCCESS
|
||||
Ok(mut dmd) => {
|
||||
match dmd.kb_tx_poll() {
|
||||
Some(c) => {
|
||||
*tx_char = c;
|
||||
SUCCESS
|
||||
}
|
||||
None => BUSY
|
||||
}
|
||||
None => BUSY,
|
||||
},
|
||||
Err(_) => ERROR,
|
||||
}
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,7 +341,7 @@ fn dmd_set_nvram(nvram: &[u8; 8192]) -> c_int {
|
|||
dmd.set_nvram(nvram);
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,7 +352,7 @@ fn dmd_get_nvram(nvram: &mut [u8; 8192]) -> c_int {
|
|||
nvram.clone_from_slice(dmd.get_nvram());
|
||||
SUCCESS
|
||||
}
|
||||
Err(_) => ERROR,
|
||||
Err(_) => ERROR
|
||||
}
|
||||
}
|
||||
|
||||
|
|
51
src/duart.rs
51
src/duart.rs
|
@ -4,28 +4,34 @@ use crate::bus::AccessCode;
|
|||
use crate::bus::Device;
|
||||
use crate::err::BusError;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Error;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
const START_ADDR: usize = 0x200000;
|
||||
const END_ADDR: usize = 0x2000040;
|
||||
const ADDRESS_RANGE: Range<usize> = START_ADDR..END_ADDR;
|
||||
|
||||
// Vertical blanks should occur at 60Hz. This value is in nanoseconds
|
||||
const VERTICAL_BLANK_DELAY: u32 = 16_666_666; // 60 Hz
|
||||
const VERTICAL_BLANK_DELAY: u32 = 16_666_666; // 60 Hz
|
||||
|
||||
// Delay rates, in nanoseconds, selected when ACR[7] = 0
|
||||
const DELAY_RATES_A: [u32; 13] =
|
||||
[160000000, 72727272, 59259260, 40000000, 26666668, 13333334, 6666667, 7619047, 3333333, 1666666, 1111111, 833333, 208333];
|
||||
const DELAY_RATES_A: [u32;13] = [
|
||||
160000000, 72727272, 59259260, 40000000,
|
||||
26666668, 13333334, 6666667, 7619047,
|
||||
3333333, 1666666, 1111111, 833333, 208333
|
||||
];
|
||||
|
||||
// Delay rates, in nanoseconds, selected when ACR[7] = 1
|
||||
const DELAY_RATES_B: [u32; 13] =
|
||||
[106666672, 72727272, 59259260, 53333336, 26666668, 13333334, 6666667, 4000000, 3333333, 1666666, 4444444, 833333, 416666];
|
||||
const DELAY_RATES_B: [u32;13] = [
|
||||
106666672, 72727272, 59259260, 53333336,
|
||||
26666668, 13333334, 6666667, 4000000,
|
||||
3333333, 1666666, 4444444, 833333, 416666
|
||||
];
|
||||
|
||||
const PORT_0: usize = 0;
|
||||
const PORT_1: usize = 1;
|
||||
|
@ -47,6 +53,7 @@ const IP_OPCR: u8 = 0x37;
|
|||
const OPBITS_SET: u8 = 0x3b;
|
||||
const OPBITS_RESET: u8 = 0x3f;
|
||||
|
||||
|
||||
//
|
||||
// Port Configuration Bits
|
||||
//
|
||||
|
@ -89,7 +96,7 @@ const TX_INT: u8 = 0x10;
|
|||
const RX_INT: u8 = 0x20;
|
||||
|
||||
struct Port {
|
||||
mode: [u8; 2],
|
||||
mode: [u8;2],
|
||||
stat: u8,
|
||||
conf: u8,
|
||||
rx_data: u8,
|
||||
|
@ -135,7 +142,7 @@ pub struct Duart {
|
|||
istat: u8,
|
||||
imr: u8,
|
||||
ivec: u8,
|
||||
next_vblank: Instant,
|
||||
next_vblank: Instant
|
||||
}
|
||||
|
||||
impl Default for Duart {
|
||||
|
@ -147,7 +154,10 @@ impl Default for Duart {
|
|||
impl Duart {
|
||||
pub fn new() -> Duart {
|
||||
Duart {
|
||||
ports: [Port::new(), Port::new()],
|
||||
ports: [
|
||||
Port::new(),
|
||||
Port::new(),
|
||||
],
|
||||
acr: 0,
|
||||
ipcr: 0x40,
|
||||
inprt: 0xb,
|
||||
|
@ -206,7 +216,10 @@ impl Duart {
|
|||
_ => (ISTS_TBI, ISTS_RBI),
|
||||
};
|
||||
|
||||
if (ctx.conf & CNF_ETX) != 0 && (ctx.stat & STS_TXR) == 0 && (ctx.stat & STS_TXE) == 0 && Instant::now() >= ctx.next_tx {
|
||||
if (ctx.conf & CNF_ETX) != 0 &&
|
||||
(ctx.stat & STS_TXR) == 0 &&
|
||||
(ctx.stat & STS_TXE) == 0 && Instant::now() >= ctx.next_tx
|
||||
{
|
||||
let c = ctx.tx_data;
|
||||
ctx.stat |= STS_TXR;
|
||||
ctx.stat |= STS_TXE;
|
||||
|
@ -382,7 +395,7 @@ impl Duart {
|
|||
ctx.stat |= STS_TXE;
|
||||
ctx.conf &= !CNF_ETX;
|
||||
}
|
||||
4 => ctx.stat &= !(STS_FER | STS_PER | STS_OER),
|
||||
4 => ctx.stat &= !(STS_FER|STS_PER|STS_OER),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -415,7 +428,9 @@ impl Device for Duart {
|
|||
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
||||
Ok(val)
|
||||
}
|
||||
CSRA => Ok(self.ports[PORT_0].stat),
|
||||
CSRA => {
|
||||
Ok(self.ports[PORT_0].stat)
|
||||
}
|
||||
THRA => {
|
||||
let mut ctx = &mut self.ports[PORT_0];
|
||||
ctx.stat &= !STS_RXR;
|
||||
|
@ -430,14 +445,18 @@ impl Device for Duart {
|
|||
self.istat &= !ISTS_IPC;
|
||||
Ok(result)
|
||||
}
|
||||
ISR_MASK => Ok(self.istat),
|
||||
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;
|
||||
Ok(val)
|
||||
}
|
||||
CSRB => Ok(self.ports[PORT_1].stat),
|
||||
CSRB => {
|
||||
Ok(self.ports[PORT_1].stat)
|
||||
}
|
||||
THRB => {
|
||||
let mut ctx = &mut self.ports[PORT_1];
|
||||
ctx.stat &= !STS_RXR;
|
||||
|
@ -445,7 +464,9 @@ impl Device for Duart {
|
|||
self.ivec &= !KEYBOARD_INT;
|
||||
Ok(ctx.rx_data)
|
||||
}
|
||||
IP_OPCR => Ok(self.inprt),
|
||||
IP_OPCR => {
|
||||
Ok(self.inprt)
|
||||
}
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
pub mod bus;
|
||||
pub mod cpu;
|
||||
pub mod dmd;
|
||||
pub mod duart;
|
||||
pub mod err;
|
||||
pub mod instr;
|
||||
pub mod mem;
|
||||
pub mod duart;
|
||||
pub mod mouse;
|
||||
pub mod rom_hi;
|
||||
pub mod rom_lo;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate libc;
|
||||
extern crate libc;
|
45
src/mem.rs
45
src/mem.rs
|
@ -6,27 +6,25 @@ use crate::err::BusError;
|
|||
use std::fmt::Debug;
|
||||
use std::fmt::Error;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Range;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::vec::Vec;
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct Mem {
|
||||
address_range: Range<usize>,
|
||||
len: usize,
|
||||
ram: Vec<u8>,
|
||||
is_read_only: bool,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
/// Memory is a Device with a single address range.
|
||||
impl Mem {
|
||||
pub fn new(start_address: usize, len: usize, is_read_only: bool) -> Mem {
|
||||
Mem {
|
||||
address_range: start_address..start_address + len,
|
||||
address_range: start_address..start_address+len,
|
||||
len,
|
||||
ram: vec![0; len],
|
||||
is_read_only,
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +91,7 @@ impl Device for Mem {
|
|||
u32::from(self.ram[offset]).wrapping_shl(24)
|
||||
| u32::from(self.ram[offset + 1]).wrapping_shl(16)
|
||||
| u32::from(self.ram[offset + 2]).wrapping_shl(8)
|
||||
| u32::from(self.ram[offset + 3]),
|
||||
| u32::from(self.ram[offset + 3])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -109,9 +107,6 @@ impl Device for Mem {
|
|||
if address >= self.address_range().end {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
if self.ram[offset] != val {
|
||||
self.dirty = true;
|
||||
}
|
||||
self.ram[offset] = val;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -127,13 +122,8 @@ impl Device for Mem {
|
|||
if address >= self.address_range().end {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
let b1 = (val.wrapping_shr(8) & 0xff) as u8;
|
||||
let b2 = (val & 0xff) as u8;
|
||||
if self.ram[offset] != b1 || self.ram[offset + 1] != b2 {
|
||||
self.dirty = true;
|
||||
}
|
||||
self.ram[offset] = b1;
|
||||
self.ram[offset + 1] = b2;
|
||||
self.ram[offset] = (val.wrapping_shr(8) & 0xff) as u8;
|
||||
self.ram[offset + 1] = (val & 0xff) as u8;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -148,19 +138,10 @@ impl Device for Mem {
|
|||
if address >= self.address_range().end {
|
||||
Err(BusError::Range)
|
||||
} else {
|
||||
let b1 = (val.wrapping_shr(24) & 0xff) as u8;
|
||||
let b2 = (val.wrapping_shr(16) & 0xff) as u8;
|
||||
let b3 = (val.wrapping_shr(8) & 0xff) as u8;
|
||||
let b4 = (val & 0xff) as u8;
|
||||
|
||||
if self.ram[offset] != b1 || self.ram[offset + 1] != b2 || self.ram[offset + 2] != b3 || self.ram[offset + 3] != b4 {
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
self.ram[offset] = b1;
|
||||
self.ram[offset + 1] = b2;
|
||||
self.ram[offset + 2] = b3;
|
||||
self.ram[offset + 3] = b4;
|
||||
self.ram[offset] = (val.wrapping_shr(24) & 0xff) as u8;
|
||||
self.ram[offset + 1] = (val.wrapping_shr(16) & 0xff) as u8;
|
||||
self.ram[offset + 2] = (val.wrapping_shr(8) & 0xff) as u8;
|
||||
self.ram[offset + 3] = (val & 0xff) as u8;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -179,14 +160,6 @@ impl Device for Mem {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn clear_dirty(&mut self) {
|
||||
self.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Mem {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use crate::bus::AccessCode;
|
||||
use crate::bus::Device;
|
||||
use crate::bus::AccessCode;
|
||||
use crate::err::BusError;
|
||||
use std::ops::Range;
|
||||
|
||||
|
@ -48,7 +48,7 @@ impl Device for Mouse {
|
|||
}
|
||||
|
||||
fn read_half(&mut self, address: usize, _access: AccessCode) -> Result<u16, BusError> {
|
||||
match address - START_ADDRESS {
|
||||
match address-START_ADDRESS {
|
||||
0 => Ok(self.y),
|
||||
2 => Ok(self.x),
|
||||
_ => Err(BusError::NoDevice(address as u32)),
|
||||
|
@ -74,4 +74,4 @@ impl Device for Mouse {
|
|||
fn load(&mut self, _address: usize, _data: &[u8]) -> Result<(), BusError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue