vmm/vstate/
interrupts.rs

1// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use kvm_ioctls::VmFd;
8use vmm_sys_util::eventfd::EventFd;
9
10use crate::Vm;
11use crate::logger::{IncMetric, METRICS};
12use crate::snapshot::Persist;
13
14#[derive(Debug, thiserror::Error, displaydoc::Display)]
15/// Errors related with Firecracker interrupts
16pub enum InterruptError {
17    /// Error allocating resources: {0}
18    Allocator(#[from] vm_allocator::Error),
19    /// IO error: {0}
20    Io(#[from] std::io::Error),
21    /// FamStruct error: {0}
22    FamStruct(#[from] vmm_sys_util::fam::Error),
23    /// KVM error: {0}
24    Kvm(#[from] kvm_ioctls::Error),
25    /// Invalid vector index: {0}
26    InvalidVectorIndex(usize),
27}
28
29/// Configuration data for an MSI-X interrupt.
30#[derive(Copy, Clone, Debug, Default)]
31pub struct MsixVectorConfig {
32    /// High address to delivery message signaled interrupt.
33    pub high_addr: u32,
34    /// Low address to delivery message signaled interrupt.
35    pub low_addr: u32,
36    /// Data to write to delivery message signaled interrupt.
37    pub data: u32,
38    /// Unique ID of the device to delivery message signaled interrupt.
39    pub devid: u32,
40}
41
42/// Type that describes an allocated interrupt
43#[derive(Debug)]
44pub struct MsixVector {
45    /// GSI used for this vector
46    pub gsi: u32,
47    /// EventFd used for this vector
48    pub event_fd: EventFd,
49    /// Flag determining whether the vector is enabled
50    pub enabled: AtomicBool,
51}
52
53impl MsixVector {
54    /// Create a new [`MsixVector`] of a particular type
55    pub fn new(gsi: u32, enabled: bool) -> Result<MsixVector, InterruptError> {
56        Ok(MsixVector {
57            gsi,
58            event_fd: EventFd::new(libc::EFD_NONBLOCK)?,
59            enabled: AtomicBool::new(enabled),
60        })
61    }
62}
63
64impl MsixVector {
65    /// Enable vector
66    pub fn enable(&self, vmfd: &VmFd) -> Result<(), InterruptError> {
67        if !self.enabled.load(Ordering::Acquire) {
68            vmfd.register_irqfd(&self.event_fd, self.gsi)?;
69            self.enabled.store(true, Ordering::Release);
70        }
71
72        Ok(())
73    }
74
75    /// Disable vector
76    pub fn disable(&self, vmfd: &VmFd) -> Result<(), InterruptError> {
77        if self.enabled.load(Ordering::Acquire) {
78            vmfd.unregister_irqfd(&self.event_fd, self.gsi)?;
79            self.enabled.store(false, Ordering::Release);
80        }
81
82        Ok(())
83    }
84}
85
86#[derive(Debug)]
87/// MSI interrupts created for a VirtIO device
88pub struct MsixVectorGroup {
89    /// Reference to the Vm object, which we'll need for interacting with the underlying KVM Vm
90    /// file descriptor
91    pub vm: Arc<Vm>,
92    /// A list of all the MSI-X vectors
93    pub vectors: Vec<MsixVector>,
94}
95
96impl MsixVectorGroup {
97    /// Returns the number of vectors in this group
98    pub fn num_vectors(&self) -> u16 {
99        // It is safe to unwrap here. We are creating `MsixVectorGroup` objects through the
100        // `Vm::create_msix_group` where the argument for the number of `vectors` is a `u16`.
101        u16::try_from(self.vectors.len()).unwrap()
102    }
103
104    /// Enable the MSI-X vector group
105    pub fn enable(&self) -> Result<(), InterruptError> {
106        for route in &self.vectors {
107            route.enable(&self.vm.common.fd)?;
108        }
109
110        Ok(())
111    }
112
113    /// Disable the MSI-X vector group
114    pub fn disable(&self) -> Result<(), InterruptError> {
115        for route in &self.vectors {
116            route.disable(&self.vm.common.fd)?;
117        }
118
119        Ok(())
120    }
121
122    /// Trigger an interrupt for a vector in the group
123    pub fn trigger(&self, index: usize) -> Result<(), InterruptError> {
124        self.notifier(index)
125            .ok_or(InterruptError::InvalidVectorIndex(index))?
126            .write(1)?;
127        METRICS.interrupts.triggers.inc();
128        Ok(())
129    }
130
131    /// Get a referece to the underlying `EventFd` used to trigger interrupts for a vector in the
132    /// group
133    pub fn notifier(&self, index: usize) -> Option<&EventFd> {
134        self.vectors.get(index).map(|route| &route.event_fd)
135    }
136
137    /// Update the MSI-X configuration for a vector in the group
138    pub fn update(
139        &self,
140        index: usize,
141        msi_config: MsixVectorConfig,
142        masked: bool,
143        set_gsi: bool,
144    ) -> Result<(), InterruptError> {
145        if let Some(vector) = self.vectors.get(index) {
146            METRICS.interrupts.config_updates.inc();
147            // When an interrupt is masked the GSI will not be passed to KVM through
148            // KVM_SET_GSI_ROUTING. So, call [`disable()`] to unregister the interrupt file
149            // descriptor before passing the interrupt routes to KVM
150            if masked {
151                vector.disable(&self.vm.common.fd)?;
152            }
153
154            self.vm.register_msi(vector, masked, msi_config)?;
155            if set_gsi {
156                self.vm
157                    .set_gsi_routes()
158                    .map_err(|err| std::io::Error::other(format!("MSI-X update: {err}")))?
159            }
160
161            // Assign KVM_IRQFD after KVM_SET_GSI_ROUTING to avoid
162            // panic on kernel which does not have commit a80ced6ea514
163            // (KVM: SVM: fix panic on out-of-bounds guest IRQ).
164            if !masked {
165                vector.enable(&self.vm.common.fd)?;
166            }
167
168            return Ok(());
169        }
170
171        Err(InterruptError::InvalidVectorIndex(index))
172    }
173}
174
175impl<'a> Persist<'a> for MsixVectorGroup {
176    type State = Vec<u32>;
177    type ConstructorArgs = Arc<Vm>;
178    type Error = InterruptError;
179
180    fn save(&self) -> Self::State {
181        // We don't save the "enabled" state of the MSI interrupt. PCI devices store the MSI-X
182        // configuration and make sure that the vector is enabled during the restore path if it was
183        // initially enabled
184        self.vectors.iter().map(|route| route.gsi).collect()
185    }
186
187    fn restore(
188        constructor_args: Self::ConstructorArgs,
189        state: &Self::State,
190    ) -> std::result::Result<Self, Self::Error> {
191        let mut vectors = Vec::with_capacity(state.len());
192
193        for gsi in state {
194            vectors.push(MsixVector::new(*gsi, false)?);
195        }
196
197        Ok(MsixVectorGroup {
198            vm: constructor_args,
199            vectors,
200        })
201    }
202}