virt_kvm/
gsi.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements GSI routing management for KVM VMs.
5
6use crate::KvmPartitionInner;
7use pal_event::Event;
8use parking_lot::Mutex;
9use std::os::unix::prelude::*;
10use std::sync::Arc;
11use std::sync::Weak;
12use std::sync::atomic::AtomicBool;
13use std::sync::atomic::Ordering;
14
15const NUM_GSIS: usize = 2048;
16
17/// The GSI routing table configured for a VM.
18#[derive(Debug)]
19pub struct GsiRouting {
20    states: Box<[GsiState; NUM_GSIS]>,
21}
22
23impl GsiRouting {
24    /// Creates a new routing table.
25    pub fn new() -> Self {
26        Self {
27            states: Box::new([GsiState::Unallocated; NUM_GSIS]),
28        }
29    }
30
31    /// Claims a specific GSI.
32    pub fn claim(&mut self, gsi: u32) {
33        let gsi = gsi as usize;
34        assert_eq!(self.states[gsi], GsiState::Unallocated);
35        self.states[gsi] = GsiState::Disabled;
36    }
37
38    /// Allocates an unused GSI.
39    pub fn alloc(&mut self) -> Option<u32> {
40        let gsi = self.states.iter().position(|state| !state.is_allocated())?;
41        self.states[gsi] = GsiState::Disabled;
42        Some(gsi as u32)
43    }
44
45    /// Frees an allocated or claimed GSI.
46    pub fn free(&mut self, gsi: u32) {
47        let gsi = gsi as usize;
48        assert_eq!(self.states[gsi], GsiState::Disabled);
49        self.states[gsi] = GsiState::Unallocated;
50    }
51
52    /// Sets the routing entry for a GSI.
53    pub fn set(&mut self, gsi: u32, entry: Option<kvm::RoutingEntry>) -> bool {
54        let new_state = entry.map_or(GsiState::Disabled, GsiState::Enabled);
55        let state = &mut self.states[gsi as usize];
56        assert!(state.is_allocated());
57        if *state != new_state {
58            *state = new_state;
59            true
60        } else {
61            false
62        }
63    }
64
65    /// Updates the kernel's routing table with the contents of this table.
66    pub fn update_routes(&mut self, kvm: &kvm::Partition) {
67        let routing: Vec<_> = self
68            .states
69            .iter()
70            .enumerate()
71            .filter_map(|(gsi, state)| match state {
72                GsiState::Unallocated | GsiState::Disabled => None,
73                GsiState::Enabled(entry) => Some((gsi as u32, *entry)),
74            })
75            .collect();
76
77        kvm.set_gsi_routes(&routing).expect("should not fail");
78    }
79}
80
81impl KvmPartitionInner {
82    /// Reserves a new route, optionally with an associated irqfd event.
83    pub(crate) fn new_route(self: &Arc<Self>, irqfd_event: Option<Event>) -> Option<GsiRoute> {
84        let gsi = self.gsi_routing.lock().alloc()?;
85        Some(GsiRoute {
86            partition: Arc::downgrade(self),
87            gsi,
88            irqfd_event,
89            enabled: false.into(),
90            enable_mutex: Mutex::new(()),
91        })
92    }
93}
94
95#[derive(Debug, Copy, Clone, PartialEq, Eq)]
96enum GsiState {
97    Unallocated,
98    Disabled,
99    Enabled(kvm::RoutingEntry),
100}
101
102impl GsiState {
103    fn is_allocated(&self) -> bool {
104        !matches!(self, GsiState::Unallocated)
105    }
106}
107
108/// A GSI route.
109#[derive(Debug)]
110pub struct GsiRoute {
111    partition: Weak<KvmPartitionInner>,
112    gsi: u32,
113    irqfd_event: Option<Event>,
114    enabled: AtomicBool,
115    enable_mutex: Mutex<()>, // used to serialize enable/disable calls
116}
117
118impl Drop for GsiRoute {
119    fn drop(&mut self) {
120        self.disable();
121        self.set_entry(None);
122        if let Some(partition) = self.partition.upgrade() {
123            partition.gsi_routing.lock().free(self.gsi);
124        }
125    }
126}
127
128impl GsiRoute {
129    fn set_entry(&self, new_entry: Option<kvm::RoutingEntry>) -> Option<Arc<KvmPartitionInner>> {
130        let partition = self.partition.upgrade();
131        if let Some(partition) = &partition {
132            let mut routing = partition.gsi_routing.lock();
133            if routing.set(self.gsi, new_entry) {
134                routing.update_routes(&partition.kvm);
135            }
136        }
137        partition
138    }
139
140    /// Enables the route and associated irqfd.
141    pub fn enable(&self, entry: kvm::RoutingEntry) {
142        let partition = self.set_entry(Some(entry));
143        let _lock = self.enable_mutex.lock();
144        if !self.enabled.load(Ordering::Relaxed) {
145            if let (Some(partition), Some(event)) = (&partition, &self.irqfd_event) {
146                partition
147                    .kvm
148                    .irqfd(self.gsi, event.as_fd().as_raw_fd(), true)
149                    .expect("should not fail");
150            }
151            self.enabled.store(true, Ordering::Relaxed);
152        }
153    }
154
155    /// Disables the associated irqfd.
156    ///
157    /// This actually leaves the route configured, but it disables the irqfd and
158    /// clears the `enabled` bool so that `signal` won't.
159    pub fn disable(&self) {
160        let _lock = self.enable_mutex.lock();
161        if self.enabled.load(Ordering::Relaxed) {
162            if let Some(irqfd_event) = &self.irqfd_event {
163                if let Some(partition) = self.partition.upgrade() {
164                    partition
165                        .kvm
166                        .irqfd(self.gsi, irqfd_event.as_fd().as_raw_fd(), false)
167                        .expect("should not fail");
168                }
169            }
170            self.enabled.store(false, Ordering::Relaxed);
171        }
172    }
173
174    /// Returns the configured irqfd event, if there is one.
175    pub fn irqfd_event(&self) -> Option<&Event> {
176        self.irqfd_event.as_ref()
177    }
178
179    /// Signals the interrupt if it is enabled.
180    pub fn _signal(&self) {
181        // Use a relaxed atomic read to avoid extra synchronization in this
182        // path. It's up to callers to synchronize this with `enable`/`disable`
183        // if strict ordering is necessary.
184        if self.enabled.load(Ordering::Relaxed) {
185            if let Some(event) = &self.irqfd_event {
186                event.signal();
187            } else if let Some(partition) = self.partition.upgrade() {
188                // TODO: `gsi` must include certain flags on aarch64 to indicate
189                // the type of the interrupt: SPI or PPI handled by the in-kernel vGIC,
190                // or the user mode GIC emulator (where have to specify the target VP, too).
191
192                // xtask-fmt allow-target-arch oneoff-guest-arch-impl
193                assert!(cfg!(target_arch = "x86_64"));
194                partition
195                    .kvm
196                    .irq_line(self.gsi, true)
197                    .expect("interrupt delivery failure");
198            }
199        }
200    }
201}