Remove dependency on ringbuffer

This change sees the introduction of a tiny FIFO buffer implementation
in util.rs, in lieu of a dependency on the ringbuffer library. The
reason for this change is largely because the ringbuffer library was
unnecessarily complicated and generic, and could only work with sizes
that were a power of 2.
This commit is contained in:
Seth Morabito 2022-11-20 10:26:24 -08:00
parent 73ea34e619
commit d19f3d4fdb
5 changed files with 147 additions and 33 deletions

View File

@ -15,7 +15,7 @@ log = { version = "0.4.8", features = ["std"] }
env_logger = "0.9.0"
lazy_static = "~1.4"
libc = "~0.2"
ringbuffer = "0.8.5"
thiserror = "1.0"
[profile.release]
debug = true

View File

@ -14,7 +14,7 @@ lazy_static! {
pub static ref DMD: Mutex<Dmd> = Mutex::new(Dmd::new());
}
// Return vlaues for the C library
// Return values for the C library
const SUCCESS: c_int = 0;
const ERROR: c_int = 1;
const BUSY: c_int = 2;

View File

@ -38,10 +38,8 @@
use crate::bus::{AccessCode, Device};
use crate::err::BusError;
use crate::utils::FifoQueue;
use log::{debug, trace};
use ringbuffer::{
ConstGenericRingBuffer, RingBuffer, RingBufferExt, RingBufferRead, RingBufferWrite,
};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::fmt::Error;
@ -141,14 +139,6 @@ 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],
@ -156,7 +146,7 @@ struct Port {
stat: u8,
conf: u8,
// State used by the RX and TX state machines.
rx_fifo: ConstGenericRingBuffer<u8, RX_FIFO_INIT_LEN>,
rx_fifo: FifoQueue,
rx_shift_reg: Option<u8>,
tx_holding_reg: Option<u8>,
tx_shift_reg: Option<u8>,
@ -177,7 +167,7 @@ impl Port {
mode_ptr: 0,
stat: 0,
conf: 0,
rx_fifo: ConstGenericRingBuffer::<u8, RX_FIFO_INIT_LEN>::new(),
rx_fifo: FifoQueue::new(),
rx_shift_reg: None,
tx_holding_reg: None,
tx_shift_reg: None,
@ -195,18 +185,20 @@ impl Port {
/// 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();
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 {
self.rx_fifo.push(c);
let _ = self.rx_fifo.push(c);
self.stat |= STS_RXR;
if self.rx_fifo.len() >= RX_FIFO_LEN {
if self.rx_fifo.is_full() {
self.stat |= STS_FFL;
}
}
@ -219,7 +211,7 @@ impl Port {
// Reset Parity Error and Received Break on read
self.stat &= !(STS_PER | STS_RXB);
val
Some(val)
} else {
None
}
@ -233,9 +225,9 @@ impl Port {
/// 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.len() < RX_FIFO_LEN {
self.rx_fifo.push(c);
if self.rx_fifo.len() >= 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;
}

View File

@ -1,13 +1,19 @@
pub mod bus;
pub mod cpu;
pub mod dmd;
pub mod duart;
pub mod err;
pub mod instr;
pub mod mem;
pub mod mouse;
pub mod rom_hi;
pub mod rom_lo;
#[allow(unused)]
mod bus;
#[allow(unused)]
mod cpu;
#[allow(unused)]
mod dmd;
mod duart;
#[allow(unused)]
mod err;
#[allow(unused)]
mod instr;
mod mem;
mod mouse;
mod rom_hi;
mod rom_lo;
mod utils;
#[macro_use]
extern crate lazy_static;

116
src/utils.rs Normal file
View File

@ -0,0 +1,116 @@
use thiserror::Error;
const FIFO_LEN: usize = 3;
/// A simple circular buffer with three slots, used as a
/// DUART character FIFO
pub struct FifoQueue {
buf: [u8; 3],
read_ptr: usize,
write_ptr: usize,
len: usize,
}
#[derive(Error, Debug, Eq, PartialEq)]
pub enum FifoError {
#[error("fifo full")]
Overflow,
#[error("fifo under-run")]
Underrun,
}
impl FifoQueue {
pub fn new() -> Self {
FifoQueue {
buf: [0; FIFO_LEN],
read_ptr: 0,
write_ptr: 0,
len: 0,
}
}
pub fn push(&mut self, c: u8) -> Result<(), FifoError> {
if self.len == FIFO_LEN {
Err(FifoError::Overflow)
} else {
self.buf[self.write_ptr] = c;
self.write_ptr = (self.write_ptr + 1) % FIFO_LEN;
self.len += 1;
Ok(())
}
}
pub fn pop(&mut self) -> Result<u8, FifoError> {
if self.len == 0 {
Err(FifoError::Underrun)
} else {
let c = self.buf[self.read_ptr];
self.read_ptr = (self.read_ptr + 1) % FIFO_LEN;
self.len -= 1;
Ok(c)
}
}
pub fn clear(&mut self) {
self.read_ptr = 0;
self.write_ptr = 0;
self.len = 0;
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn is_full(&self) -> bool {
self.len == FIFO_LEN
}
pub fn len(&self) -> usize {
self.len
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn pops_in_order() {
let mut f: FifoQueue = FifoQueue::new();
assert_eq!(Ok(()), f.push(1));
assert_eq!(Ok(()), f.push(2));
assert_eq!(Ok(()), f.push(3));
assert_eq!(Ok(1), f.pop());
assert_eq!(Ok(2), f.pop());
assert_eq!(Ok(3), f.pop());
}
#[test]
fn popping_when_empty_returns_underrun_error() {
let mut f: FifoQueue = FifoQueue::new();
assert_eq!(0, f.len());
assert_eq!(Err(FifoError::Underrun), f.pop());
assert_eq!(Ok(()), f.push(42));
assert_eq!(1, f.len());
assert_eq!(Ok(42), f.pop());
assert_eq!(Err(FifoError::Underrun), f.pop());
}
#[test]
fn pushing_when_full_returns_overflow_error() {
let mut f: FifoQueue = FifoQueue::new();
assert_eq!(0, f.len());
assert_eq!(Ok(()), f.push(0xff));
assert_eq!(1, f.len());
assert_eq!(Ok(()), f.push(0xfe));
assert_eq!(2, f.len());
assert_eq!(Ok(()), f.push(0xfd));
assert_eq!(3, f.len());
assert_eq!(Err(FifoError::Overflow), f.push(0xfc));
assert_eq!(3, f.len());
}
}