812 lines
25 KiB
Rust
812 lines
25 KiB
Rust
/// 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 crate::utils::FifoQueue;
|
|
use log::{debug, trace};
|
|
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
|
|
|
|
const BAUD_RATES_A: [u32; 13] =
|
|
[50, 110, 135, 200, 300, 600, 1200, 1050, 2400, 4800, 7200, 9600, 38400];
|
|
|
|
const BAUD_RATES_B: [u32; 13] =
|
|
[75, 110, 135, 150, 300, 600, 1200, 2000, 2400, 4800, 1800, 9600, 19200];
|
|
|
|
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 RHRA: 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 RHRB: 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; // TX Enabled
|
|
const CNF_ERX: u8 = 0x02; // RX Enabled
|
|
|
|
//
|
|
// Status Flags
|
|
//
|
|
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
|
|
|
|
//
|
|
// Command Register Commands
|
|
//
|
|
const CMD_ERX: u8 = 0x01;
|
|
const CMD_DRX: u8 = 0x02;
|
|
const CMD_ETX: u8 = 0x04;
|
|
const CMD_DTX: u8 = 0x08;
|
|
|
|
//
|
|
// Control Register Commands
|
|
//
|
|
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 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;
|
|
|
|
struct Port {
|
|
// Mode, Status, and Configuration registers
|
|
mode: [u8; 2],
|
|
mode_ptr: usize,
|
|
stat: u8,
|
|
conf: u8,
|
|
// State used by the RX and TX state machines.
|
|
rx_fifo: FifoQueue,
|
|
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_tx_service: Instant,
|
|
next_rx_service: Instant,
|
|
}
|
|
|
|
impl Port {
|
|
fn new() -> Port {
|
|
Port {
|
|
mode: [0; 2],
|
|
mode_ptr: 0,
|
|
stat: 0,
|
|
conf: 0,
|
|
rx_fifo: FifoQueue::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_tx_service: Instant::now(),
|
|
next_rx_service: Instant::now(),
|
|
}
|
|
}
|
|
|
|
/// 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() {
|
|
return None;
|
|
}
|
|
|
|
if let Ok(val) = self.rx_fifo.pop() {
|
|
// 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 {
|
|
let _ = self.rx_fifo.push(c);
|
|
self.stat |= STS_RXR;
|
|
if self.rx_fifo.is_full() {
|
|
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);
|
|
|
|
Some(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) {
|
|
trace!("rx_char: {:02x}, fifo_len={}", c, self.rx_fifo.len());
|
|
if !self.rx_fifo.is_full() {
|
|
let _ = self.rx_fifo.push(c);
|
|
if self.rx_fifo.is_full() {
|
|
trace!("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) {
|
|
if self.tx_holding_reg.is_none() && self.tx_shift_reg.is_none() {
|
|
// Nothing to do
|
|
return;
|
|
}
|
|
|
|
if Instant::now() >= self.next_tx_service {
|
|
// 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 {
|
|
trace!("RS232 TX: {:02x}", c);
|
|
if self.loopback() {
|
|
debug!("RS232 TX: LOOPBACK: Finish transmit character {:02x}", c);
|
|
self.rx_char(c);
|
|
} else {
|
|
if keyboard && c == 0x02 {
|
|
self.stat |= STS_PER;
|
|
}
|
|
debug!("RS232 TX: Finish transmit character {:02x}", c);
|
|
self.tx_deque.push_front(c);
|
|
}
|
|
|
|
self.tx_shift_reg = None;
|
|
if self.tx_holding_reg.is_none() {
|
|
self.stat |= STS_TXR;
|
|
self.stat |= STS_TXE;
|
|
}
|
|
}
|
|
|
|
// Check for data in the holding register that's ready to go
|
|
// out to the 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;
|
|
// Ready for a new character
|
|
self.next_tx_service = Instant::now() + self.char_delay;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn loopback(&self) -> bool {
|
|
(self.mode[1] & 0xc0) == 0x80
|
|
}
|
|
|
|
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;
|
|
}
|
|
if self.tx_shift_reg.is_none() {
|
|
self.stat |= STS_TXE;
|
|
}
|
|
}
|
|
|
|
fn disable_tx(&mut self) {
|
|
self.conf &= !CNF_ETX;
|
|
self.stat &= !(STS_TXR | STS_TXE);
|
|
}
|
|
|
|
fn enable_rx(&mut self) {
|
|
self.conf |= CNF_ERX;
|
|
self.stat &= !STS_RXR;
|
|
}
|
|
|
|
fn disable_rx(&mut self) {
|
|
self.conf &= !CNF_ERX;
|
|
self.stat &= !STS_RXR;
|
|
}
|
|
}
|
|
|
|
pub struct Duart {
|
|
ports: [Port; 2],
|
|
acr: u8,
|
|
ipcr: u8,
|
|
inprt: u8,
|
|
outprt: 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,
|
|
}
|
|
|
|
impl Default for Duart {
|
|
fn default() -> Self {
|
|
Duart::new()
|
|
}
|
|
}
|
|
|
|
/// Compute the delay rate to wait for the next transmit or receive
|
|
fn delay_rate(csr_bits: u8, acr_bits: u8) -> Duration {
|
|
const NS_PER_SEC: u32 = 1_000_000_000;
|
|
const BITS_PER_CHAR: u32 = 8;
|
|
|
|
let baud_bits: usize = ((csr_bits >> 4) & 0xf) as usize;
|
|
let baud_rate = if acr_bits & 0x80 == 0 {
|
|
BAUD_RATES_A[baud_bits]
|
|
} else {
|
|
BAUD_RATES_B[baud_bits]
|
|
};
|
|
|
|
Duration::new(0, NS_PER_SEC / (baud_rate / BITS_PER_CHAR))
|
|
}
|
|
|
|
impl Duart {
|
|
pub fn new() -> Duart {
|
|
Duart {
|
|
ports: [Port::new(), Port::new()],
|
|
acr: 0,
|
|
ipcr: 0x40,
|
|
inprt: 0xb,
|
|
outprt: 0,
|
|
isr: 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();
|
|
}
|
|
|
|
// 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 {
|
|
None
|
|
} else {
|
|
Some(val)
|
|
}
|
|
}
|
|
|
|
pub fn service(&mut self) {
|
|
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.isr |= 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
|
|
debug!("READ: Output Port: {:02x}", !self.outprt);
|
|
!self.outprt
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
/// 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.isr |= 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.isr |= ISTS_IPC;
|
|
self.ivec |= MOUSE_BLANK_INT;
|
|
match button {
|
|
0 => {
|
|
self.ipcr |= 0x80;
|
|
}
|
|
1 => {
|
|
self.ipcr |= 0x20;
|
|
}
|
|
2 => {
|
|
self.ipcr |= 0x10;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn handle_command(&mut self, cmd: u8, port_no: usize) {
|
|
let port = &mut self.ports[port_no];
|
|
|
|
let (tx_ists, rx_ists, dbk_ists) = match port_no {
|
|
PORT_0 => (ISTS_TAI, ISTS_RAI, ISTS_DBA),
|
|
_ => (ISTS_TBI, ISTS_RBI, ISTS_DBB),
|
|
};
|
|
|
|
// Enable or disable transmitter. Disable always wins if both
|
|
// are set.
|
|
if cmd & CMD_DTX != 0 {
|
|
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 {
|
|
debug!("Command: Enable TX");
|
|
port.enable_tx();
|
|
self.ivec |= TX_INT; // XXX: Rethink interrupt vector setting
|
|
self.isr |= tx_ists;
|
|
}
|
|
|
|
// Enable or disable receiver. Disable always wins, if both are set.
|
|
if cmd & CMD_DRX != 0 {
|
|
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; // XXX: Rethink interrupt vector setting
|
|
self.isr &= !ISTS_RBI;
|
|
}
|
|
} else if cmd & CMD_ERX != 0 {
|
|
debug!("Command: Enable RX");
|
|
port.enable_rx();
|
|
}
|
|
|
|
// Extra commands
|
|
match (cmd >> 4) & 7 {
|
|
CR_RST_MR => {
|
|
debug!("PORT{}: Reset MR Pointer.", port_no);
|
|
port.mode_ptr = 0
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ctx = &mut self.ports[PORT_0];
|
|
let val = ctx.mode[ctx.mode_ptr];
|
|
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
|
trace!("READ : MR12A, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
CSRA => {
|
|
let val = self.ports[PORT_0].stat;
|
|
trace!("READ : CSRA, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
RHRA => {
|
|
let ctx = &mut self.ports[PORT_0];
|
|
self.isr &= !ISTS_RAI;
|
|
self.ivec &= !RX_INT;
|
|
let val = if let Some(c) = ctx.rx_read_char() {
|
|
c
|
|
} else {
|
|
0
|
|
};
|
|
debug!("READ : RHRA, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
IPCR_ACR => {
|
|
let val = self.ipcr;
|
|
self.ipcr &= !0x0f;
|
|
self.ivec = 0;
|
|
self.isr &= !ISTS_IPC;
|
|
trace!("READ : IPCR_ACR, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
ISR_MASK => {
|
|
let val = self.isr;
|
|
trace!("READ : ISR_MASK, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
MR12B => {
|
|
let ctx = &mut self.ports[PORT_1];
|
|
let val = ctx.mode[ctx.mode_ptr];
|
|
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
|
trace!("READ : MR12B, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
CSRB => {
|
|
let val = self.ports[PORT_1].stat;
|
|
trace!("READ : CSRB, val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
RHRB => {
|
|
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;
|
|
trace!("READ : IP_OPCR val={:02x}", val);
|
|
Ok(val)
|
|
}
|
|
_ => {
|
|
trace!("READ : UNHANDLED. ADDRESS={:08x}", address);
|
|
Err(BusError::NoDevice(address))
|
|
}
|
|
}
|
|
}
|
|
|
|
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 => {
|
|
trace!("WRITE: MR12A, val={:02x}", val);
|
|
let ctx = &mut self.ports[PORT_0];
|
|
ctx.mode[ctx.mode_ptr] = val;
|
|
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
|
}
|
|
CSRA => {
|
|
trace!("WRITE: CSRA, val={:02x}", val);
|
|
let ctx = &mut self.ports[PORT_0];
|
|
ctx.char_delay = delay_rate(val, self.acr);
|
|
}
|
|
CRA => {
|
|
trace!("WRITE: CRA, val={:02x}", val);
|
|
self.handle_command(val, PORT_0);
|
|
}
|
|
THRA => {
|
|
let ctx = &mut self.ports[PORT_0];
|
|
debug!("WRITE: THRA, val={:02x}", val);
|
|
ctx.tx_holding_reg = Some(val);
|
|
// TxRDY and TxEMT are both de-asserted on load.
|
|
ctx.stat &= !(STS_TXR | STS_TXE);
|
|
self.isr &= !ISTS_TAI;
|
|
}
|
|
IPCR_ACR => {
|
|
trace!("WRITE: IPCR_ACR, val={:02x}", val);
|
|
self.acr = val;
|
|
// TODO: Update baud rate generator
|
|
}
|
|
ISR_MASK => {
|
|
trace!("WRITE: ISR_MASK, val={:02x}", val);
|
|
self.imr = val;
|
|
}
|
|
MR12B => {
|
|
trace!("WRITE: MR12B, val={:02x}", val);
|
|
let ctx = &mut self.ports[PORT_1];
|
|
ctx.mode[ctx.mode_ptr] = val;
|
|
ctx.mode_ptr = (ctx.mode_ptr + 1) % 2;
|
|
}
|
|
CSRB => {
|
|
trace!("WRITE: CSRB, val={:02x}", val);
|
|
let ctx = &mut self.ports[PORT_1];
|
|
ctx.char_delay = delay_rate(val, self.acr);
|
|
}
|
|
CRB => {
|
|
trace!("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.
|
|
let ctx = &mut self.ports[PORT_1];
|
|
ctx.tx_holding_reg = Some(val);
|
|
// TxRDY and TxEMT are both de-asserted on load.
|
|
ctx.stat &= !(STS_TXR | STS_TXE);
|
|
self.isr &= !ISTS_TBI;
|
|
}
|
|
IP_OPCR => {
|
|
trace!("WRITE: IP_OPCR, val={:02x}", val);
|
|
// Not implemented
|
|
}
|
|
OPBITS_SET => {
|
|
trace!("WRITE: OPBITS_SET, val={:02x}", val);
|
|
self.outprt |= val;
|
|
}
|
|
OPBITS_RESET => {
|
|
trace!("WRITE: OPBITS_RESET, val={:02x}", val);
|
|
self.outprt &= !val;
|
|
}
|
|
_ => {
|
|
trace!("WRITE: UNHANDLED. ADDRESS={:08x}", address);
|
|
}
|
|
};
|
|
|
|
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!()
|
|
}
|
|
}
|