hcl/
vmbus.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Support for the `/dev/mshv_sint` device.
5
6use crate::ioctl::HypercallError;
7use crate::ioctl::IoctlError;
8use std::fs::File;
9use std::os::unix::prelude::*;
10
11mod ioctl {
12    #![allow(non_camel_case_types)]
13
14    use nix::ioctl_write_ptr;
15    use std::os::unix::prelude::*;
16
17    const MSHV_IOCTL: u8 = 0xb8;
18    const MSHV_SINT_SIGNAL_EVENT: u16 = 0x22;
19    const MSHV_SINT_POST_MESSAGE: u16 = 0x23;
20    const MSHV_SINT_SET_EVENTFD: u16 = 0x24;
21    const MSHV_SINT_PAUSE_MESSAGE_STREAM: u16 = 0x25;
22
23    #[repr(C)]
24    #[derive(Copy, Clone, Debug)]
25    pub struct hcl_post_message {
26        pub message_type: u64,
27        pub connection_id: u32,
28        pub payload_size: u32,
29        pub payload: *const u8,
30    }
31
32    #[repr(C)]
33    #[derive(Copy, Clone, Debug)]
34    pub struct hcl_signal_event {
35        pub connection_id: u32,
36        pub flag: u32,
37    }
38
39    #[repr(C)]
40    #[derive(Copy, Clone, Debug)]
41    pub struct hcl_set_eventfd {
42        pub fd: RawFd,
43        pub flag: u32,
44    }
45
46    #[repr(C)]
47    #[derive(Copy, Clone, Debug, Default)]
48    pub struct hcl_pause_message_stream {
49        pub pause: u8,
50        pub _reserved: [u8; 7],
51    }
52
53    ioctl_write_ptr!(
54        hcl_post_message,
55        MSHV_IOCTL,
56        MSHV_SINT_POST_MESSAGE,
57        hcl_post_message
58    );
59
60    ioctl_write_ptr!(
61        hcl_signal_event,
62        MSHV_IOCTL,
63        MSHV_SINT_SIGNAL_EVENT,
64        hcl_signal_event
65    );
66
67    ioctl_write_ptr!(
68        hcl_set_eventfd,
69        MSHV_IOCTL,
70        MSHV_SINT_SET_EVENTFD,
71        hcl_set_eventfd
72    );
73
74    ioctl_write_ptr!(
75        hcl_pause_message_stream,
76        MSHV_IOCTL,
77        MSHV_SINT_PAUSE_MESSAGE_STREAM,
78        hcl_pause_message_stream
79    );
80}
81
82/// Device used to interact with a synic sint.
83pub struct HclVmbus {
84    file: File,
85}
86
87impl HclVmbus {
88    /// Opens a new instance.
89    pub fn new() -> std::io::Result<Self> {
90        let file = std::fs::OpenOptions::new()
91            .read(true)
92            .write(true)
93            .open("/dev/mshv_sint")?;
94
95        Ok(Self { file })
96    }
97
98    /// Returns the backing file.
99    pub fn into_inner(self) -> File {
100        self.file
101    }
102
103    /// Attempts to post a message to a given connection ID using the HvPostMessage hypercall.
104    pub fn post_message(
105        &self,
106        connection_id: u32,
107        message_type: u64,
108        message: &[u8],
109    ) -> Result<(), HypercallError> {
110        tracing::trace!(connection_id, "posting message");
111
112        let post_message = ioctl::hcl_post_message {
113            message_type,
114            connection_id,
115            payload_size: message.len() as u32,
116            payload: message.as_ptr(),
117        };
118
119        // SAFETY: calling IOCTL as documented, with no special requirements.
120        let result = unsafe { ioctl::hcl_post_message(self.file.as_raw_fd(), &post_message) };
121        HypercallError::check(result)
122    }
123
124    /// Attempts to signal a given event connection ID using the HvSignalEvent hypercall.
125    pub fn signal_event(&self, connection_id: u32, flag: u32) -> Result<(), HypercallError> {
126        tracing::trace!(connection_id, flag, "signaling event");
127
128        let signal_event = ioctl::hcl_signal_event {
129            connection_id,
130            flag,
131        };
132
133        // SAFETY: calling IOCTL as documented, with no special requirements.
134        let result = unsafe { ioctl::hcl_signal_event(self.file.as_raw_fd(), &signal_event) };
135        HypercallError::check(result)
136    }
137
138    /// Sets an eventfd to be signaled when event `flag` is signaled by the
139    /// hypervisor on SINT 7.
140    pub fn set_eventfd(&self, flag: u32, event: Option<BorrowedFd<'_>>) -> Result<(), IoctlError> {
141        tracing::trace!(flag, ?event, "setting event fd");
142
143        let set_eventfd = ioctl::hcl_set_eventfd {
144            flag,
145            fd: event.map_or(-1, |e| e.as_raw_fd()),
146        };
147
148        // SAFETY: Event is either None or a valid and open fd.
149        unsafe { ioctl::hcl_set_eventfd(self.file.as_raw_fd(), &set_eventfd).map_err(IoctlError) }?;
150        Ok(())
151    }
152
153    /// Indicate whether new messages should be accepted from the host.
154    ///
155    /// The primary purpose of this is to prevent new messages from arriving when saving.
156    ///
157    /// When paused, the SINT will be masked, preventing the host from sending new messages. Reading
158    /// from the device will return messages already in the slot, and then return EOF once all
159    /// messages are cleared.
160    ///
161    /// When resumed, the SINT is unmasked and reading from the message slot will block until new
162    /// messages arrive.
163    pub fn pause_message_stream(&self, pause: bool) -> Result<(), IoctlError> {
164        tracing::trace!(?pause, "pausing message stream");
165
166        let pause_message_stream = ioctl::hcl_pause_message_stream {
167            pause: pause.into(),
168            _reserved: [0; 7],
169        };
170
171        // SAFETY: ioctl has no prerequisites.
172        unsafe {
173            ioctl::hcl_pause_message_stream(self.file.as_raw_fd(), &pause_message_stream)
174                .map_err(IoctlError)?;
175        }
176
177        Ok(())
178    }
179}