Skip to main content

cpu/
alarmunit.rs

1//! The TX-2 can "mask" some alarms, and whether or not this is
2//! happening is controlled by the `AlarmUnit`.
3use std::collections::{BTreeMap, BTreeSet};
4use std::str::FromStr;
5
6use serde::Serialize;
7use tracing::{Level, event};
8
9use super::alarm::{Alarm, AlarmDetails, AlarmKind, AlarmMaskability, Alarmer, BugActivity};
10use super::changelog::ChangeIndex;
11use super::diagnostics::DiagnosticFetcher;
12use crate::diagnostics::CurrentInstructionDiagnostics;
13
14#[cfg(test)]
15use base::Unsigned6Bit;
16
17/// The status of an alarm.
18#[derive(Debug, Serialize)]
19pub struct AlarmStatus {
20    pub name: String,
21    pub maskable: bool,
22    pub masked: bool,
23    pub active: bool,
24    /// An explanation for the current situation; this was not a
25    /// faature of the original TX-2.
26    pub message: String,
27}
28
29/// An alarm is in one of the following states:
30///
31/// - inactive: it's not happening
32/// - firing: it's happening and not masked (execution will stop)
33/// - active but not firing (visible on the console, execution continues)
34#[derive(Debug, Default)]
35pub struct AlarmUnit {
36    panic_on_unmasked_alarm: bool,
37    masked: BTreeSet<AlarmKind>,
38    active: BTreeMap<AlarmKind, Alarm>,
39    changes: ChangeIndex<AlarmKind>,
40}
41
42impl AlarmUnit {
43    pub fn new() -> AlarmUnit {
44        AlarmUnit::default()
45    }
46
47    fn status_for_alarm_kind(&self, kind: &AlarmKind) -> AlarmStatus {
48        let maybe_firing_alarm: Option<&Alarm> = self.active.get(kind);
49        AlarmStatus {
50            name: kind.to_string(),
51            maskable: matches!(kind.maskable(), AlarmMaskability::Maskable),
52            masked: self.masked.contains(kind),
53            active: maybe_firing_alarm.is_some(),
54            message: match maybe_firing_alarm {
55                Some(a) => a.to_string(),
56                None => String::new(),
57            },
58        }
59    }
60
61    pub fn get_alarm_statuses(&self) -> Vec<AlarmStatus> {
62        AlarmKind::all_alarm_kinds()
63            .iter()
64            .map(|kind| self.status_for_alarm_kind(kind))
65            .collect()
66    }
67
68    pub fn drain_alarm_changes(&mut self) -> BTreeMap<AlarmKind, AlarmStatus> {
69        self.changes
70            .drain()
71            .into_iter()
72            .map(|kind| (kind, self.status_for_alarm_kind(&kind)))
73            .collect()
74    }
75
76    pub fn get_status_of_alarm(&self, name: &str) -> Option<AlarmStatus> {
77        AlarmKind::from_str(name)
78            .map(|k| self.status_for_alarm_kind(&k))
79            .ok()
80    }
81
82    pub fn new_with_panic(panic: bool) -> AlarmUnit {
83        AlarmUnit {
84            panic_on_unmasked_alarm: panic,
85            ..AlarmUnit::new()
86        }
87    }
88
89    pub fn mask(
90        &mut self,
91        kind: AlarmKind,
92        diags: &CurrentInstructionDiagnostics,
93    ) -> Result<(), Alarm> {
94        match kind.maskable() {
95            AlarmMaskability::Unmaskable => {
96                let bug = Alarm {
97                    sequence: None,
98                    details: AlarmDetails::BUGAL {
99                        activity: BugActivity::AlarmHandling,
100                        diagnostics: diags.clone(),
101                        message: format!("attempt to mask unmaskable alarm {kind}"),
102                    },
103                };
104                Err(self.always_fire(bug, diags))
105            }
106            AlarmMaskability::Maskable => {
107                self.changes.add(kind);
108                self.masked.insert(kind);
109                Ok(())
110            }
111        }
112    }
113
114    pub fn unmask(&mut self, kind: AlarmKind) {
115        if self.masked.remove(&kind) {
116            self.changes.add(kind);
117        }
118    }
119
120    fn is_masked(&self, alarm_instance: &Alarm) -> bool {
121        let kind = alarm_instance.kind();
122        match kind.maskable() {
123            AlarmMaskability::Unmaskable => false,
124            AlarmMaskability::Maskable => {
125                // TODO: is this correct for writes to un-mapped
126                // memory?
127                self.masked.contains(&kind)
128            }
129        }
130    }
131
132    fn maybe_panic(&self, alarm_instance: &Alarm) {
133        if self.panic_on_unmasked_alarm {
134            // We log an error event here primarily because the
135            // current tracing span includes the program counter
136            // value.
137            event!(Level::ERROR, "panicing with alarm {}", alarm_instance);
138            panic!(
139                "unmasked alarm and panic_on_unmasked_alarm={}: {}",
140                self.panic_on_unmasked_alarm, alarm_instance
141            );
142        }
143    }
144
145    pub fn clear_all_alarms(&mut self) {
146        event!(Level::INFO, "clearing all alarms");
147        self.active.clear();
148    }
149
150    pub fn unmasked_alarm_active(&self) -> bool {
151        self.active.keys().any(|kind| match kind.maskable() {
152            AlarmMaskability::Unmaskable => {
153                assert!(!self.masked.contains(kind));
154                true
155            }
156            AlarmMaskability::Maskable => !self.masked.contains(kind),
157        })
158    }
159
160    fn set_active(&mut self, alarm_instance: Alarm) -> Result<(), Alarm> {
161        let kind: AlarmKind = alarm_instance.kind();
162        self.changes.add(kind);
163        if self.is_masked(&alarm_instance) {
164            self.active.insert(kind, alarm_instance);
165            Ok(())
166        } else {
167            self.active.insert(kind, alarm_instance.clone());
168            self.maybe_panic(&alarm_instance);
169            Err(alarm_instance)
170        }
171    }
172}
173
174impl Alarmer for AlarmUnit {
175    fn fire_if_not_masked<F: DiagnosticFetcher>(
176        &mut self,
177        alarm_instance: Alarm,
178        _get_diags: F,
179    ) -> Result<(), Alarm> {
180        self.changes.add(alarm_instance.kind());
181        self.set_active(alarm_instance)
182    }
183
184    fn always_fire<F: DiagnosticFetcher>(
185        &mut self,
186        alarm_instance: Alarm,
187        get_diagnostics: F,
188    ) -> Alarm {
189        let kind = alarm_instance.kind();
190        self.changes.add(kind);
191        let sequence = alarm_instance.sequence;
192        match self.set_active(alarm_instance) {
193            Err(a) => a,
194            Ok(()) => {
195                let bug = Alarm {
196                    sequence,
197                    details: AlarmDetails::BUGAL {
198                        activity: BugActivity::AlarmHandling,
199                        diagnostics: get_diagnostics.diagnostics(),
200                        message: format!(
201                            "alarm {kind} is masked, but the caller assumed it could not be"
202                        ),
203                    },
204                };
205                match self.set_active(bug) {
206                    Err(a) => a,
207                    Ok(()) => unreachable!("Alarm BUGAL was unexpectedly masked"),
208                }
209            }
210        }
211    }
212}
213
214#[test]
215fn unmaskable_alarms_are_not_maskable() {
216    use base::prelude::{Address, Instruction};
217
218    let mut alarm_unit = AlarmUnit::new_with_panic(false);
219    assert!(!alarm_unit.unmasked_alarm_active());
220    let diagnostics = CurrentInstructionDiagnostics {
221        current_instruction: Instruction::invalid(),
222        instruction_address: Address::ZERO,
223    };
224    // Any attempt to mask an unmaskable alarm should itself result in an error.
225    assert!(
226        alarm_unit
227            .mask(AlarmKind::ROUNDTUITAL, &diagnostics)
228            .is_err()
229    );
230    // Now we raise some non-maskable alarm.
231    assert!(matches!(
232        alarm_unit.fire_if_not_masked(
233            Alarm {
234                sequence: Some(Unsigned6Bit::ZERO),
235                details: AlarmDetails::ROUNDTUITAL {
236                    explanation: "The ROUNDTUITAL alarm is not maskable!".to_string(),
237                    bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/144",
238                },
239            },
240            &diagnostics
241        ),
242        Err(Alarm {
243            sequence: Some(_),
244            details: AlarmDetails::ROUNDTUITAL { .. },
245        })
246    ));
247    // Verify that the alarm manager considers that an unmasked (in
248    // this case because unmaskable) alarm is active.
249    assert!(alarm_unit.unmasked_alarm_active());
250}
251
252#[test]
253fn maskable_alarms_are_not_masked_by_default() {
254    use base::prelude::{Address, Instruction};
255
256    let mut alarm_unit = AlarmUnit::new_with_panic(false);
257    assert!(!alarm_unit.unmasked_alarm_active());
258    let diagnostics = CurrentInstructionDiagnostics {
259        current_instruction: Instruction::invalid(),
260        instruction_address: Address::ZERO,
261    };
262    // Now we raise some maskable, but not masked, alarm.
263    let the_alarm = Alarm {
264        sequence: Some(Unsigned6Bit::ZERO),
265        details: AlarmDetails::PSAL(22, "some PPSAL alarm".to_string()),
266    };
267    // raise the alarm, verify that it really fires.
268    assert!(matches!(
269        alarm_unit.fire_if_not_masked(the_alarm, &diagnostics),
270        Err(Alarm {
271            sequence: _,
272            details: AlarmDetails::PSAL(22, _),
273        },)
274    ));
275    // Verify that the alarm manager considers that an unmasked (in
276    // this case because maskable but not actually masked) alarm is active.
277    assert!(alarm_unit.unmasked_alarm_active());
278}