dmd_core/src/duart.rs

562 lines
15 KiB
Rust

#![allow(clippy::unreadable_literal)]
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;
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
// 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,
];
// 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 PORT_0: usize = 0;
const PORT_1: usize = 1;
//
// Registers
//
const MR12A: u8 = 0x03;
const CSRA: u8 = 0x07;
const CRA: u8 = 0x0b;
const THRA: u8 = 0x0f;
const IPCR_ACR: u8 = 0x13;
const ISR_MASK: u8 = 0x17;
const MR12B: u8 = 0x23;
const CSRB: u8 = 0x27;
const CRB: u8 = 0x2b;
const THRB: u8 = 0x2f;
const IP_OPCR: u8 = 0x37;
const OPBITS_SET: u8 = 0x3b;
const OPBITS_RESET: u8 = 0x3f;
//
// Port Configuration Bits
//
const CNF_ETX: u8 = 0x01;
const CNF_ERX: u8 = 0x02;
//
// 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;
//
// Commands
//
const CMD_ERX: u8 = 0x01;
const CMD_DRX: u8 = 0x02;
const CMD_ETX: u8 = 0x04;
const CMD_DTX: u8 = 0x08;
//
// Interrupt Status Register
//
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;
//
// Interrupt Masks
//
const KEYBOARD_INT: u8 = 0x04;
const MOUSE_BLANK_INT: u8 = 0x02;
const TX_INT: u8 = 0x10;
const RX_INT: u8 = 0x20;
struct Port {
mode: [u8; 2],
stat: u8,
conf: u8,
rx_data: u8,
tx_data: u8,
mode_ptr: usize,
rx_queue: VecDeque<u8>,
tx_queue: VecDeque<u8>,
char_delay: Duration,
next_rx: Instant,
next_tx: Instant,
}
impl Port {
fn new() -> Port {
Port {
mode: [0; 2],
stat: 0,
conf: 0,
rx_data: 0,
tx_data: 0,
mode_ptr: 0,
rx_queue: VecDeque::new(),
tx_queue: VecDeque::new(),
char_delay: Duration::new(0, 1_000_000),
next_rx: Instant::now(),
next_tx: Instant::now(),
}
}
}
impl Default for Port {
fn default() -> Self {
Port::new()
}
}
pub struct Duart {
ports: [Port; 2],
acr: u8,
ipcr: u8,
inprt: u8,
outprt: u8,
istat: u8,
imr: u8,
ivec: u8,
next_vblank: Instant,
}
impl Default for Duart {
fn default() -> Self {
Duart::new()
}
}
impl Duart {
pub fn new() -> Duart {
Duart {
ports: [Port::new(), Port::new()],
acr: 0,
ipcr: 0x40,
inprt: 0xb,
outprt: 0,
istat: 0,
imr: 0,
ivec: 0,
next_vblank: Instant::now() + Duration::new(0, VERTICAL_BLANK_DELAY),
}
}
pub fn get_interrupt(&mut self) -> Option<u8> {
if Instant::now() > self.next_vblank {
self.next_vblank = Instant::now() + Duration::new(0, VERTICAL_BLANK_DELAY);
self.vertical_blank();
}
let val = self.ivec;
if val == 0 {
None
} else {
Some(val)
}
}
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),
};
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;
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);
}
}
}
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);
}
pub fn vertical_blank(&mut self) {
self.ivec |= MOUSE_BLANK_INT;
self.ipcr |= 0x40;
self.istat |= ISTS_IPC;
if self.inprt & 0x04 == 0 {
self.ipcr |= 0x40;
} else {
self.inprt &= !0x04;
}
}
pub fn output_port(&self) -> u8 {
// The output port always returns a complement of the bits
!self.outprt
}
pub fn rs232_tx_poll(&mut self) -> Option<u8> {
self.ports[PORT_0].tx_queue.pop_back()
}
pub fn kb_tx_poll(&mut self) -> Option<u8> {
self.ports[PORT_1].tx_queue.pop_back()
}
pub fn mouse_down(&mut self, button: u8) {
self.ipcr = 0;
self.inprt |= 0xb;
self.istat |= ISTS_IPC;
self.ivec |= MOUSE_BLANK_INT;
match button {
0 => {
self.ipcr |= 0x80;
self.inprt &= !(0x08);
}
1 => {
self.ipcr |= 0x20;
self.inprt &= !(0x02);
}
2 => {
self.ipcr |= 0x10;
self.inprt &= !(0x01)
}
_ => {}
}
}
pub fn mouse_up(&mut self, button: u8) {
self.ipcr = 0;
self.inprt |= 0xb;
self.istat |= ISTS_IPC;
self.ivec |= MOUSE_BLANK_INT;
match button {
0 => {
self.ipcr |= 0x80;
}
1 => {
self.ipcr |= 0x20;
}
2 => {
self.ipcr |= 0x10;
}
_ => {}
}
}
pub fn rx_keyboard(&mut self, c: u8) {
let mut ctx = &mut self.ports[PORT_1];
if ctx.rx_queue.is_empty() {
ctx.next_rx = Instant::now() + ctx.char_delay;
}
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
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;
}
} else if cmd & CMD_ETX != 0 {
ctx.conf |= CNF_ETX;
ctx.stat |= STS_TXR;
ctx.stat |= STS_TXE;
if port == PORT_0 {
self.istat |= ISTS_TAI;
self.ivec |= TX_INT;
}
}
// Enable or disable receiver
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;
} else {
self.ivec &= !KEYBOARD_INT;
self.istat &= !ISTS_RBI;
}
} else if cmd & CMD_ERX != 0 {
ctx.conf |= CNF_ERX;
ctx.stat |= STS_RXR;
}
// Extra commands
match (cmd >> 4) & 7 {
1 => ctx.mode_ptr = 0,
2 => {
ctx.stat |= STS_RXR;
ctx.conf |= CNF_ERX;
}
3 => {
ctx.stat |= STS_TXR;
ctx.stat |= STS_TXE;
ctx.conf &= !CNF_ETX;
}
4 => ctx.stat &= !(STS_FER | STS_PER | STS_OER),
_ => {}
}
}
}
impl Debug for Duart {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "[DUART]")
}
}
impl Device for Duart {
fn address_range(&self) -> &Range<usize> {
&ADDRESS_RANGE
}
fn name(&self) -> &str {
"ACIA"
}
fn is_read_only(&self) -> bool {
false
}
fn read_byte(&mut self, address: usize, _access: AccessCode) -> Result<u8, BusError> {
match (address - START_ADDR) as u8 {
MR12A => {
let mut ctx = &mut self.ports[PORT_0];
let val = ctx.mode[ctx.mode_ptr];
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
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;
self.ivec &= !RX_INT;
Ok(ctx.rx_data)
}
IPCR_ACR => {
let result = self.ipcr;
self.ipcr &= !0x0f;
self.ivec = 0;
self.istat &= !ISTS_IPC;
Ok(result)
}
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),
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)
}
IP_OPCR => Ok(self.inprt),
_ => Ok(0),
}
}
fn read_half(&mut self, address: usize, access: AccessCode) -> Result<u16, BusError> {
let b = self.read_byte(address + 2, access)?;
Ok(u16::from(b))
}
fn read_word(&mut self, address: usize, access: AccessCode) -> Result<u32, BusError> {
let b = self.read_byte(address + 3, access)?;
Ok(u32::from(b))
}
fn write_byte(&mut self, address: usize, val: u8, _access: AccessCode) -> Result<(), BusError> {
match (address - START_ADDR) as u8 {
MR12A => {
let mut ctx = &mut self.ports[PORT_0];
ctx.mode[ctx.mode_ptr] = val;
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
}
CSRA => {
// Set the baud rate.
let baud_bits: usize = ((val >> 4) & 0xf) as usize;
let delay = if self.acr & 0x80 == 0 {
DELAY_RATES_A[baud_bits]
} else {
DELAY_RATES_B[baud_bits]
};
let mut ctx = &mut self.ports[PORT_0];
ctx.char_delay = Duration::new(0, delay);
}
CRA => {
self.handle_command(val, PORT_0);
}
THRA => {
let mut ctx = &mut self.ports[PORT_0];
ctx.tx_data = 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.stat &= !(STS_TXE | STS_TXR);
self.istat &= !ISTS_TAI;
self.ivec &= !TX_INT;
}
IPCR_ACR => {
self.acr = val;
}
ISR_MASK => {
self.imr = val;
}
MR12B => {
let mut ctx = &mut self.ports[PORT_1];
ctx.mode[ctx.mode_ptr] = val;
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
}
CRB => {
self.handle_command(val, PORT_1);
}
THRB => {
// 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 = val;
ctx.next_tx = Instant::now() + ctx.char_delay;
ctx.stat &= !(STS_TXE | STS_TXR);
self.istat &= !ISTS_TBI;
}
}
IP_OPCR => {
// Not implemented
}
OPBITS_SET => {
self.outprt |= val;
}
OPBITS_RESET => {
self.outprt &= !val;
}
_ => {}
};
Ok(())
}
fn write_half(&mut self, address: usize, val: u16, access: AccessCode) -> Result<(), BusError> {
self.write_byte(address + 2, val as u8, access)
}
fn write_word(&mut self, address: usize, val: u32, access: AccessCode) -> Result<(), BusError> {
self.write_byte(address + 3, val as u8, access)
}
fn load(&mut self, _address: usize, _data: &[u8]) -> Result<(), BusError> {
unimplemented!()
}
}