1#![expect(unsafe_code)]
12
13use crate::MshvPartitionInner;
14use anyhow::Context;
15use headervec::HeaderVec;
16use mshv_bindings::MSHV_IRQFD_BIT_DEASSIGN;
17use mshv_bindings::mshv_user_irq_entry;
18use mshv_bindings::mshv_user_irqfd;
19use pal_event::Event;
20use parking_lot::Mutex;
21use std::os::fd::AsFd;
22use std::os::fd::AsRawFd;
23use std::sync::Arc;
24use virt::irqfd::IrqFd;
25use virt::irqfd::IrqFdRoute;
26
27pub(crate) const NUM_GSIS: usize = 2048;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub(crate) enum GsiState {
32 Unallocated,
34 Disabled,
36 Enabled(MsiRoute),
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub(crate) struct MsiRoute {
43 address_lo: u32,
44 address_hi: u32,
45 data: u32,
46}
47
48impl MshvPartitionInner {
49 fn alloc_gsi(&self) -> Option<u32> {
51 let mut states = self.gsi_states.lock();
52 let gsi = states
53 .iter()
54 .position(|state| matches!(state, GsiState::Unallocated))?;
55 states[gsi] = GsiState::Disabled;
56 Some(gsi as u32)
57 }
58
59 fn free_gsi(&self, gsi: u32) {
61 self.gsi_states.lock()[gsi as usize] = GsiState::Unallocated;
62 }
63
64 fn set_gsi_route(&self, gsi: u32, route: Option<MsiRoute>) -> anyhow::Result<()> {
67 let mut states = self.gsi_states.lock();
68 let state = &mut states[gsi as usize];
69 anyhow::ensure!(
70 !matches!(state, GsiState::Unallocated),
71 "cannot set route for unallocated GSI {gsi}"
72 );
73 let new_state = match route {
74 Some(r) => GsiState::Enabled(r),
75 None => GsiState::Disabled,
76 };
77 if *state == new_state {
78 return Ok(());
79 }
80 let old_state = *state;
81 *state = new_state;
82
83 if let Err(e) = Self::push_routing_table(&self.vmfd, &states) {
84 states[gsi as usize] = old_state;
86 return Err(e);
87 }
88 Ok(())
89 }
90
91 fn push_routing_table(
93 vmfd: &mshv_ioctls::VmFd,
94 states: &[GsiState; NUM_GSIS],
95 ) -> anyhow::Result<()> {
96 let entries: Vec<mshv_user_irq_entry> = states
97 .iter()
98 .enumerate()
99 .filter_map(|(gsi, state)| match state {
100 GsiState::Enabled(route) => Some(mshv_user_irq_entry {
101 gsi: gsi as u32,
102 address_lo: route.address_lo,
103 address_hi: route.address_hi,
104 data: route.data,
105 }),
106 _ => None,
107 })
108 .collect();
109
110 set_msi_routing_ioctl(vmfd, &entries).context("failed to set MSI routing")
111 }
112
113 unsafe fn register_irqfd(&self, event: &Event, gsi: u32) -> anyhow::Result<()> {
120 let irqfd_arg = mshv_user_irqfd {
121 fd: event.as_fd().as_raw_fd(),
122 resamplefd: 0,
123 gsi,
124 flags: 0,
125 };
126 let ret = unsafe {
131 libc::ioctl(
132 self.vmfd.as_raw_fd(),
133 mshv_ioctls::MSHV_IRQFD() as _,
134 std::ptr::from_ref(&irqfd_arg),
135 )
136 };
137 if ret < 0 {
138 return Err(std::io::Error::last_os_error()).context("MSHV_IRQFD register failed");
139 }
140 Ok(())
141 }
142
143 unsafe fn unregister_irqfd(&self, event: &Event, gsi: u32) -> anyhow::Result<()> {
150 let irqfd_arg = mshv_user_irqfd {
151 fd: event.as_fd().as_raw_fd(),
152 resamplefd: 0,
153 gsi,
154 flags: 1 << MSHV_IRQFD_BIT_DEASSIGN,
155 };
156 let ret = unsafe {
160 libc::ioctl(
161 self.vmfd.as_raw_fd(),
162 mshv_ioctls::MSHV_IRQFD() as _,
163 std::ptr::from_ref(&irqfd_arg),
164 )
165 };
166 if ret < 0 {
167 return Err(std::io::Error::last_os_error()).context("MSHV_IRQFD unregister failed");
168 }
169 Ok(())
170 }
171}
172
173pub(crate) struct MshvIrqFd {
179 partition: Arc<MshvPartitionInner>,
180}
181
182impl MshvIrqFd {
183 pub fn new(partition: Arc<MshvPartitionInner>) -> Self {
184 Self { partition }
185 }
186}
187
188impl IrqFd for MshvIrqFd {
189 fn new_irqfd_route(&self) -> anyhow::Result<Box<dyn IrqFdRoute>> {
190 let gsi = self
191 .partition
192 .alloc_gsi()
193 .context("no free GSIs available for irqfd")?;
194
195 let event = Event::new();
202 Ok(Box::new(MshvIrqFdRoute {
203 partition: self.partition.clone(),
204 gsi,
205 event,
206 armed: Mutex::new(false),
207 }))
208 }
209}
210
211struct MshvIrqFdRoute {
215 partition: Arc<MshvPartitionInner>,
216 gsi: u32,
217 event: Event,
218 armed: Mutex<bool>,
221}
222
223impl MshvIrqFdRoute {
224 fn disarm(&self) {
225 let mut armed = self.armed.lock();
226 if *armed {
227 if let Err(e) = unsafe { self.partition.unregister_irqfd(&self.event, self.gsi) } {
229 tracelimit::warn_ratelimited!(error = ?e, gsi = self.gsi, "failed to unregister irqfd");
230 return;
231 }
232 *armed = false;
233 }
234 }
235}
236
237impl IrqFdRoute for MshvIrqFdRoute {
238 fn event(&self) -> &Event {
239 &self.event
240 }
241
242 fn enable(&self, address: u64, data: u32, _devid: Option<u32>) {
243 let mut armed = self.armed.lock();
244 let route = MsiRoute {
245 address_lo: address as u32,
246 address_hi: (address >> 32) as u32,
247 data,
248 };
249 if let Err(e) = self.partition.set_gsi_route(self.gsi, Some(route)) {
250 tracelimit::warn_ratelimited!(error = ?e, gsi = self.gsi, "failed to set GSI route");
251 return;
252 }
253 if !*armed {
254 if let Err(e) = unsafe { self.partition.register_irqfd(&self.event, self.gsi) } {
257 tracelimit::warn_ratelimited!(error = ?e, gsi = self.gsi, "failed to register irqfd");
258 return;
259 }
260 *armed = true;
261 }
262 }
263
264 fn disable(&self) {
265 self.disarm();
270 }
271}
272
273impl Drop for MshvIrqFdRoute {
274 fn drop(&mut self) {
275 self.disarm();
276
277 self.partition
278 .set_gsi_route(self.gsi, None)
279 .expect("failed to clear GSI route on drop");
280
281 self.partition.free_gsi(self.gsi);
282 }
283}
284
285#[repr(C)]
289#[derive(Debug, Copy, Clone)]
290struct MsiRoutingHeader {
291 nr: u32,
292 rsvd: u32,
293}
294
295fn set_msi_routing_ioctl(
300 vmfd: &mshv_ioctls::VmFd,
301 entries: &[mshv_user_irq_entry],
302) -> anyhow::Result<()> {
303 let mut buf = HeaderVec::<MsiRoutingHeader, mshv_user_irq_entry, 0>::new(MsiRoutingHeader {
304 nr: entries.len() as u32,
305 rsvd: 0,
306 });
307 buf.extend_tail_from_slice(entries);
308
309 let ret = unsafe {
314 libc::ioctl(
315 vmfd.as_raw_fd(),
316 mshv_ioctls::MSHV_SET_MSI_ROUTING() as _,
317 buf.as_ptr(),
318 )
319 };
320 if ret < 0 {
321 return Err(std::io::Error::last_os_error()).context("MSHV_SET_MSI_ROUTING ioctl failed");
322 }
323
324 Ok(())
325}