// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE-BSD-3-Clause file. // // Copyright © 2019 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause //! Implements virtio devices, queues, and transport mechanisms. use std::sync::atomic::{AtomicU8, Ordering}; use std::{fmt, io, result}; use serde::{Deserialize, Serialize}; use thiserror::Error; #[macro_use] mod device; pub mod balloon; pub mod block; mod console; pub mod epoll_helper; mod iommu; pub mod mem; pub mod net; mod pmem; mod rng; mod rtc; pub mod seccomp_filters; mod thread_helper; pub mod transport; pub mod vdpa; pub mod vhost_user; pub mod vsock; pub mod watchdog; use vm_memory::bitmap::AtomicBitmap; use vm_memory::{GuestAddress, GuestMemory}; use vm_virtio::VirtioDeviceType; pub use self::balloon::Balloon; pub use self::block::{Block, BlockState}; pub use self::console::{Console, ConsoleResizer, Endpoint}; pub use self::device::{ ActivationContext, DmaRemapping, VirtioCommon, VirtioDevice, VirtioInterrupt, VirtioInterruptType, VirtioSharedMemoryList, }; pub use self::epoll_helper::{ EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, }; pub use self::iommu::{AccessPlatformMapping, Iommu, IommuMapping}; pub use self::mem::{BlocksState, Mem, VIRTIO_MEM_ALIGN_SIZE, VirtioMemMappingSource}; pub use self::net::{Net, NetCtrlEpollHandler}; pub use self::pmem::Pmem; pub use self::rng::Rng; pub use self::rtc::Rtc; pub use self::vdpa::{Vdpa, VdpaDmaMapping}; pub use self::vsock::Vsock; pub use self::watchdog::Watchdog; type GuestMemoryMmap = vm_memory::GuestMemoryMmap; type GuestRegionMmap = vm_memory::GuestRegionMmap; type MmapRegion = vm_memory::MmapRegion; const DEVICE_INIT: u32 = 0x00; const DEVICE_ACKNOWLEDGE: u32 = 0x01; const DEVICE_DRIVER: u32 = 0x02; const DEVICE_DRIVER_OK: u32 = 0x04; const DEVICE_FEATURES_OK: u32 = 0x08; const DEVICE_NEEDS_RESET: u32 = 0x40; const DEVICE_FAILED: u32 = 0x80; /// Marks a virtio device as `NEEDS_RESET` and notifies the guest via a config /// change interrupt. Used when a guest induced error (corrupted virtqueue, /// malformed descriptor chain or similar) is detected and the worker thread /// has stopped processing the device's queues. /// /// `context` is included verbatim in the warning log to identify the cause. pub(crate) fn mark_device_needs_reset( device_status: &AtomicU8, interrupt_cb: &dyn self::VirtioInterrupt, context: fmt::Arguments<'_>, ) { log::warn!( "Corrupted request detected ({context}). Setting device status to 'NEEDS_RESET' and stopping processing queues until reset." ); device_status.fetch_or(DEVICE_NEEDS_RESET as u8, Ordering::SeqCst); if let Err(e) = interrupt_cb.trigger(self::VirtioInterruptType::Config) { log::error!("Failed to signal config interrupt: {e:?}"); } } const VIRTIO_F_RING_INDIRECT_DESC: u32 = 28; const VIRTIO_F_RING_EVENT_IDX: u32 = 29; const VIRTIO_F_VERSION_1: u32 = 32; const VIRTIO_F_ACCESS_PLATFORM: u32 = 33; const VIRTIO_F_IN_ORDER: u32 = 35; const VIRTIO_F_ORDER_PLATFORM: u32 = 36; #[expect(dead_code)] const VIRTIO_F_SR_IOV: u32 = 37; const VIRTIO_F_NOTIFICATION_DATA: u32 = 38; #[derive(Error, Debug)] pub enum ActivateError { #[error("Failed to activate virtio device")] BadActivate, #[error("Failed to clone EventFd")] CloneEventFd(#[source] io::Error), #[error("Failed to spawn thread")] ThreadSpawn(#[source] io::Error), #[error("Failed to setup vhost-user-fs daemon")] VhostUserFsSetup(#[source] vhost_user::Error), #[error("Failed to setup vhost-user daemon")] VhostUserSetup(#[source] vhost_user::Error), #[error("Failed to create seccomp filter")] CreateSeccompFilter(#[source] seccompiler::Error), #[error("Failed to create rate limiter")] CreateRateLimiter(#[source] io::Error), #[error("Failed to activate the vDPA device")] ActivateVdpa(#[source] vdpa::Error), } pub type ActivateResult = result::Result<(), ActivateError>; pub type DeviceEventT = u16; #[derive(Error, Debug)] pub enum Error { #[error("Failed to single used queue")] FailedSignalingUsedQueue(#[source] io::Error), #[error("I/O Error")] IoError(#[source] io::Error), #[error("Failed to update memory vhost-user")] VhostUserUpdateMemory(#[source] vhost_user::Error), #[error("Failed to add memory region vhost-user")] VhostUserAddMemoryRegion(#[source] vhost_user::Error), #[error("Failed to set shared memory region")] SetShmRegionsNotSupported, #[error("Failed to process net queue")] NetQueuePair(#[source] ::net_util::NetQueuePairError), } #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct TokenBucketConfig { pub size: u64, pub one_time_burst: Option, pub refill_time: u64, } #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct RateLimiterConfig { pub bandwidth: Option, pub ops: Option, } impl TryInto for RateLimiterConfig { type Error = io::Error; fn try_into(self) -> result::Result { let bw = self.bandwidth.unwrap_or_default(); let ops = self.ops.unwrap_or_default(); rate_limiter::RateLimiter::new( bw.size, bw.one_time_burst.unwrap_or(0), bw.refill_time, ops.size, ops.one_time_burst.unwrap_or(0), ops.refill_time, ) } } /// Return the host virtual address corresponding to the given guest address range /// /// Convert an absolute address into an address space (GuestMemory) /// to a host pointer and verify that the provided size defines a valid /// range within a single memory region. /// Return None if it is out of bounds, spans multiple regions, or has /// zero size at an unmapped GPA. pub fn get_host_address_range( mem: &M, addr: GuestAddress, size: usize, ) -> Option<*mut u8> { // Reject zero-length, no use of a pointer to an empty range. if size == 0 { return None; } let slice = mem.get_slice(addr, size).ok()?; Some(slice.ptr_guard_mut().as_ptr()) }