cpu/io/
dev_lincoln_writer.rs

1//! Lincoln Writer, units 65, 71 (input), 66, 72 (output).
2//!
3//! A TX-2 unit is always either for input or output, not both
4//! (consider for example that the TSD instruction specifies no
5//! direction - it is implicit).
6//!
7//! The Lincoln Writer (Lincoln Lab Group Report 51-8) states on page
8//! 10 that the Lincoln Writer prints about about 10 characters per
9//! second (i.e. slightly slower than the IBM Selectric typewriter's
10//! 14.8 characters per scond).
11
12use std::cell::RefCell;
13use std::fmt::Debug;
14use std::rc::Rc;
15use std::time::Duration;
16
17use super::super::context::Context;
18use super::super::diagnostics::CurrentInstructionDiagnostics;
19use super::super::event::{InputEvent, InputEventError, OutputEvent};
20use super::super::io::{FlagChange, InputFlagRaised, TransferFailed, Unit, UnitStatus};
21use super::super::types::*;
22use super::super::{Alarm, AlarmDetails};
23use base::charset::LincolnStateTextInfo;
24#[cfg(test)]
25use base::charset::LwKeyboardCase;
26use base::charset::{LincolnState, lincoln_char_to_described_char, lincoln_writer_state_update};
27use base::prelude::*;
28use tracing::{Level, event};
29
30#[cfg(test)]
31use base::charset::{Colour, DescribedChar, LincolnChar, Script};
32
33const CHAR_TRANSMIT_TIME: Duration = Duration::from_millis(100);
34const LATER: Duration = Duration::from_secs(300);
35
36#[derive(Debug)]
37pub(crate) struct LincolnWriterOutput {
38    unit: Unsigned6Bit,
39    mode: Unsigned12Bit,
40    connected: bool,
41    transmit_will_be_finished_at: Option<Duration>,
42    state: Rc<RefCell<LincolnState>>,
43}
44
45impl LincolnWriterOutput {
46    pub(crate) fn new(unit: Unsigned6Bit, state: Rc<RefCell<LincolnState>>) -> LincolnWriterOutput {
47        LincolnWriterOutput {
48            unit,
49            mode: Unsigned12Bit::ZERO,
50            connected: false,
51            transmit_will_be_finished_at: None,
52            state,
53        }
54    }
55
56    fn lw_number(&self) -> u8 {
57        match u8::from(self.unit) {
58            0o66 => 1,
59            0o72 => 2,
60            n => n,
61        }
62    }
63
64    fn make_alarm(&self, details: AlarmDetails) -> Alarm {
65        Alarm {
66            sequence: Some(self.unit),
67            details,
68        }
69    }
70}
71
72impl Unit for LincolnWriterOutput {
73    fn poll(&mut self, ctx: &Context) -> UnitStatus {
74        let (transmitting, next_poll) = match self.transmit_will_be_finished_at {
75            Some(d) if d > ctx.simulated_time => {
76                event!(
77                    Level::TRACE,
78                    "still transmitting; remaining transmit time is {:?}",
79                    d - ctx.simulated_time
80                );
81                (true, d)
82            }
83            None => {
84                event!(Level::TRACE, "no transmit has yet been started");
85                (false, ctx.simulated_time + LATER)
86            }
87            Some(d) => {
88                event!(
89                    Level::TRACE,
90                    "transmission completed {:?} ago, ready to transmit",
91                    (ctx.simulated_time - d)
92                );
93                (false, ctx.simulated_time + LATER)
94            }
95        };
96        // next_poll is far in the future if we are already ready to
97        // transmit, since we're raising the flag now.  No need to
98        // poll us again to discover we're still ready.
99        let change_flag = if !self.connected || transmitting {
100            None
101        } else {
102            Some(FlagChange::Raise(
103                "(still or newly) connected and not already transmitting",
104            ))
105        };
106        event!(
107            Level::TRACE,
108            "connected: {}, flag: {:?}",
109            self.connected,
110            change_flag
111        );
112        UnitStatus {
113            special: Unsigned12Bit::ZERO,
114            change_flag,
115            buffer_available_to_cpu: !transmitting,
116            inability: false,
117            missed_data: false,
118            mode: self.mode,
119            poll_after: next_poll,
120            is_input_unit: false,
121        }
122    }
123
124    fn connect(&mut self, _ctx: &Context, mode: base::Unsigned12Bit) {
125        event!(Level::INFO, "{} connected", self.name(),);
126        self.connected = true;
127        self.mode = mode;
128    }
129
130    fn read(
131        &mut self,
132        _ctx: &Context,
133        diags: &CurrentInstructionDiagnostics,
134    ) -> Result<MaskedWord, TransferFailed> {
135        unreachable!("attempted to read from an output device (executing {diags})")
136    }
137
138    fn write(
139        &mut self,
140        ctx: &Context,
141        source: base::Unsigned36Bit,
142        diagnostics: &CurrentInstructionDiagnostics,
143    ) -> Result<Option<OutputEvent>, TransferFailed> {
144        match self.state.try_borrow_mut() {
145            Ok(mut state) => {
146                match self.transmit_will_be_finished_at {
147                    Some(t) if t > ctx.simulated_time => {
148                        event!(
149                            Level::DEBUG,
150                            "cannot complete TSD, we are already transmitting"
151                        );
152                        return Err(TransferFailed::BufferNotFree);
153                    }
154                    None => {
155                        event!(Level::TRACE, "this is the unit's first transmit operation");
156                    }
157                    Some(then) => {
158                        let idle_for = ctx.simulated_time - then;
159                        event!(
160                            Level::TRACE,
161                            "ready to transmit more data (and have been for {idle_for:?}"
162                        );
163                    }
164                }
165                let done_at = ctx.simulated_time + CHAR_TRANSMIT_TIME;
166                event!(
167                    Level::DEBUG,
168                    "beginning new transmit operation at {:?}, it will complete at {:?}",
169                    &ctx.simulated_time,
170                    &done_at
171                );
172                self.transmit_will_be_finished_at = Some(done_at);
173                let char_data = Unsigned6Bit::try_from(u64::from(source) & 0o77)
174                    .expect("item should only have six value bits (this is a bug)");
175                match lincoln_char_to_described_char(char_data, &mut state) {
176                    None => Ok(None),
177                    Some(described_char) => Ok(Some(OutputEvent::LincolnWriterPrint {
178                        unit: self.unit,
179                        ch: described_char,
180                    })),
181                }
182            }
183            Err(e) => Err(TransferFailed::Alarm(self.make_alarm(AlarmDetails::BUGAL {
184                activity: crate::alarm::BugActivity::Io,
185                diagnostics: diagnostics.clone(),
186                message: format!(
187                    "attempted to transmit on unit {:o} while the Lincoln Writer state is being mutated by receive path: {e}",
188                    self.unit,
189                )
190            })))
191        }
192    }
193
194    fn name(&self) -> String {
195        format!("Lincoln Writer output {:2o}", self.lw_number())
196    }
197
198    fn transfer_mode(&self) -> crate::TransferMode {
199        TransferMode::Exchange
200    }
201
202    fn disconnect(&mut self, _ctx: &Context) {
203        self.connected = false;
204    }
205
206    fn on_input_event(
207        &mut self,
208        _ctx: &Context,
209        _event: crate::event::InputEvent,
210    ) -> Result<InputFlagRaised, InputEventError> {
211        // Does nothing
212        Ok(InputFlagRaised::No)
213    }
214
215    fn text_info(&self, _ctx: &Context) -> String {
216        // Don't indicate connected/disconnected, because that is
217        // shown separately.
218        let maybe_transmitting = if self.transmit_will_be_finished_at.is_some() {
219            "Transmitting."
220        } else {
221            "Idle."
222        };
223        match self.state.try_borrow() {
224            Ok(state) => {
225                let info: LincolnStateTextInfo = (&*state).into();
226                format!(
227                    "{} {}. {}. {}.",
228                    maybe_transmitting, info.script, info.case, info.colour
229                )
230            }
231            Err(_) => maybe_transmitting.to_string(),
232        }
233    }
234}
235
236#[cfg(test)]
237fn check_output(
238    writer: &mut LincolnWriterOutput,
239    when: Duration,
240    out: Unsigned6Bit,
241    expected_output: &Option<DescribedChar>,
242    expected_state: &LincolnState,
243    actual_state: &Rc<RefCell<LincolnState>>,
244) {
245    use base::prelude::{Address, Instruction};
246
247    let context = Context {
248        simulated_time: when,
249        real_elapsed_time: when,
250    };
251    let diagnostics = CurrentInstructionDiagnostics {
252        current_instruction: Instruction::invalid(),
253        instruction_address: Address::ZERO,
254    };
255
256    match (
257        expected_output,
258        writer.write(&context, out.into(), &diagnostics),
259    ) {
260        (Some(expected_output), Ok(Some(OutputEvent::LincolnWriterPrint { unit: _, ch }))) => {
261            if &ch != expected_output {
262                panic!(
263                    "output of code {out:o} expected to generate character {expected_output:?}, actually generated {ch:?}"
264                );
265            }
266        }
267        (None, Ok(None)) => (),
268        (Some(expected), Ok(None)) => {
269            panic!(
270                "printing code {out:o} produced no output event, but should have produced {expected:?}"
271            );
272        }
273        (None, Ok(Some(actual))) => {
274            panic!(
275                "printing code {out:o} should have produced no output event, but actually produced {actual:?}"
276            );
277        }
278        (_, Err(e)) => {
279            panic!("output transfer failed {e:?}");
280        }
281    }
282    let actual = actual_state.borrow();
283    if &*actual != expected_state {
284        panic!(
285            "output of code {out:o} expected to generate state {expected_state:?}, actual state is {actual:?}"
286        );
287    }
288}
289
290#[test]
291fn lw_output_space() {
292    let unit = u6!(0o66);
293    let state = Rc::new(RefCell::new(LincolnState::default()));
294    let mut lw_out = LincolnWriterOutput::new(unit, state.clone());
295
296    check_output(
297        &mut lw_out,
298        Duration::from_millis(0),
299        u6!(0o70), // SPACE
300        &Some(DescribedChar {
301            base_char: LincolnChar::UnicodeBaseChar(' '),
302            unicode_representation: Some(' '),
303            attributes: LincolnState {
304                script: Script::Normal,
305                case: LwKeyboardCase::Lower,
306                colour: Colour::Black,
307            },
308            advance: true,
309            label_matches_unicode: false,
310        }),
311        &LincolnState::default(),
312        &state,
313    );
314}
315
316#[test]
317fn lw_output_cr_resets_state() {
318    let unit = u6!(0o66);
319    let state = Rc::new(RefCell::new(LincolnState::default()));
320    let mut lw_out = LincolnWriterOutput::new(unit, state.clone());
321
322    check_output(
323        &mut lw_out,
324        Duration::from_millis(100),
325        u6!(0o64), // SUPER
326        &None,     // no output for this char
327        &LincolnState {
328            script: Script::Super,
329            case: LwKeyboardCase::Lower,
330            colour: Colour::Black,
331        },
332        &state,
333    );
334    check_output(
335        &mut lw_out,
336        Duration::from_millis(200),
337        u6!(0o67), // COLOR RED
338        &None,     // no output for this char
339        &LincolnState {
340            script: Script::Super, // unchanged
341            case: LwKeyboardCase::Lower,
342            colour: Colour::Red,
343        },
344        &state,
345    );
346    check_output(
347        &mut lw_out,
348        Duration::from_millis(300),
349        u6!(0o26), // G
350        &Some(DescribedChar {
351            base_char: LincolnChar::UnicodeBaseChar('G'),
352            unicode_representation: Some('ᴳ'),
353            attributes: LincolnState {
354                script: Script::Super,
355                case: LwKeyboardCase::Lower,
356                colour: Colour::Red,
357            },
358            advance: true,
359            label_matches_unicode: true,
360        }),
361        &LincolnState {
362            script: Script::Super,
363            case: LwKeyboardCase::Lower,
364            colour: Colour::Red,
365        },
366        &state,
367    );
368
369    // Now we emit a newline which should reset the script and case
370    // state, but not the colour.
371    check_output(
372        &mut lw_out,
373        Duration::from_millis(400),
374        u6!(0o60), // CAR RETURN
375        &Some(DescribedChar {
376            base_char: LincolnChar::UnicodeBaseChar('\r'),
377            unicode_representation: Some('\r'),
378            attributes: LincolnState {
379                script: Script::Normal,
380                case: LwKeyboardCase::Lower,
381                // colour is not reset, per the Users Guide
382                // (in the description of sequences 65,66).
383                colour: Colour::Red,
384            },
385            advance: true,
386            label_matches_unicode: false,
387        }),
388        &LincolnState {
389            script: Script::Normal,
390            case: LwKeyboardCase::Lower,
391            colour: Colour::Red,
392        },
393        &state,
394    );
395}
396
397#[derive(Debug)]
398pub(crate) struct LincolnWriterInput {
399    unit: Unsigned6Bit,
400    mode: Unsigned12Bit,
401    connected: bool,
402    data: Vec<Unsigned6Bit>,
403    state: Rc<RefCell<LincolnState>>,
404}
405
406impl LincolnWriterInput {
407    pub(crate) fn new(unit: Unsigned6Bit, state: Rc<RefCell<LincolnState>>) -> LincolnWriterInput {
408        LincolnWriterInput {
409            unit,
410            mode: Unsigned12Bit::ZERO,
411            connected: false,
412            data: Vec::new(),
413            state,
414        }
415    }
416
417    fn lw_number(&self) -> u8 {
418        match u8::from(self.unit) {
419            0o65 => 1,
420            0o71 => 2,
421            n => n,
422        }
423    }
424}
425
426impl Unit for LincolnWriterInput {
427    fn poll(&mut self, ctx: &Context) -> UnitStatus {
428        let data_available = !self.data.is_empty();
429        let change_flag: Option<FlagChange> = if self.connected {
430            if data_available {
431                event!(
432                    Level::DEBUG,
433                    "LW input {:o}: connected and data is ready; raising flag",
434                    self.unit
435                );
436                Some(FlagChange::Raise("keyboard data is ready")) // data is ready, raise flag
437            } else {
438                event!(
439                    Level::TRACE,
440                    "LW input {:o}: connected but no data ready, will not raise flag",
441                    self.unit
442                );
443                None // no data ready, so don't raise flag
444            }
445        } else {
446            event!(
447                Level::TRACE,
448                "LW input {:o}: not connected, will not raise flag",
449                self.unit
450            );
451            None // no flag raise since not connected
452        };
453
454        UnitStatus {
455            special: Unsigned12Bit::ZERO,
456            change_flag,
457            buffer_available_to_cpu: self.connected && data_available,
458            inability: false,
459            missed_data: false,
460            mode: self.mode,
461            // Our input processing is driven by calls to
462            // DeviceManager::on_input_event() which will result in
463            // flag raises when necessary.  So there is no need to
464            // call poll() in order to detect input becoming evailable
465            // and so we provide long poll_after values.
466            poll_after: ctx.simulated_time + LATER,
467            is_input_unit: true,
468        }
469    }
470
471    fn text_info(&self, _ctx: &Context) -> String {
472        // Don't show connected/disconnected state here, as that is
473        // shown separately.
474        match self.data.as_slice() {
475            [] => "Idle.".to_string(),
476            [one] => {
477                format!("Input available: {one:o}.")
478            }
479            many => {
480                // This case should not happen, but for now we don't
481                // raise an alarm.
482                let len = many.len();
483                format!("Multiple ({len}) inputs available!")
484            }
485        }
486    }
487
488    fn connect(&mut self, _ctx: &Context, mode: Unsigned12Bit) {
489        self.mode = mode;
490        self.connected = true;
491    }
492
493    fn disconnect(&mut self, _ctx: &Context) {
494        self.connected = false;
495    }
496
497    fn transfer_mode(&self) -> TransferMode {
498        TransferMode::Exchange
499    }
500
501    fn read(
502        &mut self,
503        _ctx: &Context,
504        _diags: &CurrentInstructionDiagnostics,
505    ) -> Result<MaskedWord, TransferFailed> {
506        event!(
507            Level::DEBUG,
508            "read from LW input device having state {self:?}"
509        );
510        if self.data.is_empty() {
511            Err(TransferFailed::BufferNotFree)
512        } else {
513            Ok(MaskedWord {
514                bits: Unsigned36Bit::from(self.data.remove(0)),
515                mask: u36!(0o77),
516            })
517        }
518    }
519
520    fn write(
521        &mut self,
522        _ctx: &Context,
523        _source: Unsigned36Bit,
524        diagnostics: &CurrentInstructionDiagnostics,
525    ) -> Result<Option<OutputEvent>, TransferFailed> {
526        unreachable!("attempted to write to an input device (while executing {diagnostics})")
527    }
528
529    fn name(&self) -> String {
530        format!("Lincoln Writer input {:2o}", self.lw_number())
531    }
532
533    // The send and receive processes involve different units but
534    // share the upper/lower case state within the lincoln writer.
535    //
536    // This implementation is not quite right because we don't emulate
537    // a receive interval between the case-change code and the key
538    // code that follows it.  Consier these codes:
539    //
540    //     Lower case    / Upper case
541    // 20: A             /  ≈
542    // 21: B             /  ⊂
543    // 22: C             /  ∨
544    // 23: D             /  q
545    // 74: LOWER CASE
546    // 75: UPPER CASE
547    //
548    // Suppose a program generates these codes for output: 75  20  74  21.
549    // Suppose these codes arrive on input: 74  22  75  23.
550    //
551    // The streams would likely take effect in this order, where no
552    // output occurs between the successive pairs of input characters.
553    //
554    // Output codes: 75,      20,     74, 21
555    // Input unit:      74,22,   75,23
556    //
557    // This would output "AB" and input "Cq", perhaps not what was expected.
558    //
559    fn on_input_event(
560        &mut self,
561        _ctx: &Context,
562        event: crate::InputEvent,
563    ) -> Result<InputFlagRaised, InputEventError> {
564        event!(
565            Level::DEBUG,
566            "LW input {:o} processing input event: {:?}",
567            self.unit,
568            &event
569        );
570        let result: Result<InputFlagRaised, InputEventError> =
571            if let InputEvent::LwKeyboardInput { data } = event {
572                match (self.data.is_empty(), data.as_slice()) {
573                    (_, []) => Ok(InputFlagRaised::No), // no incoming data
574                    (false, _) => Err(InputEventError::BufferUnavailable),
575                    (true, items) => {
576                        match self.state.try_borrow_mut() {
577                            Ok(mut state) => {
578                                if !self.data.is_empty() {
579                                    return Err(InputEventError::Alarm(Alarm {
580                                        sequence: None,
581                                        details: AlarmDetails::MISAL {
582                                            affected_unit: self.unit,
583                                        },
584                                    }));
585                                }
586                                for item in items {
587                                    // Deal with any state changes.
588                                    // Because all state changes occur in
589                                    // one go, we may get the unexpected
590                                    // behaviour described in the comment
591                                    // above.
592                                    lincoln_writer_state_update(*item, &mut state);
593                                    self.data.push(*item);
594                                }
595                                Ok(InputFlagRaised::Yes)
596                            }
597                            Err(_) => Err(InputEventError::InvalidReentrantCall),
598                        }
599                    }
600                }
601            } else {
602                Err(InputEventError::InputEventNotValidForDevice)
603            };
604        event!(
605            Level::DEBUG,
606            "LW input {:o} completing input event, data is now {:?}, result is {:?}",
607            self.unit,
608            self.data,
609            &result
610        );
611        result
612    }
613}