Skip to main content

lab_proxy_core/
interrupt.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Core interrupt observation, sequence tracking, waiter notification, and
6//! acknowledgement.
7//!
8//! Preserves interrupt events, timestamps, sequence ordering, and coalescing
9//! metrics per Spec Section 11.7 and Section 17 (Milestone P5).
10
11use crate::access_policy::{Denial, ResourceId};
12use crate::hardware_backend::BackendError;
13use std::collections::BTreeMap;
14
15/// One interrupt event report returned to waiters and audited (Spec 11.7, 17).
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct InterruptEvent {
18    /// The interrupt resource ID.
19    pub resource: ResourceId,
20    /// Monotonically increasing sequence number for this interrupt resource.
21    pub sequence: u64,
22    /// Total lifetime count of interrupts on this resource.
23    pub count: u64,
24    /// Target monotonic timestamp (in nanoseconds) when the interrupt triggered.
25    pub timestamp_ns: i64,
26    /// Number of coalesced events since the last waiter read/drain.
27    pub coalesced_count: u64,
28}
29
30/// Abstract hardware interrupt backend (for acknowledging virtual or physical IRQs).
31pub trait InterruptBackend: Send {
32    /// Acknowledges the interrupt on every path (Spec 17 step 6).
33    fn acknowledge(&mut self) -> Result<(), BackendError> {
34        Ok(())
35    }
36}
37
38/// In-memory fake interrupt backend for deterministic tests.
39#[derive(Clone, Debug, Default)]
40pub struct FakeInterrupt {
41    pub acks: usize,
42}
43
44impl FakeInterrupt {
45    pub fn new() -> Self {
46        Self::default()
47    }
48}
49
50impl InterruptBackend for FakeInterrupt {
51    fn acknowledge(&mut self) -> Result<(), BackendError> {
52        self.acks += 1;
53        Ok(())
54    }
55}
56
57/// A pending waiter on an interrupt resource.
58struct Waiter {
59    after_sequence: u64,
60    sender: Box<dyn FnOnce(InterruptEvent) + Send>,
61}
62
63/// State tracking for one interrupt resource.
64pub struct InterruptState {
65    pub resource: ResourceId,
66    pub sequence: u64,
67    pub count: u64,
68    pub last_timestamp_ns: i64,
69    pub coalesced_count: u64,
70    waiters: Vec<Waiter>,
71}
72
73impl InterruptState {
74    pub fn new(resource: ResourceId) -> Self {
75        Self {
76            resource,
77            sequence: 0,
78            count: 0,
79            last_timestamp_ns: 0,
80            coalesced_count: 0,
81            waiters: Vec::new(),
82        }
83    }
84
85    /// Current event snapshot.
86    pub fn current_event(&self) -> InterruptEvent {
87        InterruptEvent {
88            resource: self.resource,
89            sequence: self.sequence,
90            count: self.count,
91            timestamp_ns: self.last_timestamp_ns,
92            coalesced_count: self.coalesced_count,
93        }
94    }
95}
96
97/// Central interrupt manager coordinating interrupt observations and waiters.
98pub struct InterruptManager {
99    resources: BTreeMap<ResourceId, InterruptState>,
100}
101
102impl InterruptManager {
103    pub fn new() -> Self {
104        Self { resources: BTreeMap::new() }
105    }
106
107    /// Registers an offered interrupt resource.
108    pub fn register(&mut self, resource: ResourceId) {
109        self.resources.entry(resource).or_insert_with(|| InterruptState::new(resource));
110    }
111
112    /// Checks if a resource is registered.
113    pub fn is_registered(&self, resource: ResourceId) -> bool {
114        self.resources.contains_key(&resource)
115    }
116
117    /// Triggers an interrupt event on `resource`.
118    ///
119    /// Increments sequence and count, updates timestamp, calculates coalesced count,
120    /// satisfies all eligible waiters, and invokes their callbacks (Spec 17).
121    pub fn on_interrupt(
122        &mut self,
123        resource: ResourceId,
124        timestamp_ns: i64,
125        backend: Option<&mut dyn InterruptBackend>,
126    ) -> Result<InterruptEvent, Denial> {
127        let state = self.resources.get_mut(&resource).ok_or(Denial::UnknownResource)?;
128        state.sequence += 1;
129        state.count += 1;
130        state.last_timestamp_ns = timestamp_ns;
131
132        let mut satisfied = Vec::new();
133        let mut remaining = Vec::new();
134        for waiter in state.waiters.drain(..) {
135            if state.sequence > waiter.after_sequence {
136                satisfied.push(waiter);
137            } else {
138                remaining.push(waiter);
139            }
140        }
141        state.waiters = remaining;
142
143        if satisfied.is_empty() {
144            state.coalesced_count += 1;
145        } else {
146            state.coalesced_count = 0;
147        }
148
149        let event = state.current_event();
150
151        for waiter in satisfied {
152            (waiter.sender)(event);
153        }
154
155        if let Some(backend) = backend {
156            let _ = backend.acknowledge();
157        }
158
159        Ok(event)
160    }
161
162    /// Requests an interrupt observation.
163    ///
164    /// If `state.sequence > after_sequence`, returns `Ok(Some(event))` immediately.
165    /// Otherwise, registers the callback to be notified when the next event arrives.
166    pub fn wait_for_interrupt(
167        &mut self,
168        resource: ResourceId,
169        after_sequence: u64,
170        sender: Box<dyn FnOnce(InterruptEvent) + Send>,
171    ) -> Result<Option<InterruptEvent>, Denial> {
172        let state = self.resources.get_mut(&resource).ok_or(Denial::UnknownResource)?;
173        if state.sequence > after_sequence {
174            Ok(Some(state.current_event()))
175        } else {
176            state.waiters.push(Waiter { after_sequence, sender });
177            Ok(None)
178        }
179    }
180
181    /// Clears all pending waiters (for example during session close or PrepareStop).
182    pub fn cancel_waiters(&mut self) {
183        for state in self.resources.values_mut() {
184            state.waiters.clear();
185        }
186    }
187
188    /// Number of active waiters for a given resource.
189    pub fn waiter_count(&self, resource: ResourceId) -> usize {
190        self.resources.get(&resource).map(|s| s.waiters.len()).unwrap_or(0)
191    }
192}
193
194impl std::fmt::Debug for InterruptState {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        f.debug_struct("InterruptState")
197            .field("resource", &self.resource)
198            .field("sequence", &self.sequence)
199            .field("count", &self.count)
200            .field("last_timestamp_ns", &self.last_timestamp_ns)
201            .field("coalesced_count", &self.coalesced_count)
202            .field("waiters", &self.waiters.len())
203            .finish()
204    }
205}
206
207impl std::fmt::Debug for InterruptManager {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        f.debug_struct("InterruptManager").field("resources", &self.resources).finish()
210    }
211}
212
213impl Default for InterruptManager {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use std::sync::atomic::{AtomicBool, Ordering};
223    use std::sync::{Arc, Mutex};
224
225    #[test]
226    fn test_interrupt_registration_and_trigger() {
227        let mut manager = InterruptManager::new();
228        manager.register(1);
229        assert!(manager.is_registered(1));
230        assert!(!manager.is_registered(2));
231
232        let mut fake_backend = FakeInterrupt::new();
233        let event = manager.on_interrupt(1, 100_000, Some(&mut fake_backend)).unwrap();
234        assert_eq!(event.resource, 1);
235        assert_eq!(event.sequence, 1);
236        assert_eq!(event.count, 1);
237        assert_eq!(event.timestamp_ns, 100_000);
238        assert_eq!(event.coalesced_count, 1);
239        assert_eq!(fake_backend.acks, 1);
240    }
241
242    #[test]
243    fn test_interrupt_immediate_wait_when_sequence_greater() {
244        let mut manager = InterruptManager::new();
245        manager.register(1);
246        manager.on_interrupt(1, 100_000, None).unwrap();
247
248        let called = Arc::new(AtomicBool::new(false));
249        let called_clone = called.clone();
250        let immediate = manager
251            .wait_for_interrupt(
252                1,
253                0,
254                Box::new(move |_| {
255                    called_clone.store(true, Ordering::SeqCst);
256                }),
257            )
258            .unwrap();
259
260        assert!(immediate.is_some());
261        let event = immediate.unwrap();
262        assert_eq!(event.sequence, 1);
263        assert!(!called.load(Ordering::SeqCst));
264    }
265
266    #[test]
267    fn test_interrupt_waiter_satisfied_on_trigger() {
268        let mut manager = InterruptManager::new();
269        manager.register(1);
270
271        let received = Arc::new(Mutex::new(None));
272        let received_clone = received.clone();
273        let immediate = manager
274            .wait_for_interrupt(
275                1,
276                0,
277                Box::new(move |event| {
278                    *received_clone.lock().unwrap() = Some(event);
279                }),
280            )
281            .unwrap();
282
283        assert!(immediate.is_none());
284        assert_eq!(manager.waiter_count(1), 1);
285
286        let mut fake_backend = FakeInterrupt::new();
287        manager.on_interrupt(1, 200_000, Some(&mut fake_backend)).unwrap();
288
289        assert_eq!(manager.waiter_count(1), 0);
290        let event = received.lock().unwrap().take().expect("waiter should receive event");
291        assert_eq!(event.sequence, 1);
292        assert_eq!(event.count, 1);
293        assert_eq!(event.timestamp_ns, 200_000);
294        assert_eq!(event.coalesced_count, 0);
295        assert_eq!(fake_backend.acks, 1);
296    }
297
298    #[test]
299    fn test_interrupt_cancel_waiters() {
300        let mut manager = InterruptManager::new();
301        manager.register(1);
302
303        manager.wait_for_interrupt(1, 0, Box::new(|_| {})).unwrap();
304        assert_eq!(manager.waiter_count(1), 1);
305
306        manager.cancel_waiters();
307        assert_eq!(manager.waiter_count(1), 0);
308    }
309}