spectrusty_peripherals/
parallel.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! Parallel port device designed for parallel printer but other devices can be also emulated.
9//!
10use std::io;
11
12use spectrusty_core::clock::{TimestampOps, FTs};
13
14#[cfg(feature = "snapshot")]
15use serde::{Serialize, Deserialize};
16
17/// An interface for emulating communication between a Spectrum's or other peripherals' hardware port
18/// and remote parallel devices.
19///
20/// Emulators of peripheral devices should implement this trait.
21///
22/// Methods of this trait are being called by bus devices implementing parallel port communication.
23pub trait ParallelPortDevice {
24    type Timestamp: Sized;
25    /// A device receives a byte written to the parallel data port.
26    fn write_data(&mut self, data: u8, timestamp: Self::Timestamp);
27    /// A device receives a changed `STROBE` bit status.
28    /// `strobe` is `true` if the `STROBE` went high, `false` when it went low.
29    ///
30    /// Should return the `BUSY` status signal:
31    /// * `true` if the `BUSY` signal is high,
32    /// * `false` when it's low.
33    fn write_strobe(&mut self, strobe: bool, timestamp: Self::Timestamp) -> bool;
34    /// This method is being called once every frame, near the end of it, and should return a `BUSY` signal state.
35    /// Returns `true` if the `BUSY` signal is high, and `false` when it's low.
36    fn poll_busy(&mut self) -> bool;
37    /// Called when the current frame ends to allow emulators to wrap stored timestamps.
38    fn next_frame(&mut self, eof_timestamp: Self::Timestamp);
39}
40
41/// Emulates a parallel port device with a custom writer.
42#[derive(Default, Clone, Debug)]
43#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
44#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
45pub struct ParallelPortWriter<T, W> {
46    #[cfg_attr(feature = "snapshot", serde(default))]
47    pub writer: W,
48    busy: bool,
49    data: u8,
50    last_ts: T,
51}
52
53/// A parallel port device that does nothing and provides a constant low `BUSY` signal.
54#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
55#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
56pub struct NullParallelPort<T>(core::marker::PhantomData<T>);
57
58const STROBE_TSTATES_MAX: FTs = 10000;
59
60impl<V, W: io::Write> ParallelPortWriter<V, W> {
61    fn write_byte_to_writer(&mut self) -> bool {
62        let buf = core::slice::from_ref(&self.data);
63        loop {
64            return match self.writer.write(buf) {
65                Ok(0) => false,
66                Ok(..) => true,
67                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
68                Err(e) => panic!("an error occured while writing {}", e)
69            }
70        }
71    }
72}
73
74impl<T: TimestampOps, W: io::Write> ParallelPortDevice for ParallelPortWriter<T, W> {
75    type Timestamp = T;
76
77    fn write_data(&mut self, data: u8, timestamp: Self::Timestamp) {
78        self.data = data;
79        self.last_ts = timestamp;
80    }
81
82    fn write_strobe(&mut self, strobe: bool, timestamp: Self::Timestamp) -> bool {
83        if strobe {}
84        else if timestamp.diff_from(self.last_ts) <  STROBE_TSTATES_MAX {
85            self.busy = !self.write_byte_to_writer();
86            self.last_ts = T::min_value();
87        }
88        else {
89            // println!("centronics timeout: {} >= {}", V::vts_diff(self.last_ts, timestamp), STROBE_TSTATES_MAX);
90        }
91        self.busy
92    }
93
94    fn poll_busy(&mut self) -> bool {
95        if self.busy {
96            self.busy = !self.write_byte_to_writer();
97        }
98        self.busy
99    }
100
101    fn next_frame(&mut self, eof_timestamp: T) {
102        self.last_ts = self.last_ts.saturating_sub(eof_timestamp);
103    }
104}
105
106impl<T> ParallelPortDevice for NullParallelPort<T> {
107    type Timestamp = T;
108
109    #[inline(always)]
110    fn write_data(&mut self, _data: u8, _timestamp: Self::Timestamp) {}
111    #[inline(always)]
112    fn write_strobe(&mut self, _strobe: bool, _timestamp: Self::Timestamp) -> bool {
113        false
114    }
115    #[inline(always)]
116    fn poll_busy(&mut self) -> bool {
117        false
118    }
119    #[inline(always)]
120    fn next_frame(&mut self, _eof_timestamp: Self::Timestamp) {}
121}