1use 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#[derive(Debug, Serialize)]
19pub struct AlarmStatus {
20 pub name: String,
21 pub maskable: bool,
22 pub masked: bool,
23 pub active: bool,
24 pub message: String,
27}
28
29#[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 self.masked.contains(&kind)
128 }
129 }
130 }
131
132 fn maybe_panic(&self, alarm_instance: &Alarm) {
133 if self.panic_on_unmasked_alarm {
134 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 assert!(
226 alarm_unit
227 .mask(AlarmKind::ROUNDTUITAL, &diagnostics)
228 .is_err()
229 );
230 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 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 let the_alarm = Alarm {
264 sequence: Some(Unsigned6Bit::ZERO),
265 details: AlarmDetails::PSAL(22, "some PPSAL alarm".to_string()),
266 };
267 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 assert!(alarm_unit.unmasked_alarm_active());
278}