Skip to main content

axvirtio_common/pci/
interrupt.rs

1//! Level-triggered VirtIO PCI interrupt state.
2//!
3//! The coordinator owns only VirtIO ISR and suppression state.  It returns
4//! transition intents to its caller; the endpoint context is responsible for
5//! executing those intents through an admitted `EndpointIrqTransitionPermit`.
6
7use ax_sync::SpinLock;
8
9/// A physical interrupt transition requested by the coordinator.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum InterruptTransition {
12    /// No physical line transition is needed.
13    None,
14    /// Assert the level-triggered line.
15    Assert,
16    /// Deassert the level-triggered line.
17    Deassert,
18}
19
20/// ISR value captured by a read, together with the resulting line intent.
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub struct InterruptReadOutcome {
23    /// The value returned to the guest.
24    pub value: u8,
25    /// The line transition to execute after the read is committed.
26    pub transition: InterruptTransition,
27}
28
29#[derive(Default)]
30struct InterruptState {
31    isr: u8,
32    asserted: bool,
33    disabled: bool,
34    needs_resync: bool,
35    transition_in_flight: Option<bool>,
36}
37
38/// VirtIO PCI ISR and level-INTx state machine.
39pub struct VirtioPciInterruptCoordinator {
40    state: SpinLock<InterruptState>,
41}
42
43impl VirtioPciInterruptCoordinator {
44    /// Creates an idle, interrupt-enabled coordinator.
45    pub const fn new() -> Self {
46        Self {
47            state: SpinLock::new(InterruptState {
48                isr: 0,
49                asserted: false,
50                disabled: false,
51                needs_resync: false,
52                transition_in_flight: None,
53            }),
54        }
55    }
56
57    /// Returns whether any ISR bit is pending.
58    pub fn pending(&self) -> bool {
59        self.state.lock().isr != 0
60    }
61
62    /// Returns whether the logical line is currently asserted.
63    pub fn asserted(&self) -> bool {
64        self.state.lock().asserted
65    }
66
67    /// Returns whether the last physical transition needs to be retried.
68    pub fn needs_resync(&self) -> bool {
69        self.state.lock().needs_resync
70    }
71
72    /// Records a used-ring completion and returns a line transition intent.
73    pub fn record_queue_completion(&self, notify: bool) -> InterruptTransition {
74        self.record(1, notify)
75    }
76
77    /// Records a device-configuration change and returns a line transition intent.
78    pub fn record_config_change(&self) -> InterruptTransition {
79        self.record(2, true)
80    }
81
82    /// Suppresses one stale queue completion while preserving configuration
83    /// change and other independently pending ISR state. `transition`
84    /// identifies the queue-owned line intent, if it had one.
85    pub fn suppress_queue_completion(
86        &self,
87        transition: InterruptTransition,
88    ) -> InterruptTransition {
89        let mut state = self.state.lock();
90        state.isr &= !1;
91        let target = match transition {
92            InterruptTransition::Assert => Some(true),
93            InterruptTransition::Deassert => Some(false),
94            InterruptTransition::None => None,
95        };
96        if target.is_some() && state.transition_in_flight == target {
97            // Suppression cancels the stale queue-owned transition. Retain a
98            // retry request whenever the current logical line is mismatched,
99            // including when a configuration ISR bit remains pending.
100            state.transition_in_flight = None;
101            let desired = !state.disabled && state.isr != 0;
102            state.needs_resync = state.asserted != desired;
103            return InterruptTransition::None;
104        }
105        requested_transition(&mut state)
106    }
107
108    /// Reads and clears the ISR bits, returning the line transition intent.
109    pub fn read_isr(&self) -> InterruptReadOutcome {
110        let mut state = self.state.lock();
111        let value = state.isr;
112        state.isr = 0;
113        let transition = requested_transition(&mut state);
114        InterruptReadOutcome { value, transition }
115    }
116
117    /// Applies the PCI Command.INTx Disable state and returns a line intent.
118    pub fn set_disabled(&self, disabled: bool) -> InterruptTransition {
119        let mut state = self.state.lock();
120        state.disabled = disabled;
121        requested_transition(&mut state)
122    }
123
124    /// Commits the result of a transition executed by the endpoint context.
125    ///
126    /// A failed physical operation leaves the logical state retryable; the
127    /// caller decides whether that failure also fails the guest-facing access.
128    /// A successful transition returns a follow-up intent when another
129    /// concurrent ISR/state change made the current physical target stale.
130    pub fn complete_transition(
131        &self,
132        transition: InterruptTransition,
133        success: bool,
134    ) -> InterruptTransition {
135        if transition == InterruptTransition::None {
136            return InterruptTransition::None;
137        }
138        let mut state = self.state.lock();
139        let target = match transition {
140            InterruptTransition::Assert => true,
141            InterruptTransition::Deassert => false,
142            InterruptTransition::None => return InterruptTransition::None,
143        };
144        if state.transition_in_flight != Some(target) {
145            return InterruptTransition::None;
146        }
147        state.transition_in_flight = None;
148        if success {
149            state.asserted = target;
150            state.needs_resync = false;
151            requested_transition(&mut state)
152        } else {
153            state.needs_resync = true;
154            InterruptTransition::None
155        }
156    }
157
158    /// Suppresses a stale endpoint transition without recording a line error.
159    pub fn suppress_transition(&self, transition: InterruptTransition) {
160        if transition == InterruptTransition::None {
161            return;
162        }
163        let mut state = self.state.lock();
164        let target = matches!(transition, InterruptTransition::Assert);
165        if state.transition_in_flight == Some(target) {
166            state.transition_in_flight = None;
167            let desired = !state.disabled && state.isr != 0;
168            state.needs_resync = state.asserted != desired;
169        }
170    }
171
172    /// Suppresses a transition whose VirtIO queue generation is stale.
173    ///
174    /// A stale generation is not a physical-line failure. Preserve any
175    /// existing retry state and only release the matching in-flight intent.
176    pub(super) fn suppress_stale_transition(&self, transition: InterruptTransition) {
177        if transition == InterruptTransition::None {
178            return;
179        }
180        let mut state = self.state.lock();
181        let target = matches!(transition, InterruptTransition::Assert);
182        if state.transition_in_flight == Some(target) {
183            state.transition_in_flight = None;
184        }
185    }
186
187    /// Cancels a transition that was admitted but never executed by its
188    /// caller. The physical line remains at `asserted`, so keep the logical
189    /// mismatch retryable for the next synchronization point.
190    pub fn cancel_transition(&self, transition: InterruptTransition) {
191        if transition == InterruptTransition::None {
192            return;
193        }
194        let mut state = self.state.lock();
195        let target = matches!(transition, InterruptTransition::Assert);
196        if state.transition_in_flight == Some(target) {
197            state.transition_in_flight = None;
198            let desired = !state.disabled && state.isr != 0;
199            state.needs_resync = state.asserted != desired;
200        }
201    }
202
203    /// Returns the next transition needed to synchronize the physical line.
204    pub fn resynchronize(&self) -> InterruptTransition {
205        let mut state = self.state.lock();
206        requested_transition(&mut state)
207    }
208
209    /// Clears all state and returns the line intent needed to leave idle.
210    pub fn reset(&self) -> InterruptTransition {
211        let mut state = self.state.lock();
212        let transition = if state.asserted || state.needs_resync {
213            InterruptTransition::Deassert
214        } else {
215            InterruptTransition::None
216        };
217        *state = InterruptState {
218            disabled: state.disabled,
219            asserted: state.asserted,
220            ..InterruptState::default()
221        };
222        if transition == InterruptTransition::Deassert {
223            // Keep the owner-side reset deassertion in the same completion
224            // protocol as an ordinary ISR transition. A failed physical
225            // operation must remain retryable through `resynchronize`.
226            state.transition_in_flight = Some(false);
227        }
228        transition
229    }
230
231    fn record(&self, bit: u8, notify: bool) -> InterruptTransition {
232        let mut state = self.state.lock();
233        state.isr |= bit;
234        if notify {
235            requested_transition(&mut state)
236        } else {
237            InterruptTransition::None
238        }
239    }
240}
241
242fn requested_transition(state: &mut InterruptState) -> InterruptTransition {
243    if state.transition_in_flight.is_some() {
244        return InterruptTransition::None;
245    }
246    let desired = !state.disabled && state.isr != 0;
247    if desired == state.asserted {
248        state.needs_resync = false;
249        return InterruptTransition::None;
250    }
251    state.transition_in_flight = Some(desired);
252    state.needs_resync = false;
253    if desired {
254        InterruptTransition::Assert
255    } else {
256        InterruptTransition::Deassert
257    }
258}
259
260impl Default for VirtioPciInterruptCoordinator {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn notification_is_suppressed_until_interrupts_are_enabled() {
272        let coordinator = VirtioPciInterruptCoordinator::new();
273        assert_eq!(coordinator.set_disabled(true), InterruptTransition::None);
274        assert_eq!(
275            coordinator.record_queue_completion(true),
276            InterruptTransition::None
277        );
278        assert!(coordinator.pending());
279        assert_eq!(coordinator.set_disabled(false), InterruptTransition::Assert);
280        let assert_transition = coordinator.resynchronize();
281        assert_eq!(assert_transition, InterruptTransition::None);
282        coordinator.complete_transition(InterruptTransition::Assert, true);
283        assert_eq!(
284            coordinator.read_isr(),
285            InterruptReadOutcome {
286                value: 1,
287                transition: InterruptTransition::Deassert,
288            }
289        );
290        coordinator.complete_transition(InterruptTransition::Deassert, true);
291    }
292
293    #[test]
294    fn failed_line_transition_is_retryable_without_losing_isr_state() {
295        let coordinator = VirtioPciInterruptCoordinator::new();
296        let assert_transition = coordinator.record_config_change();
297        assert_eq!(assert_transition, InterruptTransition::Assert);
298        coordinator.complete_transition(assert_transition, false);
299        assert!(coordinator.needs_resync());
300        let retry_assert = coordinator.resynchronize();
301        assert_eq!(retry_assert, InterruptTransition::Assert);
302        coordinator.complete_transition(retry_assert, true);
303
304        let read = coordinator.read_isr();
305        assert_eq!(read.value, 2);
306        coordinator.complete_transition(read.transition, false);
307        let deassert = coordinator.resynchronize();
308        assert_eq!(deassert, InterruptTransition::Deassert);
309        coordinator.complete_transition(deassert, true);
310        assert!(!coordinator.needs_resync());
311    }
312
313    #[test]
314    fn stale_queue_suppression_preserves_config_change() {
315        let coordinator = VirtioPciInterruptCoordinator::new();
316        assert_eq!(
317            coordinator.record_config_change(),
318            InterruptTransition::Assert
319        );
320        assert_eq!(
321            coordinator.record_queue_completion(true),
322            InterruptTransition::None
323        );
324        assert_eq!(
325            coordinator.suppress_queue_completion(InterruptTransition::None),
326            InterruptTransition::None
327        );
328        assert_eq!(coordinator.read_isr().value, 2);
329    }
330
331    #[test]
332    fn stale_queue_suppression_releases_a_pending_deassertion() {
333        let coordinator = VirtioPciInterruptCoordinator::new();
334        let assert_transition = coordinator.record_config_change();
335        coordinator.complete_transition(assert_transition, true);
336        assert_eq!(
337            coordinator.set_disabled(true),
338            InterruptTransition::Deassert
339        );
340        coordinator.complete_transition(InterruptTransition::Deassert, false);
341
342        assert_eq!(
343            coordinator.record_queue_completion(true),
344            InterruptTransition::Deassert
345        );
346        assert_eq!(
347            coordinator.suppress_queue_completion(InterruptTransition::Deassert),
348            InterruptTransition::None
349        );
350        assert_eq!(coordinator.resynchronize(), InterruptTransition::Deassert);
351    }
352
353    #[test]
354    fn stale_queue_assert_suppression_releases_config_assertion() {
355        let coordinator = VirtioPciInterruptCoordinator::new();
356        assert_eq!(
357            coordinator.record_queue_completion(true),
358            InterruptTransition::Assert
359        );
360        assert_eq!(
361            coordinator.record_config_change(),
362            InterruptTransition::None
363        );
364        assert_eq!(
365            coordinator.suppress_queue_completion(InterruptTransition::Assert),
366            InterruptTransition::None
367        );
368        assert_eq!(coordinator.resynchronize(), InterruptTransition::Assert);
369    }
370
371    #[test]
372    fn completion_reconciles_a_read_that_raced_with_assertion() {
373        let coordinator = VirtioPciInterruptCoordinator::new();
374        let assert_transition = coordinator.record_queue_completion(true);
375        assert_eq!(assert_transition, InterruptTransition::Assert);
376        let read = coordinator.read_isr();
377        assert_eq!(read.value, 1);
378        assert_eq!(read.transition, InterruptTransition::None);
379
380        let deassert_transition = coordinator.complete_transition(assert_transition, true);
381        assert_eq!(deassert_transition, InterruptTransition::Deassert);
382        coordinator.complete_transition(deassert_transition, true);
383        assert!(!coordinator.asserted());
384    }
385
386    #[test]
387    fn reset_deassertion_remains_retryable_after_a_line_failure() {
388        let coordinator = VirtioPciInterruptCoordinator::new();
389        let assert_transition = coordinator.record_config_change();
390        coordinator.complete_transition(assert_transition, true);
391
392        let reset_transition = coordinator.reset();
393        assert_eq!(reset_transition, InterruptTransition::Deassert);
394        coordinator.complete_transition(reset_transition, false);
395        assert!(coordinator.needs_resync());
396
397        let retry = coordinator.resynchronize();
398        assert_eq!(retry, InterruptTransition::Deassert);
399        coordinator.complete_transition(retry, true);
400        assert!(!coordinator.needs_resync());
401    }
402
403    #[test]
404    fn stale_transition_suppression_preserves_existing_retry_state() {
405        let coordinator = VirtioPciInterruptCoordinator::new();
406        let assert_transition = coordinator.record_queue_completion(true);
407        assert_eq!(assert_transition, InterruptTransition::Assert);
408        coordinator.complete_transition(assert_transition, false);
409        assert!(coordinator.needs_resync());
410
411        coordinator.suppress_stale_transition(InterruptTransition::Assert);
412        assert!(coordinator.needs_resync());
413    }
414}