virt/
synic.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! [`SynicPortAccess`] implementation for backends that intercept
5//! `HvPostMessage` / `HvSignalEvent` hypercalls in user mode.
6//!
7//! Backends where the hypervisor handles synic in-kernel should implement
8//! [`SynicPortAccess`] directly.
9
10use hvdef::HvError;
11use hvdef::HvResult;
12use hvdef::Vtl;
13use inspect::Inspect;
14use parking_lot::Mutex;
15use std::collections::HashMap;
16use std::collections::hash_map;
17use std::fmt::Debug;
18use std::sync::Arc;
19use std::sync::Weak;
20use std::task::Context;
21use std::task::Poll;
22use vm_topology::processor::VpIndex;
23use vmcore::monitor::MonitorId;
24use vmcore::synic::EventPort;
25use vmcore::synic::GuestEventPort;
26use vmcore::synic::GuestMessagePort;
27use vmcore::synic::MessagePort;
28use vmcore::synic::MonitorInfo;
29use vmcore::synic::MonitorPageGpas;
30use vmcore::synic::SynicMonitorAccess;
31use vmcore::synic::SynicPortAccess;
32
33/// Registry of message and event ports, keyed by connection ID.
34///
35/// This is the shared state that backs [`SynicPorts`]. Store an instance
36/// on the partition inner struct so that both `SynicPortAccess` consumers
37/// (VMBus, etc.) and hypercall handlers share the same port map.
38///
39/// Hypercall handlers should call [`SynicPortMap::handle_post_message`] /
40/// [`SynicPortMap::handle_signal_event`] to dispatch guest hypercalls.
41#[derive(Inspect, Debug, Default)]
42pub struct SynicPortMap {
43    #[inspect(with = "|x| inspect::adhoc(|req| inspect::iter_by_key(&*x.lock()).inspect(req))")]
44    ports: Mutex<HashMap<u32, Port>>,
45}
46
47impl SynicPortMap {
48    /// Dispatches a guest `HvPostMessage` hypercall to the registered port.
49    pub fn handle_post_message(
50        &self,
51        vtl: Vtl,
52        connection_id: u32,
53        secure: bool,
54        message: &[u8],
55    ) -> HvResult<()> {
56        let port = self.ports.lock().get(&connection_id).cloned();
57        if let Some(Port {
58            port_type: PortType::Message(port),
59            minimum_vtl,
60        }) = port
61        {
62            if vtl < minimum_vtl {
63                Err(HvError::OperationDenied)
64            } else if port.poll_handle_message(
65                &mut Context::from_waker(std::task::Waker::noop()),
66                message,
67                secure,
68            ) == Poll::Ready(())
69            {
70                Ok(())
71            } else {
72                // TODO: VMBus sometimes (in Azure?) returns HV_STATUS_TIMEOUT
73                //       here instead to force the guest to retry. Should we do
74                //       the same? Perhaps only for Linux VMs?
75                Err(HvError::InsufficientBuffers)
76            }
77        } else {
78            Err(HvError::InvalidConnectionId)
79        }
80    }
81
82    /// Dispatches a guest `HvSignalEvent` hypercall to the registered port.
83    pub fn handle_signal_event(
84        &self,
85        vtl: Vtl,
86        connection_id: u32,
87        flag_number: u16,
88    ) -> HvResult<()> {
89        let port = self.ports.lock().get(&connection_id).cloned();
90        if let Some(Port {
91            port_type: PortType::Event(port),
92            minimum_vtl,
93        }) = port
94        {
95            if vtl < minimum_vtl {
96                Err(HvError::OperationDenied)
97            } else {
98                port.handle_event(flag_number);
99                Ok(())
100            }
101        } else {
102            Err(HvError::InvalidConnectionId)
103        }
104    }
105
106    fn add_message_port(
107        &self,
108        connection_id: u32,
109        minimum_vtl: Vtl,
110        port: Arc<dyn MessagePort>,
111    ) -> Result<(), vmcore::synic::Error> {
112        match self.ports.lock().entry(connection_id) {
113            hash_map::Entry::Occupied(_) => {
114                Err(vmcore::synic::Error::ConnectionIdInUse(connection_id))
115            }
116            hash_map::Entry::Vacant(e) => {
117                e.insert(Port {
118                    port_type: PortType::Message(port),
119                    minimum_vtl,
120                });
121                Ok(())
122            }
123        }
124    }
125
126    fn add_event_port(
127        &self,
128        connection_id: u32,
129        minimum_vtl: Vtl,
130        port: Arc<dyn EventPort>,
131    ) -> Result<(), vmcore::synic::Error> {
132        match self.ports.lock().entry(connection_id) {
133            hash_map::Entry::Occupied(_) => {
134                Err(vmcore::synic::Error::ConnectionIdInUse(connection_id))
135            }
136            hash_map::Entry::Vacant(e) => {
137                e.insert(Port {
138                    port_type: PortType::Event(port),
139                    minimum_vtl,
140                });
141                Ok(())
142            }
143        }
144    }
145}
146
147pub trait Synic: 'static + Send + Sync {
148    /// Returns the port map for this partition, which can be used to register
149    /// ports.
150    fn port_map(&self) -> &SynicPortMap;
151
152    /// Adds a fast path to signal `event` when the guest signals
153    /// `connection_id` from VTL >= `minimum_vtl`.
154    ///
155    /// Returns Ok(None) if this acceleration is not supported.
156    fn new_host_event_port(
157        self: Arc<Self>,
158        connection_id: u32,
159        minimum_vtl: Vtl,
160        event: &pal_event::Event,
161    ) -> Result<Option<Box<dyn Sync + Send>>, vmcore::synic::Error> {
162        let _ = (connection_id, minimum_vtl, event);
163        Ok(None)
164    }
165
166    /// Posts a message to the guest.
167    fn post_message(&self, vtl: Vtl, vp: VpIndex, sint: u8, typ: u32, payload: &[u8]);
168
169    /// Creates a [`GuestEventPort`] for signaling VMBus channels in the guest.
170    fn new_guest_event_port(
171        self: Arc<Self>,
172        vtl: Vtl,
173        vp: u32,
174        sint: u8,
175        flag: u16,
176    ) -> Box<dyn GuestEventPort>;
177
178    /// Returns whether callers should pass an OS event when creating event
179    /// ports, as opposed to passing a function to call.
180    ///
181    /// This is true when the hypervisor can more quickly dispatch an OS event
182    /// and resume the VP than it can take an intercept into user mode and call
183    /// a function.
184    fn prefer_os_events(&self) -> bool;
185
186    /// Returns an object for manipulating the monitor page, or None if monitor pages aren't
187    /// supported.
188    fn monitor_support(&self) -> Option<&dyn SynicMonitor> {
189        None
190    }
191}
192
193/// Provides monitor page functionality for a `Synic` implementation.
194pub trait SynicMonitor: Synic {
195    /// Registers a monitored interrupt. The returned struct will unregister the ID when dropped.
196    ///
197    /// # Panics
198    ///
199    /// Panics if monitor_id is already in use.
200    fn register_monitor(&self, monitor_id: MonitorId, connection_id: u32) -> Box<dyn Sync + Send>;
201
202    /// Sets the GPA of the monitor page currently in use.
203    fn set_monitor_page(&self, vtl: Vtl, gpa: Option<u64>) -> anyhow::Result<()>;
204
205    /// Allocates a monitor page and sets it as the monitor page currently in use. If allocating
206    /// monitor pages is not supported, returns `Ok(None)`.
207    ///
208    /// The page will be deallocated if the monitor page is subsequently changed or cleared using
209    /// [`SynicMonitor::set_monitor_page`].
210    fn allocate_monitor_page(&self, vtl: Vtl) -> anyhow::Result<Option<u64>> {
211        let _ = vtl;
212        Ok(None)
213    }
214}
215
216/// Adapts a [`Synic`] implementation to [`SynicPortAccess`].
217///
218/// Wraps a shared [`SynicPortMap`] (stored on the partition inner struct)
219/// with the [`Synic`] trait methods needed for port registration.
220#[derive(Debug)]
221pub struct SynicPorts<T> {
222    synic: Arc<T>,
223}
224
225impl<T: Synic> SynicPorts<T> {
226    /// Creates a new `SynicPorts` backed by the given partition.
227    pub fn new(synic: Arc<T>) -> Self {
228        Self { synic }
229    }
230}
231
232impl<T: Synic> SynicPortAccess for SynicPorts<T> {
233    fn add_message_port(
234        &self,
235        connection_id: u32,
236        minimum_vtl: Vtl,
237        port: Arc<dyn MessagePort>,
238    ) -> Result<Box<dyn Sync + Send>, vmcore::synic::Error> {
239        self.synic
240            .port_map()
241            .add_message_port(connection_id, minimum_vtl, port)?;
242        Ok(Box::new(PortHandle {
243            synic: Arc::downgrade(&self.synic),
244            connection_id,
245            _inner_handle: None,
246            _monitor: None,
247        }))
248    }
249
250    fn add_event_port(
251        &self,
252        connection_id: u32,
253        minimum_vtl: Vtl,
254        port: Arc<dyn EventPort>,
255        monitor_info: Option<MonitorInfo>,
256    ) -> Result<Box<dyn Sync + Send>, vmcore::synic::Error> {
257        // Create a direct port mapping in the hypervisor if an event was provided.
258        let inner_handle = if let Some(event) = port.os_event() {
259            self.synic
260                .clone()
261                .new_host_event_port(connection_id, minimum_vtl, event)?
262        } else {
263            None
264        };
265
266        self.synic
267            .port_map()
268            .add_event_port(connection_id, minimum_vtl, port)?;
269
270        let monitor = monitor_info.as_ref().and_then(|info| {
271            self.synic
272                .monitor_support()
273                .map(|monitor| monitor.register_monitor(info.monitor_id, connection_id))
274        });
275
276        Ok(Box::new(PortHandle {
277            synic: Arc::downgrade(&self.synic),
278            connection_id,
279            _inner_handle: inner_handle,
280            _monitor: monitor,
281        }))
282    }
283
284    fn new_guest_message_port(
285        &self,
286        vtl: Vtl,
287        vp: u32,
288        sint: u8,
289    ) -> Result<Box<dyn GuestMessagePort>, vmcore::synic::HypervisorError> {
290        Ok(Box::new(DirectGuestMessagePort {
291            partition: Arc::clone(&self.synic),
292            vtl,
293            vp: VpIndex::new(vp),
294            sint,
295        }))
296    }
297
298    fn new_guest_event_port(
299        &self,
300        _port_id: u32,
301        vtl: Vtl,
302        vp: u32,
303        sint: u8,
304        flag: u16,
305        _monitor_info: Option<MonitorInfo>,
306    ) -> Result<Box<dyn GuestEventPort>, vmcore::synic::HypervisorError> {
307        Ok(self.synic.clone().new_guest_event_port(vtl, vp, sint, flag))
308    }
309
310    fn prefer_os_events(&self) -> bool {
311        self.synic.prefer_os_events()
312    }
313
314    fn monitor_support(&self) -> Option<&dyn SynicMonitorAccess> {
315        self.synic.monitor_support().and(Some(self))
316    }
317}
318
319impl<T: Synic> SynicMonitorAccess for SynicPorts<T> {
320    fn set_monitor_page(&self, vtl: Vtl, gpa: Option<MonitorPageGpas>) -> anyhow::Result<()> {
321        self.synic
322            .monitor_support()
323            .unwrap()
324            .set_monitor_page(vtl, gpa.map(|mp| mp.child_to_parent))
325    }
326
327    fn allocate_monitor_page(&self, vtl: Vtl) -> anyhow::Result<Option<MonitorPageGpas>> {
328        self.synic
329            .monitor_support()
330            .unwrap()
331            .allocate_monitor_page(vtl)
332            .map(|gpa| {
333                gpa.map(|child_to_parent| MonitorPageGpas {
334                    parent_to_child: 0,
335                    child_to_parent,
336                })
337            })
338    }
339}
340
341struct PortHandle<T: Synic> {
342    synic: Weak<T>,
343    connection_id: u32,
344    _inner_handle: Option<Box<dyn Sync + Send>>,
345    _monitor: Option<Box<dyn Sync + Send>>,
346}
347
348impl<T: Synic> Drop for PortHandle<T> {
349    fn drop(&mut self) {
350        if let Some(synic) = self.synic.upgrade() {
351            let entry = synic.port_map().ports.lock().remove(&self.connection_id);
352            entry.expect("port was previously added");
353        }
354    }
355}
356
357#[derive(Debug, Clone, Inspect)]
358struct Port {
359    port_type: PortType,
360    #[inspect(with = "|&x| x as u8")]
361    minimum_vtl: Vtl,
362}
363
364#[derive(Clone, Inspect)]
365#[inspect(external_tag)]
366enum PortType {
367    Message(#[inspect(skip)] Arc<dyn MessagePort>),
368    Event(#[inspect(skip)] Arc<dyn EventPort>),
369}
370
371impl Debug for PortType {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        f.pad(match self {
374            Self::Message(_) => "Port::Message",
375            Self::Event(_) => "Port::Event",
376        })
377    }
378}
379
380#[derive(Inspect)]
381#[inspect(bound = "")]
382struct DirectGuestMessagePort<T> {
383    #[inspect(skip)]
384    partition: Arc<T>,
385    #[inspect(with = "|&x| x as u8")]
386    vtl: Vtl,
387    vp: VpIndex,
388    sint: u8,
389}
390
391impl<T: Synic> GuestMessagePort for DirectGuestMessagePort<T> {
392    fn poll_post_message(&mut self, _cx: &mut Context<'_>, typ: u32, payload: &[u8]) -> Poll<()> {
393        self.partition
394            .post_message(self.vtl, self.vp, self.sint, typ, payload);
395        Poll::Ready(())
396    }
397
398    fn set_target_vp(&mut self, vp: u32) -> Result<(), vmcore::synic::HypervisorError> {
399        self.vp = VpIndex::new(vp);
400        Ok(())
401    }
402}