1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Implements virtio devices, queues, and transport mechanisms.

mod async_device;
mod async_utils;
#[cfg(feature = "balloon")]
mod balloon;
mod descriptor_chain;
mod descriptor_utils;
pub mod device_constants;
pub mod input;
mod interrupt;
mod iommu;
#[cfg(feature = "net")]
pub mod net;
#[cfg(feature = "pvclock")]
pub mod pvclock;
mod queue;
mod rng;
#[cfg(feature = "vtpm")]
mod tpm;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
mod video;
mod virtio_device;
mod virtio_mmio_device;
mod virtio_pci_common_config;
mod virtio_pci_device;

pub mod block;
pub mod console;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod resource_bridge;
pub mod scsi;
#[cfg(feature = "audio")]
pub mod snd;
pub mod vhost;
pub mod vhost_user_frontend;
pub mod vsock;

#[cfg(feature = "balloon")]
pub use self::balloon::Balloon;
#[cfg(feature = "balloon")]
pub use self::balloon::BalloonFeatures;
#[cfg(feature = "balloon")]
pub use self::balloon::BalloonMode;
pub use self::block::BlockAsync;
pub use self::console::Console;
pub use self::descriptor_chain::DescriptorChain;
pub use self::descriptor_chain::DescriptorChainIter;
pub use self::descriptor_utils::create_descriptor_chain;
pub use self::descriptor_utils::DescriptorType;
pub use self::descriptor_utils::Reader;
pub use self::descriptor_utils::Writer;
#[cfg(feature = "gpu")]
pub use self::gpu::DisplayBackend;
#[cfg(feature = "gpu")]
pub use self::gpu::Gpu;
#[cfg(feature = "gpu")]
pub use self::gpu::GpuDisplayMode;
#[cfg(feature = "gpu")]
pub use self::gpu::GpuDisplayParameters;
#[cfg(feature = "gpu")]
pub use self::gpu::GpuMode;
#[cfg(feature = "gpu")]
pub use self::gpu::GpuMouseMode;
#[cfg(feature = "gpu")]
pub use self::gpu::GpuParameters;
#[cfg(feature = "gpu")]
pub use self::gpu::GpuWsi;
pub use self::interrupt::Interrupt;
pub use self::interrupt::InterruptSnapshot;
pub use self::iommu::ipc_memory_mapper;
pub use self::iommu::memory_mapper;
pub use self::iommu::Iommu;
pub use self::iommu::IommuError;
#[cfg(feature = "net")]
pub use self::net::Net;
#[cfg(feature = "net")]
pub use self::net::NetError;
#[cfg(feature = "net")]
pub use self::net::NetParameters;
#[cfg(feature = "net")]
pub use self::net::NetParametersMode;
pub use self::queue::split_descriptor_chain::Desc;
pub use self::queue::split_descriptor_chain::SplitDescriptorChain;
pub use self::queue::PeekedDescriptorChain;
pub use self::queue::Queue;
pub use self::queue::QueueConfig;
pub use self::rng::Rng;
pub use self::scsi::Controller as ScsiController;
pub use self::scsi::DiskConfig as ScsiDiskConfig;
#[cfg(feature = "vtpm")]
pub use self::tpm::Tpm;
#[cfg(feature = "vtpm")]
pub use self::tpm::TpmBackend;
pub use self::vhost_user_frontend::VhostUserFrontend;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
pub use self::video::VideoDevice;
pub use self::virtio_device::SharedMemoryMapper;
pub use self::virtio_device::SharedMemoryRegion;
pub use self::virtio_device::VirtioDevice;
pub use self::virtio_device::VirtioTransportType;
pub use self::virtio_mmio_device::VirtioMmioDevice;
pub use self::virtio_pci_device::PciCapabilityType;
pub use self::virtio_pci_device::VirtioPciCap;
pub use self::virtio_pci_device::VirtioPciDevice;
pub use self::virtio_pci_device::VirtioPciShmCap;
#[cfg(feature = "pvclock")]
pub use self::DeviceType::Pvclock;

cfg_if::cfg_if! {
    if #[cfg(any(target_os = "android", target_os = "linux"))] {
        mod p9;
        mod pmem;

        pub mod wl;
        pub mod fs;

        pub use self::iommu::sys::linux::vfio_wrapper;
        #[cfg(feature = "net")]
        pub use self::net::VhostNetParameters;
        #[cfg(feature = "net")]
        pub use self::net::VHOST_NET_DEFAULT_PATH;
        pub use self::p9::P9;
        pub use self::pmem::Pmem;
        #[cfg(feature = "audio")]
        pub use self::snd::new_sound;
        pub use self::wl::Wl;
    } else if #[cfg(windows)] {
        pub use self::vsock::Vsock;
    } else {
        compile_error!("Unsupported platform");
    }
}

use std::cmp;
use std::convert::TryFrom;

use futures::channel::oneshot;
use hypervisor::ProtectionType;
use serde::Deserialize;
use serde::Serialize;
use virtio_sys::virtio_config::VIRTIO_F_ACCESS_PLATFORM;
use virtio_sys::virtio_config::VIRTIO_F_SUSPEND;
use virtio_sys::virtio_config::VIRTIO_F_VERSION_1;
use virtio_sys::virtio_ids;
use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;

const DEVICE_RESET: u32 = 0x0;

const INTERRUPT_STATUS_USED_RING: u32 = 0x1;
const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2;

const VIRTIO_MSI_NO_VECTOR: u16 = 0xffff;

#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[repr(u32)]
pub enum DeviceType {
    Net = virtio_ids::VIRTIO_ID_NET,
    Block = virtio_ids::VIRTIO_ID_BLOCK,
    Console = virtio_ids::VIRTIO_ID_CONSOLE,
    Rng = virtio_ids::VIRTIO_ID_RNG,
    Balloon = virtio_ids::VIRTIO_ID_BALLOON,
    Scsi = virtio_ids::VIRTIO_ID_SCSI,
    #[serde(rename = "9p")]
    P9 = virtio_ids::VIRTIO_ID_9P,
    Gpu = virtio_ids::VIRTIO_ID_GPU,
    Input = virtio_ids::VIRTIO_ID_INPUT,
    Vsock = virtio_ids::VIRTIO_ID_VSOCK,
    Iommu = virtio_ids::VIRTIO_ID_IOMMU,
    Sound = virtio_ids::VIRTIO_ID_SOUND,
    Fs = virtio_ids::VIRTIO_ID_FS,
    Pmem = virtio_ids::VIRTIO_ID_PMEM,
    #[serde(rename = "mac80211-hwsim")]
    Mac80211HwSim = virtio_ids::VIRTIO_ID_MAC80211_HWSIM,
    VideoEncoder = virtio_ids::VIRTIO_ID_VIDEO_ENCODER,
    VideoDecoder = virtio_ids::VIRTIO_ID_VIDEO_DECODER,
    Scmi = virtio_ids::VIRTIO_ID_SCMI,
    Wl = virtio_ids::VIRTIO_ID_WL,
    Tpm = virtio_ids::VIRTIO_ID_TPM,
    Pvclock = virtio_ids::VIRTIO_ID_PVCLOCK,
}

impl DeviceType {
    /// Returns the minimum number of queues that a device of the corresponding type must support.
    ///
    /// Note that this does not mean a driver must activate these queues, only that they must be
    /// implemented by a spec-compliant device.
    pub fn min_queues(&self) -> usize {
        match self {
            DeviceType::Net => 3,           // rx, tx (TODO: b/314353246: ctrl is optional)
            DeviceType::Block => 1,         // request queue
            DeviceType::Console => 2,       // receiveq, transmitq
            DeviceType::Rng => 1,           // request queue
            DeviceType::Balloon => 2,       // inflateq, deflateq
            DeviceType::Scsi => 3,          // controlq, eventq, request queue
            DeviceType::P9 => 1,            // request queue
            DeviceType::Gpu => 2,           // controlq, cursorq
            DeviceType::Input => 2,         // eventq, statusq
            DeviceType::Vsock => 3,         // rx, tx, event
            DeviceType::Iommu => 2,         // requestq, eventq
            DeviceType::Sound => 4,         // controlq, eventq, txq, rxq
            DeviceType::Fs => 2,            // hiprio, request queue
            DeviceType::Pmem => 1,          // request queue
            DeviceType::Mac80211HwSim => 2, // tx, rx
            DeviceType::VideoEncoder => 2,  // cmdq, eventq
            DeviceType::VideoDecoder => 2,  // cmdq, eventq
            DeviceType::Scmi => 2,          // cmdq, eventq
            DeviceType::Wl => 2,            // in, out
            DeviceType::Tpm => 1,           // request queue
            DeviceType::Pvclock => 1,       // request queue
        }
    }
}

/// Prints a string representation of the given virtio device type.
impl std::fmt::Display for DeviceType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match &self {
            DeviceType::Net => write!(f, "net"),
            DeviceType::Block => write!(f, "block"),
            DeviceType::Console => write!(f, "console"),
            DeviceType::Rng => write!(f, "rng"),
            DeviceType::Balloon => write!(f, "balloon"),
            DeviceType::Scsi => write!(f, "scsi"),
            DeviceType::P9 => write!(f, "9p"),
            DeviceType::Input => write!(f, "input"),
            DeviceType::Gpu => write!(f, "gpu"),
            DeviceType::Vsock => write!(f, "vsock"),
            DeviceType::Iommu => write!(f, "iommu"),
            DeviceType::Sound => write!(f, "sound"),
            DeviceType::Fs => write!(f, "fs"),
            DeviceType::Pmem => write!(f, "pmem"),
            DeviceType::Wl => write!(f, "wl"),
            DeviceType::Tpm => write!(f, "tpm"),
            DeviceType::Pvclock => write!(f, "pvclock"),
            DeviceType::VideoDecoder => write!(f, "video-decoder"),
            DeviceType::VideoEncoder => write!(f, "video-encoder"),
            DeviceType::Mac80211HwSim => write!(f, "mac80211-hwsim"),
            DeviceType::Scmi => write!(f, "scmi"),
        }
    }
}

/// Copy virtio device configuration data from a subslice of `src` to a subslice of `dst`.
/// Unlike std::slice::copy_from_slice(), this function copies as much as possible within
/// the common subset of the two slices, truncating the requested range instead of
/// panicking if the slices do not match in size.
///
/// `dst_offset` and `src_offset` specify the starting indexes of the `dst` and `src`
/// slices, respectively; if either index is out of bounds, this function is a no-op
/// rather than panicking.  This makes it safe to call with arbitrary user-controlled
/// inputs.
pub fn copy_config(dst: &mut [u8], dst_offset: u64, src: &[u8], src_offset: u64) {
    if let Ok(dst_offset) = usize::try_from(dst_offset) {
        if let Ok(src_offset) = usize::try_from(src_offset) {
            if let Some(dst_slice) = dst.get_mut(dst_offset..) {
                if let Some(src_slice) = src.get(src_offset..) {
                    let len = cmp::min(dst_slice.len(), src_slice.len());
                    let dst_subslice = &mut dst_slice[0..len];
                    let src_subslice = &src_slice[0..len];
                    dst_subslice.copy_from_slice(src_subslice);
                }
            }
        }
    }
}

/// Returns the set of reserved base features common to all virtio devices.
pub fn base_features(protection_type: ProtectionType) -> u64 {
    let mut features: u64 =
        1 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_RING_F_EVENT_IDX | 1 << VIRTIO_F_SUSPEND;

    if protection_type != ProtectionType::Unprotected {
        features |= 1 << VIRTIO_F_ACCESS_PLATFORM;
    }

    features
}

/// Type of virtio transport.
///
/// The virtio protocol can be transported by several means, which affects a few things for device
/// creation - for instance, the seccomp policy we need to use when jailing the device.
pub enum VirtioDeviceType {
    /// A regular (in-VMM) virtio device.
    Regular,
    /// Socket-backed vhost-user device.
    VhostUser,
}

impl VirtioDeviceType {
    /// Returns the seccomp policy file that we will want to load for device `base`, depending on
    /// the virtio transport type.
    pub fn seccomp_policy_file(&self, base: &str) -> String {
        match self {
            VirtioDeviceType::Regular => format!("{base}_device"),
            VirtioDeviceType::VhostUser => format!("{base}_device_vhost_user"),
        }
    }
}

/// Creates a oneshot channel, returning the rx end and adding the tx end to the
/// provided `Vec`. Useful for creating oneshots that signal a virtqueue future
/// to stop processing and exit.
pub(crate) fn create_stop_oneshot(tx_vec: &mut Vec<oneshot::Sender<()>>) -> oneshot::Receiver<()> {
    let (stop_tx, stop_rx) = futures::channel::oneshot::channel();
    tx_vec.push(stop_tx);
    stop_rx
}

/// When we request to stop the worker, this represents the terminal state
/// for the thread (if it exists).
pub(crate) enum StoppedWorker<Q> {
    /// Worker stopped successfully & returned its queues.
    WithQueues(Box<Q>),

    /// Worker wasn't running when the stop was requested.
    AlreadyStopped,

    /// Worker was running but did not successfully return its queues. Something
    /// has gone wrong (and will be in the error log). In the case of a device
    /// reset this is fine since the next activation will replace the queues.
    MissingQueues,
}