Skip to main content

squib_virtio/
interrupt.rs

1//! Per-device IRQ delivery.
2//!
3//! Each [`crate::transport::VirtioMmioTransport`] owns one [`IrqLine`] which
4//! wraps the GIC handle and the device's INTID. On a queue-completion event
5//! or a config-change event the device handler calls
6//! [`IrqLine::trigger_queue`] / [`IrqLine::trigger_config`]; this:
7//!
8//! 1. ORs the corresponding interrupt-status bit into the atomic counter that backs the MMIO
9//!    `InterruptStatus` register at offset `0x60`.
10//! 2. Pulses the GIC SPI line via [`squib_gic::Gic::pulse_spi`] (D24: edge-rising on virtio-MMIO,
11//!    level lines are the device's responsibility if it negotiated `VIRTIO_F_NOTIFY_ON_EMPTY`).
12//!
13//! The pulse + status-bit pattern is what every modern virtio guest driver
14//! expects. The driver reads `InterruptStatus`, finds out whether the IRQ is
15//! a config-change or a vring-completion, services the queue, and writes the
16//! same bits to `InterruptACK` (offset `0x64`) to clear them.
17
18use std::sync::{
19    Arc,
20    atomic::{AtomicU32, Ordering},
21};
22
23use squib_arch::IntId;
24use squib_gic::{Gic, GicError};
25
26/// MMIO `InterruptStatus` bit set when one or more vrings have new used
27/// buffers.
28pub const INT_VRING: u32 = 0x01;
29/// MMIO `InterruptStatus` bit set when device config-space has changed.
30pub const INT_CONFIG: u32 = 0x02;
31
32/// IRQ delivery channel for a single virtio-MMIO device.
33///
34/// Cloning is cheap (`Arc` and friends); the underlying GIC handle is shared
35/// across every device on the bus.
36#[derive(Clone)]
37pub struct IrqLine {
38    gic: Arc<dyn Gic + Send + Sync>,
39    intid: IntId,
40    status: Arc<AtomicU32>,
41}
42
43impl std::fmt::Debug for IrqLine {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        // The `gic` field is `Arc<dyn Gic>` which doesn't impl `Debug`; we
46        // intentionally omit it. `finish_non_exhaustive` makes that visible.
47        f.debug_struct("IrqLine")
48            .field("intid", &self.intid.as_raw())
49            .field("status", &self.status.load(Ordering::SeqCst))
50            .finish_non_exhaustive()
51    }
52}
53
54impl IrqLine {
55    /// Build an IRQ line from a GIC handle and the device's INTID.
56    #[must_use]
57    pub fn new(gic: Arc<dyn Gic + Send + Sync>, intid: IntId) -> Self {
58        Self {
59            gic,
60            intid,
61            status: Arc::new(AtomicU32::new(0)),
62        }
63    }
64
65    /// Atomic shadow of the MMIO `InterruptStatus` register. The transport
66    /// `BusDevice` reads this on `0x60` reads and clears the matching bits on
67    /// `0x64` writes.
68    #[must_use]
69    pub fn status(&self) -> Arc<AtomicU32> {
70        Arc::clone(&self.status)
71    }
72
73    /// Mark a queue-completion event and pulse the GIC line.
74    ///
75    /// # Errors
76    /// [`GicError`] if the GIC's pulse fails.
77    pub fn trigger_queue(&self) -> Result<(), GicError> {
78        self.status.fetch_or(INT_VRING, Ordering::SeqCst);
79        self.gic.pulse_spi(self.intid)
80    }
81
82    /// Mark a device-config-change event and pulse the GIC line.
83    ///
84    /// # Errors
85    /// [`GicError`] if the GIC's pulse fails.
86    pub fn trigger_config(&self) -> Result<(), GicError> {
87        self.status.fetch_or(INT_CONFIG, Ordering::SeqCst);
88        self.gic.pulse_spi(self.intid)
89    }
90
91    /// Clear the bits the driver acked on a `0x64` write.
92    pub fn ack(&self, mask: u32) {
93        self.status.fetch_and(!mask, Ordering::SeqCst);
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use parking_lot::Mutex;
100
101    use super::*;
102
103    #[derive(Debug, Default)]
104    struct StubGic {
105        pulses: Mutex<Vec<u32>>,
106    }
107
108    impl Gic for StubGic {
109        fn pulse_spi(&self, intid: IntId) -> Result<(), GicError> {
110            self.pulses.lock().push(intid.as_raw());
111            Ok(())
112        }
113        fn set_spi_level(&self, _intid: IntId, _level: bool) -> Result<(), GicError> {
114            Ok(())
115        }
116        fn save_state(&self) -> Result<Vec<u8>, GicError> {
117            Ok(Vec::new())
118        }
119        fn restore_state(&self, _data: &[u8]) -> Result<(), GicError> {
120            Ok(())
121        }
122    }
123
124    fn line() -> (IrqLine, Arc<StubGic>) {
125        let gic = Arc::new(StubGic::default());
126        let line = IrqLine::new(gic.clone(), IntId::from_spi_cell(0).expect("intid 0"));
127        (line, gic)
128    }
129
130    #[test]
131    fn test_should_set_vring_bit_and_pulse_on_trigger_queue() {
132        let (line, gic) = line();
133        line.trigger_queue().unwrap();
134        assert_eq!(line.status().load(Ordering::SeqCst), INT_VRING);
135        assert_eq!(gic.pulses.lock().len(), 1);
136    }
137
138    #[test]
139    fn test_should_set_config_bit_and_pulse_on_trigger_config() {
140        let (line, _gic) = line();
141        line.trigger_config().unwrap();
142        assert_eq!(line.status().load(Ordering::SeqCst), INT_CONFIG);
143    }
144
145    #[test]
146    fn test_should_clear_only_acked_bits() {
147        let (line, _) = line();
148        line.trigger_queue().unwrap();
149        line.trigger_config().unwrap();
150        assert_eq!(line.status().load(Ordering::SeqCst), INT_VRING | INT_CONFIG);
151        line.ack(INT_VRING);
152        assert_eq!(line.status().load(Ordering::SeqCst), INT_CONFIG);
153    }
154}