| // Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! Define communication messages for the vhost-user protocol. |
| //! |
| //! For message definition, please refer to the [vhost-user spec](https://github.com/qemu/qemu/blob/f7526eece29cd2e36a63b6703508b24453095eb8/docs/interop/vhost-user.txt). |
| |
| #![allow(dead_code)] |
| #![allow(non_camel_case_types)] |
| #![allow(clippy::upper_case_acronyms)] |
| |
| use std::fmt::Debug; |
| use std::marker::PhantomData; |
| |
| use crate::VringConfigData; |
| |
| /// The vhost-user specification uses a field of u32 to store message length. |
| /// On the other hand, preallocated buffers are needed to receive messages from the Unix domain |
| /// socket. To preallocating a 4GB buffer for each vhost-user message is really just an overhead. |
| /// Among all defined vhost-user messages, only the VhostUserConfig and VhostUserMemory has variable |
| /// message size. For the VhostUserConfig, a maximum size of 4K is enough because the user |
| /// configuration space for virtio devices is (4K - 0x100) bytes at most. For the VhostUserMemory, |
| /// 4K should be enough too because it can support 255 memory regions at most. |
| pub const MAX_MSG_SIZE: usize = 0x1000; |
| |
| /// The VhostUserMemory message has variable message size and variable number of attached file |
| /// descriptors. Each user memory region entry in the message payload occupies 32 bytes, |
| /// so setting maximum number of attached file descriptors based on the maximum message size. |
| /// But rust only implements Default and AsMut traits for arrays with 0 - 32 entries, so further |
| /// reduce the maximum number... |
| // pub const MAX_ATTACHED_FD_ENTRIES: usize = (MAX_MSG_SIZE - 8) / 32; |
| pub const MAX_ATTACHED_FD_ENTRIES: usize = 32; |
| |
| /// Starting position (inclusion) of the device configuration space in virtio devices. |
| pub const VHOST_USER_CONFIG_OFFSET: u32 = 0x100; |
| |
| /// Ending position (exclusion) of the device configuration space in virtio devices. |
| pub const VHOST_USER_CONFIG_SIZE: u32 = 0x1000; |
| |
| /// Maximum number of vrings supported. |
| pub const VHOST_USER_MAX_VRINGS: u64 = 0x8000u64; |
| |
| pub(super) trait Req: |
| Clone + Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Into<u32> |
| { |
| fn is_valid(&self) -> bool; |
| } |
| |
| /// Type of requests sending from masters to slaves. |
| #[repr(u32)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| pub enum MasterReq { |
| /// Null operation. |
| NOOP = 0, |
| /// Get from the underlying vhost implementation the features bit mask. |
| GET_FEATURES = 1, |
| /// Enable features in the underlying vhost implementation using a bit mask. |
| SET_FEATURES = 2, |
| /// Set the current Master as an owner of the session. |
| SET_OWNER = 3, |
| /// No longer used. |
| RESET_OWNER = 4, |
| /// Set the memory map regions on the slave so it can translate the vring addresses. |
| SET_MEM_TABLE = 5, |
| /// Set logging shared memory space. |
| SET_LOG_BASE = 6, |
| /// Set the logging file descriptor, which is passed as ancillary data. |
| SET_LOG_FD = 7, |
| /// Set the size of the queue. |
| SET_VRING_NUM = 8, |
| /// Set the addresses of the different aspects of the vring. |
| SET_VRING_ADDR = 9, |
| /// Set the base offset in the available vring. |
| SET_VRING_BASE = 10, |
| /// Get the available vring base offset. |
| GET_VRING_BASE = 11, |
| /// Set the event file descriptor for adding buffers to the vring. |
| SET_VRING_KICK = 12, |
| /// Set the event file descriptor to signal when buffers are used. |
| SET_VRING_CALL = 13, |
| /// Set the event file descriptor to signal when error occurs. |
| SET_VRING_ERR = 14, |
| /// Get the protocol feature bit mask from the underlying vhost implementation. |
| GET_PROTOCOL_FEATURES = 15, |
| /// Enable protocol features in the underlying vhost implementation. |
| SET_PROTOCOL_FEATURES = 16, |
| /// Query how many queues the backend supports. |
| GET_QUEUE_NUM = 17, |
| /// Signal slave to enable or disable corresponding vring. |
| SET_VRING_ENABLE = 18, |
| /// Ask vhost user backend to broadcast a fake RARP to notify the migration is terminated |
| /// for guest that does not support GUEST_ANNOUNCE. |
| SEND_RARP = 19, |
| /// Set host MTU value exposed to the guest. |
| NET_SET_MTU = 20, |
| /// Set the socket file descriptor for slave initiated requests. |
| SET_SLAVE_REQ_FD = 21, |
| /// Send IOTLB messages with struct vhost_iotlb_msg as payload. |
| IOTLB_MSG = 22, |
| /// Set the endianness of a VQ for legacy devices. |
| SET_VRING_ENDIAN = 23, |
| /// Fetch the contents of the virtio device configuration space. |
| GET_CONFIG = 24, |
| /// Change the contents of the virtio device configuration space. |
| SET_CONFIG = 25, |
| /// Create a session for crypto operation. |
| CREATE_CRYPTO_SESSION = 26, |
| /// Close a session for crypto operation. |
| CLOSE_CRYPTO_SESSION = 27, |
| /// Advise slave that a migration with postcopy enabled is underway. |
| POSTCOPY_ADVISE = 28, |
| /// Advise slave that a transition to postcopy mode has happened. |
| POSTCOPY_LISTEN = 29, |
| /// Advise that postcopy migration has now completed. |
| POSTCOPY_END = 30, |
| /// Get a shared buffer from slave. |
| GET_INFLIGHT_FD = 31, |
| /// Send the shared inflight buffer back to slave. |
| SET_INFLIGHT_FD = 32, |
| /// Sets the GPU protocol socket file descriptor. |
| GPU_SET_SOCKET = 33, |
| /// Ask the vhost user backend to disable all rings and reset all internal |
| /// device state to the initial state. |
| RESET_DEVICE = 34, |
| /// Indicate that a buffer was added to the vring instead of signalling it |
| /// using the vring’s kick file descriptor. |
| VRING_KICK = 35, |
| /// Return a u64 payload containing the maximum number of memory slots. |
| GET_MAX_MEM_SLOTS = 36, |
| /// Update the memory tables by adding the region described. |
| ADD_MEM_REG = 37, |
| /// Update the memory tables by removing the region described. |
| REM_MEM_REG = 38, |
| /// Notify the backend with updated device status as defined in the VIRTIO |
| /// specification. |
| SET_STATUS = 39, |
| /// Query the backend for its device status as defined in the VIRTIO |
| /// specification. |
| GET_STATUS = 40, |
| /// Upper bound of valid commands. |
| MAX_CMD = 41, |
| } |
| |
| impl From<MasterReq> for u32 { |
| fn from(req: MasterReq) -> u32 { |
| req as u32 |
| } |
| } |
| |
| impl Req for MasterReq { |
| fn is_valid(&self) -> bool { |
| (*self > MasterReq::NOOP) && (*self < MasterReq::MAX_CMD) |
| } |
| } |
| |
| /// Type of requests sending from slaves to masters. |
| #[repr(u32)] |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| pub enum SlaveReq { |
| /// Null operation. |
| NOOP = 0, |
| /// Send IOTLB messages with struct vhost_iotlb_msg as payload. |
| IOTLB_MSG = 1, |
| /// Notify that the virtio device's configuration space has changed. |
| CONFIG_CHANGE_MSG = 2, |
| /// Set host notifier for a specified queue. |
| VRING_HOST_NOTIFIER_MSG = 3, |
| /// Indicate that a buffer was used from the vring. |
| VRING_CALL = 4, |
| /// Indicate that an error occurred on the specific vring. |
| VRING_ERR = 5, |
| /// Virtio-fs draft: map file content into the window. |
| FS_MAP = 6, |
| /// Virtio-fs draft: unmap file content from the window. |
| FS_UNMAP = 7, |
| /// Virtio-fs draft: sync file content. |
| FS_SYNC = 8, |
| /// Virtio-fs draft: perform a read/write from an fd directly to GPA. |
| FS_IO = 9, |
| /// Upper bound of valid commands. |
| MAX_CMD = 10, |
| } |
| |
| impl From<SlaveReq> for u32 { |
| fn from(req: SlaveReq) -> u32 { |
| req as u32 |
| } |
| } |
| |
| impl Req for SlaveReq { |
| fn is_valid(&self) -> bool { |
| (*self > SlaveReq::NOOP) && (*self < SlaveReq::MAX_CMD) |
| } |
| } |
| |
| /// Vhost message Validator. |
| pub trait VhostUserMsgValidator { |
| /// Validate message syntax only. |
| /// It doesn't validate message semantics such as protocol version number and dependency |
| /// on feature flags etc. |
| fn is_valid(&self) -> bool { |
| true |
| } |
| } |
| |
| // Bit mask for common message flags. |
| bitflags! { |
| /// Common message flags for vhost-user requests and replies. |
| pub struct VhostUserHeaderFlag: u32 { |
| /// Bits[0..2] is message version number. |
| const VERSION = 0x3; |
| /// Mark message as reply. |
| const REPLY = 0x4; |
| /// Sender anticipates a reply message from the peer. |
| const NEED_REPLY = 0x8; |
| /// All valid bits. |
| const ALL_FLAGS = 0xc; |
| /// All reserved bits. |
| const RESERVED_BITS = !0xf; |
| } |
| } |
| |
| /// Common message header for vhost-user requests and replies. |
| /// A vhost-user message consists of 3 header fields and an optional payload. All numbers are in the |
| /// machine native byte order. |
| #[allow(safe_packed_borrows)] |
| #[repr(packed)] |
| #[derive(Debug, Clone, Copy, PartialEq)] |
| pub(super) struct VhostUserMsgHeader<R: Req> { |
| request: u32, |
| flags: u32, |
| size: u32, |
| _r: PhantomData<R>, |
| } |
| |
| impl<R: Req> VhostUserMsgHeader<R> { |
| /// Create a new instance of `VhostUserMsgHeader`. |
| pub fn new(request: R, flags: u32, size: u32) -> Self { |
| // Default to protocol version 1 |
| let fl = (flags & VhostUserHeaderFlag::ALL_FLAGS.bits()) | 0x1; |
| VhostUserMsgHeader { |
| request: request.into(), |
| flags: fl, |
| size, |
| _r: PhantomData, |
| } |
| } |
| |
| /// Get message type. |
| pub fn get_code(&self) -> R { |
| // It's safe because R is marked as repr(u32). |
| unsafe { std::mem::transmute_copy::<u32, R>(&self.request) } |
| } |
| |
| /// Set message type. |
| pub fn set_code(&mut self, request: R) { |
| self.request = request.into(); |
| } |
| |
| /// Get message version number. |
| pub fn get_version(&self) -> u32 { |
| self.flags & 0x3 |
| } |
| |
| /// Set message version number. |
| pub fn set_version(&mut self, ver: u32) { |
| self.flags &= !0x3; |
| self.flags |= ver & 0x3; |
| } |
| |
| /// Check whether it's a reply message. |
| pub fn is_reply(&self) -> bool { |
| (self.flags & VhostUserHeaderFlag::REPLY.bits()) != 0 |
| } |
| |
| /// Mark message as reply. |
| pub fn set_reply(&mut self, is_reply: bool) { |
| if is_reply { |
| self.flags |= VhostUserHeaderFlag::REPLY.bits(); |
| } else { |
| self.flags &= !VhostUserHeaderFlag::REPLY.bits(); |
| } |
| } |
| |
| /// Check whether reply for this message is requested. |
| pub fn is_need_reply(&self) -> bool { |
| (self.flags & VhostUserHeaderFlag::NEED_REPLY.bits()) != 0 |
| } |
| |
| /// Mark that reply for this message is needed. |
| pub fn set_need_reply(&mut self, need_reply: bool) { |
| if need_reply { |
| self.flags |= VhostUserHeaderFlag::NEED_REPLY.bits(); |
| } else { |
| self.flags &= !VhostUserHeaderFlag::NEED_REPLY.bits(); |
| } |
| } |
| |
| /// Check whether it's the reply message for the request `req`. |
| pub fn is_reply_for(&self, req: &VhostUserMsgHeader<R>) -> bool { |
| self.is_reply() && !req.is_reply() && self.get_code() == req.get_code() |
| } |
| |
| /// Get message size. |
| pub fn get_size(&self) -> u32 { |
| self.size |
| } |
| |
| /// Set message size. |
| pub fn set_size(&mut self, size: u32) { |
| self.size = size; |
| } |
| } |
| |
| impl<R: Req> Default for VhostUserMsgHeader<R> { |
| fn default() -> Self { |
| VhostUserMsgHeader { |
| request: 0, |
| flags: 0x1, |
| size: 0, |
| _r: PhantomData, |
| } |
| } |
| } |
| |
| impl<T: Req> VhostUserMsgValidator for VhostUserMsgHeader<T> { |
| #[allow(clippy::if_same_then_else)] |
| fn is_valid(&self) -> bool { |
| if !self.get_code().is_valid() { |
| return false; |
| } else if self.size as usize > MAX_MSG_SIZE { |
| return false; |
| } else if self.get_version() != 0x1 { |
| return false; |
| } else if (self.flags & VhostUserHeaderFlag::RESERVED_BITS.bits()) != 0 { |
| return false; |
| } |
| true |
| } |
| } |
| |
| // Bit mask for transport specific flags in VirtIO feature set defined by vhost-user. |
| bitflags! { |
| /// Transport specific flags in VirtIO feature set defined by vhost-user. |
| pub struct VhostUserVirtioFeatures: u64 { |
| /// Feature flag for the protocol feature. |
| const PROTOCOL_FEATURES = 0x4000_0000; |
| } |
| } |
| |
| // Bit mask for vhost-user protocol feature flags. |
| bitflags! { |
| /// Vhost-user protocol feature flags. |
| pub struct VhostUserProtocolFeatures: u64 { |
| /// Support multiple queues. |
| const MQ = 0x0000_0001; |
| /// Support logging through shared memory fd. |
| const LOG_SHMFD = 0x0000_0002; |
| /// Support broadcasting fake RARP packet. |
| const RARP = 0x0000_0004; |
| /// Support sending reply messages for requests with NEED_REPLY flag set. |
| const REPLY_ACK = 0x0000_0008; |
| /// Support setting MTU for virtio-net devices. |
| const MTU = 0x0000_0010; |
| /// Allow the slave to send requests to the master by an optional communication channel. |
| const SLAVE_REQ = 0x0000_0020; |
| /// Support setting slave endian by SET_VRING_ENDIAN. |
| const CROSS_ENDIAN = 0x0000_0040; |
| /// Support crypto operations. |
| const CRYPTO_SESSION = 0x0000_0080; |
| /// Support sending userfault_fd from slaves to masters. |
| const PAGEFAULT = 0x0000_0100; |
| /// Support Virtio device configuration. |
| const CONFIG = 0x0000_0200; |
| /// Allow the slave to send fds (at most 8 descriptors in each message) to the master. |
| const SLAVE_SEND_FD = 0x0000_0400; |
| /// Allow the slave to register a host notifier. |
| const HOST_NOTIFIER = 0x0000_0800; |
| /// Support inflight shmfd. |
| const INFLIGHT_SHMFD = 0x0000_1000; |
| /// Support resetting the device. |
| const RESET_DEVICE = 0x0000_2000; |
| /// Support inband notifications. |
| const INBAND_NOTIFICATIONS = 0x0000_4000; |
| /// Support configuring memory slots. |
| const CONFIGURE_MEM_SLOTS = 0x0000_8000; |
| /// Support reporting status. |
| const STATUS = 0x0001_0000; |
| } |
| } |
| |
| /// A generic message to encapsulate a 64-bit value. |
| #[repr(packed)] |
| #[derive(Default)] |
| pub struct VhostUserU64 { |
| /// The encapsulated 64-bit common value. |
| pub value: u64, |
| } |
| |
| impl VhostUserU64 { |
| /// Create a new instance. |
| pub fn new(value: u64) -> Self { |
| VhostUserU64 { value } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserU64 {} |
| |
| /// Memory region descriptor for the SET_MEM_TABLE request. |
| #[repr(packed)] |
| #[derive(Default)] |
| pub struct VhostUserMemory { |
| /// Number of memory regions in the payload. |
| pub num_regions: u32, |
| /// Padding for alignment. |
| pub padding1: u32, |
| } |
| |
| impl VhostUserMemory { |
| /// Create a new instance. |
| pub fn new(cnt: u32) -> Self { |
| VhostUserMemory { |
| num_regions: cnt, |
| padding1: 0, |
| } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserMemory { |
| #[allow(clippy::if_same_then_else)] |
| fn is_valid(&self) -> bool { |
| if self.padding1 != 0 { |
| return false; |
| } else if self.num_regions == 0 || self.num_regions > MAX_ATTACHED_FD_ENTRIES as u32 { |
| return false; |
| } |
| true |
| } |
| } |
| |
| /// Memory region descriptors as payload for the SET_MEM_TABLE request. |
| #[repr(packed)] |
| #[derive(Default, Clone, Copy)] |
| pub struct VhostUserMemoryRegion { |
| /// Guest physical address of the memory region. |
| pub guest_phys_addr: u64, |
| /// Size of the memory region. |
| pub memory_size: u64, |
| /// Virtual address in the current process. |
| pub user_addr: u64, |
| /// Offset where region starts in the mapped memory. |
| pub mmap_offset: u64, |
| } |
| |
| impl VhostUserMemoryRegion { |
| /// Create a new instance. |
| pub fn new(guest_phys_addr: u64, memory_size: u64, user_addr: u64, mmap_offset: u64) -> Self { |
| VhostUserMemoryRegion { |
| guest_phys_addr, |
| memory_size, |
| user_addr, |
| mmap_offset, |
| } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserMemoryRegion { |
| fn is_valid(&self) -> bool { |
| if self.memory_size == 0 |
| || self.guest_phys_addr.checked_add(self.memory_size).is_none() |
| || self.user_addr.checked_add(self.memory_size).is_none() |
| || self.mmap_offset.checked_add(self.memory_size).is_none() |
| { |
| return false; |
| } |
| true |
| } |
| } |
| |
| /// Payload of the VhostUserMemory message. |
| pub type VhostUserMemoryPayload = Vec<VhostUserMemoryRegion>; |
| |
| /// Single memory region descriptor as payload for ADD_MEM_REG and REM_MEM_REG |
| /// requests. |
| #[repr(C)] |
| #[derive(Default, Clone, Copy)] |
| pub struct VhostUserSingleMemoryRegion { |
| /// Padding for correct alignment |
| padding: u64, |
| /// Guest physical address of the memory region. |
| pub guest_phys_addr: u64, |
| /// Size of the memory region. |
| pub memory_size: u64, |
| /// Virtual address in the current process. |
| pub user_addr: u64, |
| /// Offset where region starts in the mapped memory. |
| pub mmap_offset: u64, |
| } |
| |
| impl VhostUserSingleMemoryRegion { |
| /// Create a new instance. |
| pub fn new(guest_phys_addr: u64, memory_size: u64, user_addr: u64, mmap_offset: u64) -> Self { |
| VhostUserSingleMemoryRegion { |
| padding: 0, |
| guest_phys_addr, |
| memory_size, |
| user_addr, |
| mmap_offset, |
| } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserSingleMemoryRegion { |
| fn is_valid(&self) -> bool { |
| if self.memory_size == 0 |
| || self.guest_phys_addr.checked_add(self.memory_size).is_none() |
| || self.user_addr.checked_add(self.memory_size).is_none() |
| || self.mmap_offset.checked_add(self.memory_size).is_none() |
| { |
| return false; |
| } |
| true |
| } |
| } |
| |
| /// Vring state descriptor. |
| #[repr(packed)] |
| #[derive(Default)] |
| pub struct VhostUserVringState { |
| /// Vring index. |
| pub index: u32, |
| /// A common 32bit value to encapsulate vring state etc. |
| pub num: u32, |
| } |
| |
| impl VhostUserVringState { |
| /// Create a new instance. |
| pub fn new(index: u32, num: u32) -> Self { |
| VhostUserVringState { index, num } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserVringState {} |
| |
| // Bit mask for vring address flags. |
| bitflags! { |
| /// Flags for vring address. |
| pub struct VhostUserVringAddrFlags: u32 { |
| /// Support log of vring operations. |
| /// Modifications to "used" vring should be logged. |
| const VHOST_VRING_F_LOG = 0x1; |
| } |
| } |
| |
| /// Vring address descriptor. |
| #[repr(packed)] |
| #[derive(Default)] |
| pub struct VhostUserVringAddr { |
| /// Vring index. |
| pub index: u32, |
| /// Vring flags defined by VhostUserVringAddrFlags. |
| pub flags: u32, |
| /// Ring address of the vring descriptor table. |
| pub descriptor: u64, |
| /// Ring address of the vring used ring. |
| pub used: u64, |
| /// Ring address of the vring available ring. |
| pub available: u64, |
| /// Guest address for logging. |
| pub log: u64, |
| } |
| |
| impl VhostUserVringAddr { |
| /// Create a new instance. |
| pub fn new( |
| index: u32, |
| flags: VhostUserVringAddrFlags, |
| descriptor: u64, |
| used: u64, |
| available: u64, |
| log: u64, |
| ) -> Self { |
| VhostUserVringAddr { |
| index, |
| flags: flags.bits(), |
| descriptor, |
| used, |
| available, |
| log, |
| } |
| } |
| |
| /// Create a new instance from `VringConfigData`. |
| #[cfg_attr(feature = "cargo-clippy", allow(clippy::identity_conversion))] |
| pub fn from_config_data(index: u32, config_data: &VringConfigData) -> Self { |
| let log_addr = config_data.log_addr.unwrap_or(0); |
| VhostUserVringAddr { |
| index, |
| flags: config_data.flags, |
| descriptor: config_data.desc_table_addr, |
| used: config_data.used_ring_addr, |
| available: config_data.avail_ring_addr, |
| log: log_addr, |
| } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserVringAddr { |
| #[allow(clippy::if_same_then_else)] |
| fn is_valid(&self) -> bool { |
| if (self.flags & !VhostUserVringAddrFlags::all().bits()) != 0 { |
| return false; |
| } else if self.descriptor & 0xf != 0 { |
| return false; |
| } else if self.available & 0x1 != 0 { |
| return false; |
| } else if self.used & 0x3 != 0 { |
| return false; |
| } |
| true |
| } |
| } |
| |
| // Bit mask for the vhost-user device configuration message. |
| bitflags! { |
| /// Flags for the device configuration message. |
| pub struct VhostUserConfigFlags: u32 { |
| /// Vhost master messages used for writeable fields. |
| const WRITABLE = 0x1; |
| /// Vhost master messages used for live migration. |
| const LIVE_MIGRATION = 0x2; |
| } |
| } |
| |
| /// Message to read/write device configuration space. |
| #[repr(packed)] |
| #[derive(Default)] |
| pub struct VhostUserConfig { |
| /// Offset of virtio device's configuration space. |
| pub offset: u32, |
| /// Configuration space access size in bytes. |
| pub size: u32, |
| /// Flags for the device configuration operation. |
| pub flags: u32, |
| } |
| |
| impl VhostUserConfig { |
| /// Create a new instance. |
| pub fn new(offset: u32, size: u32, flags: VhostUserConfigFlags) -> Self { |
| VhostUserConfig { |
| offset, |
| size, |
| flags: flags.bits(), |
| } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserConfig { |
| #[allow(clippy::if_same_then_else)] |
| fn is_valid(&self) -> bool { |
| let end_addr = match self.size.checked_add(self.offset) { |
| Some(addr) => addr, |
| None => return false, |
| }; |
| if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 { |
| return false; |
| } else if self.size == 0 || end_addr > VHOST_USER_CONFIG_SIZE { |
| return false; |
| } |
| true |
| } |
| } |
| |
| /// Payload for the VhostUserConfig message. |
| pub type VhostUserConfigPayload = Vec<u8>; |
| |
| /// Single memory region descriptor as payload for ADD_MEM_REG and REM_MEM_REG |
| /// requests. |
| #[repr(C)] |
| #[derive(Default, Clone)] |
| pub struct VhostUserInflight { |
| /// Size of the area to track inflight I/O. |
| pub mmap_size: u64, |
| /// Offset of this area from the start of the supplied file descriptor. |
| pub mmap_offset: u64, |
| /// Number of virtqueues. |
| pub num_queues: u16, |
| /// Size of virtqueues. |
| pub queue_size: u16, |
| } |
| |
| impl VhostUserInflight { |
| /// Create a new instance. |
| pub fn new(mmap_size: u64, mmap_offset: u64, num_queues: u16, queue_size: u16) -> Self { |
| VhostUserInflight { |
| mmap_size, |
| mmap_offset, |
| num_queues, |
| queue_size, |
| } |
| } |
| } |
| |
| impl VhostUserMsgValidator for VhostUserInflight { |
| fn is_valid(&self) -> bool { |
| if self.num_queues == 0 || self.queue_size == 0 { |
| return false; |
| } |
| true |
| } |
| } |
| |
| /* |
| * TODO: support dirty log, live migration and IOTLB operations. |
| #[repr(packed)] |
| pub struct VhostUserVringArea { |
| pub index: u32, |
| pub flags: u32, |
| pub size: u64, |
| pub offset: u64, |
| } |
| |
| #[repr(packed)] |
| pub struct VhostUserLog { |
| pub size: u64, |
| pub offset: u64, |
| } |
| |
| #[repr(packed)] |
| pub struct VhostUserIotlb { |
| pub iova: u64, |
| pub size: u64, |
| pub user_addr: u64, |
| pub permission: u8, |
| pub optype: u8, |
| } |
| */ |
| |
| // Bit mask for flags in virtio-fs slave messages |
| bitflags! { |
| #[derive(Default)] |
| /// Flags for virtio-fs slave messages. |
| pub struct VhostUserFSSlaveMsgFlags: u64 { |
| /// Empty permission. |
| const EMPTY = 0x0; |
| /// Read permission. |
| const MAP_R = 0x1; |
| /// Write permission. |
| const MAP_W = 0x2; |
| } |
| } |
| |
| /// Max entries in one virtio-fs slave request. |
| pub const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8; |
| |
| /// Slave request message to update the MMIO window. |
| #[repr(packed)] |
| #[derive(Default)] |
| pub struct VhostUserFSSlaveMsg { |
| /// File offset. |
| pub fd_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES], |
| /// Offset into the DAX window. |
| pub cache_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES], |
| /// Size of region to map. |
| pub len: [u64; VHOST_USER_FS_SLAVE_ENTRIES], |
| /// Flags for the mmap operation |
| pub flags: [VhostUserFSSlaveMsgFlags; VHOST_USER_FS_SLAVE_ENTRIES], |
| } |
| |
| impl VhostUserMsgValidator for VhostUserFSSlaveMsg { |
| fn is_valid(&self) -> bool { |
| for i in 0..VHOST_USER_FS_SLAVE_ENTRIES { |
| if ({ self.flags[i] }.bits() & !VhostUserFSSlaveMsgFlags::all().bits()) != 0 |
| || self.fd_offset[i].checked_add(self.len[i]).is_none() |
| || self.cache_offset[i].checked_add(self.len[i]).is_none() |
| { |
| return false; |
| } |
| } |
| true |
| } |
| } |
| |
| /// Inflight I/O descriptor state for split virtqueues |
| #[repr(packed)] |
| #[derive(Clone, Copy, Default)] |
| pub struct DescStateSplit { |
| /// Indicate whether this descriptor (only head) is inflight or not. |
| inflight: u8, |
| /// Padding |
| padding: [u8; 5], |
| /// List of last batch of used descriptors, only when batching is used for submitting |
| next: u16, |
| /// Preserve order of fetching available descriptors, only for head descriptor |
| counter: u64, |
| } |
| |
| impl DescStateSplit { |
| /// New instance of DescStateSplit struct |
| pub fn new() -> Self { |
| Self::default() |
| } |
| } |
| |
| /// Inflight I/O queue region for split virtqueues |
| #[allow(safe_packed_borrows)] |
| #[repr(packed)] |
| pub struct QueueRegionSplit { |
| /// Features flags of this region |
| features: u64, |
| /// Version of this region |
| version: u16, |
| /// Number of DescStateSplit entries |
| desc_num: u16, |
| /// List to track last batch of used descriptors |
| last_batch_head: u16, |
| /// Idx value of used ring |
| used_idx: u16, |
| /// Pointer to an array of DescStateSplit entries |
| desc: u64, |
| } |
| |
| impl QueueRegionSplit { |
| /// New instance of QueueRegionSplit struct |
| pub fn new(features: u64, queue_size: u16) -> Self { |
| QueueRegionSplit { |
| features, |
| version: 1, |
| desc_num: queue_size, |
| last_batch_head: 0, |
| used_idx: 0, |
| desc: 0, |
| } |
| } |
| } |
| |
| /// Inflight I/O descriptor state for packed virtqueues |
| #[repr(packed)] |
| #[derive(Clone, Copy, Default)] |
| pub struct DescStatePacked { |
| /// Indicate whether this descriptor (only head) is inflight or not. |
| inflight: u8, |
| /// Padding |
| padding: u8, |
| /// Link to next free entry |
| next: u16, |
| /// Link to last entry of descriptor list, only for head |
| last: u16, |
| /// Length of descriptor list, only for head |
| num: u16, |
| /// Preserve order of fetching avail descriptors, only for head |
| counter: u64, |
| /// Buffer ID |
| id: u16, |
| /// Descriptor flags |
| flags: u16, |
| /// Buffer length |
| len: u32, |
| /// Buffer address |
| addr: u64, |
| } |
| |
| impl DescStatePacked { |
| /// New instance of DescStatePacked struct |
| pub fn new() -> Self { |
| Self::default() |
| } |
| } |
| |
| /// Inflight I/O queue region for packed virtqueues |
| #[allow(safe_packed_borrows)] |
| #[repr(packed)] |
| pub struct QueueRegionPacked { |
| /// Features flags of this region |
| features: u64, |
| /// version of this region |
| version: u16, |
| /// size of descriptor state array |
| desc_num: u16, |
| /// head of free DescStatePacked entry list |
| free_head: u16, |
| /// old head of free DescStatePacked entry list |
| old_free_head: u16, |
| /// used idx of descriptor ring |
| used_idx: u16, |
| /// old used idx of descriptor ring |
| old_used_idx: u16, |
| /// device ring wrap counter |
| used_wrap_counter: u8, |
| /// old device ring wrap counter |
| old_used_wrap_counter: u8, |
| /// Padding |
| padding: [u8; 7], |
| /// Pointer to array tracking state of each descriptor from descriptor ring |
| desc: u64, |
| } |
| |
| impl QueueRegionPacked { |
| /// New instance of QueueRegionPacked struct |
| pub fn new(features: u64, queue_size: u16) -> Self { |
| QueueRegionPacked { |
| features, |
| version: 1, |
| desc_num: queue_size, |
| free_head: 0, |
| old_free_head: 0, |
| used_idx: 0, |
| old_used_idx: 0, |
| used_wrap_counter: 0, |
| old_used_wrap_counter: 0, |
| padding: [0; 7], |
| desc: 0, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use std::mem; |
| |
| #[test] |
| fn check_master_request_code() { |
| let code = MasterReq::NOOP; |
| assert!(!code.is_valid()); |
| let code = MasterReq::MAX_CMD; |
| assert!(!code.is_valid()); |
| assert!(code > MasterReq::NOOP); |
| let code = MasterReq::GET_FEATURES; |
| assert!(code.is_valid()); |
| assert_eq!(code, code.clone()); |
| let code: MasterReq = unsafe { std::mem::transmute::<u32, MasterReq>(10000u32) }; |
| assert!(!code.is_valid()); |
| } |
| |
| #[test] |
| fn check_slave_request_code() { |
| let code = SlaveReq::NOOP; |
| assert!(!code.is_valid()); |
| let code = SlaveReq::MAX_CMD; |
| assert!(!code.is_valid()); |
| assert!(code > SlaveReq::NOOP); |
| let code = SlaveReq::CONFIG_CHANGE_MSG; |
| assert!(code.is_valid()); |
| assert_eq!(code, code.clone()); |
| let code: SlaveReq = unsafe { std::mem::transmute::<u32, SlaveReq>(10000u32) }; |
| assert!(!code.is_valid()); |
| } |
| |
| #[test] |
| fn msg_header_ops() { |
| let mut hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, 0x100); |
| assert_eq!(hdr.get_code(), MasterReq::GET_FEATURES); |
| hdr.set_code(MasterReq::SET_FEATURES); |
| assert_eq!(hdr.get_code(), MasterReq::SET_FEATURES); |
| |
| assert_eq!(hdr.get_version(), 0x1); |
| |
| assert_eq!(hdr.is_reply(), false); |
| hdr.set_reply(true); |
| assert_eq!(hdr.is_reply(), true); |
| hdr.set_reply(false); |
| |
| assert_eq!(hdr.is_need_reply(), false); |
| hdr.set_need_reply(true); |
| assert_eq!(hdr.is_need_reply(), true); |
| hdr.set_need_reply(false); |
| |
| assert_eq!(hdr.get_size(), 0x100); |
| hdr.set_size(0x200); |
| assert_eq!(hdr.get_size(), 0x200); |
| |
| assert_eq!(hdr.is_need_reply(), false); |
| assert_eq!(hdr.is_reply(), false); |
| assert_eq!(hdr.get_version(), 0x1); |
| |
| // Check message length |
| assert!(hdr.is_valid()); |
| hdr.set_size(0x2000); |
| assert!(!hdr.is_valid()); |
| hdr.set_size(0x100); |
| assert_eq!(hdr.get_size(), 0x100); |
| assert!(hdr.is_valid()); |
| hdr.set_size((MAX_MSG_SIZE - mem::size_of::<VhostUserMsgHeader<MasterReq>>()) as u32); |
| assert!(hdr.is_valid()); |
| hdr.set_size(0x0); |
| assert!(hdr.is_valid()); |
| |
| // Check version |
| hdr.set_version(0x0); |
| assert!(!hdr.is_valid()); |
| hdr.set_version(0x2); |
| assert!(!hdr.is_valid()); |
| hdr.set_version(0x1); |
| assert!(hdr.is_valid()); |
| |
| assert_eq!(hdr, hdr.clone()); |
| } |
| |
| #[test] |
| fn test_vhost_user_message_u64() { |
| let val = VhostUserU64::default(); |
| let val1 = VhostUserU64::new(0); |
| |
| let a = val.value; |
| let b = val1.value; |
| assert_eq!(a, b); |
| let a = VhostUserU64::new(1).value; |
| assert_eq!(a, 1); |
| } |
| |
| #[test] |
| fn check_user_memory() { |
| let mut msg = VhostUserMemory::new(1); |
| assert!(msg.is_valid()); |
| msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32; |
| assert!(msg.is_valid()); |
| |
| msg.num_regions += 1; |
| assert!(!msg.is_valid()); |
| msg.num_regions = 0xFFFFFFFF; |
| assert!(!msg.is_valid()); |
| msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32; |
| msg.padding1 = 1; |
| assert!(!msg.is_valid()); |
| } |
| |
| #[test] |
| fn check_user_memory_region() { |
| let mut msg = VhostUserMemoryRegion { |
| guest_phys_addr: 0, |
| memory_size: 0x1000, |
| user_addr: 0, |
| mmap_offset: 0, |
| }; |
| assert!(msg.is_valid()); |
| msg.guest_phys_addr = 0xFFFFFFFFFFFFEFFF; |
| assert!(msg.is_valid()); |
| msg.guest_phys_addr = 0xFFFFFFFFFFFFF000; |
| assert!(!msg.is_valid()); |
| msg.guest_phys_addr = 0xFFFFFFFFFFFF0000; |
| msg.memory_size = 0; |
| assert!(!msg.is_valid()); |
| let a = msg.guest_phys_addr; |
| let b = msg.guest_phys_addr; |
| assert_eq!(a, b); |
| |
| let msg = VhostUserMemoryRegion::default(); |
| let a = msg.guest_phys_addr; |
| assert_eq!(a, 0); |
| let a = msg.memory_size; |
| assert_eq!(a, 0); |
| let a = msg.user_addr; |
| assert_eq!(a, 0); |
| let a = msg.mmap_offset; |
| assert_eq!(a, 0); |
| } |
| |
| #[test] |
| fn test_vhost_user_state() { |
| let state = VhostUserVringState::new(5, 8); |
| |
| let a = state.index; |
| assert_eq!(a, 5); |
| let a = state.num; |
| assert_eq!(a, 8); |
| assert_eq!(state.is_valid(), true); |
| |
| let state = VhostUserVringState::default(); |
| let a = state.index; |
| assert_eq!(a, 0); |
| let a = state.num; |
| assert_eq!(a, 0); |
| assert_eq!(state.is_valid(), true); |
| } |
| |
| #[test] |
| fn test_vhost_user_addr() { |
| let mut addr = VhostUserVringAddr::new( |
| 2, |
| VhostUserVringAddrFlags::VHOST_VRING_F_LOG, |
| 0x1000, |
| 0x2000, |
| 0x3000, |
| 0x4000, |
| ); |
| |
| let a = addr.index; |
| assert_eq!(a, 2); |
| let a = addr.flags; |
| assert_eq!(a, VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits()); |
| let a = addr.descriptor; |
| assert_eq!(a, 0x1000); |
| let a = addr.used; |
| assert_eq!(a, 0x2000); |
| let a = addr.available; |
| assert_eq!(a, 0x3000); |
| let a = addr.log; |
| assert_eq!(a, 0x4000); |
| assert_eq!(addr.is_valid(), true); |
| |
| addr.descriptor = 0x1001; |
| assert_eq!(addr.is_valid(), false); |
| addr.descriptor = 0x1000; |
| |
| addr.available = 0x3001; |
| assert_eq!(addr.is_valid(), false); |
| addr.available = 0x3000; |
| |
| addr.used = 0x2001; |
| assert_eq!(addr.is_valid(), false); |
| addr.used = 0x2000; |
| assert_eq!(addr.is_valid(), true); |
| } |
| |
| #[test] |
| fn test_vhost_user_state_from_config() { |
| let config = VringConfigData { |
| queue_max_size: 256, |
| queue_size: 128, |
| flags: VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits, |
| desc_table_addr: 0x1000, |
| used_ring_addr: 0x2000, |
| avail_ring_addr: 0x3000, |
| log_addr: Some(0x4000), |
| }; |
| let addr = VhostUserVringAddr::from_config_data(2, &config); |
| |
| let a = addr.index; |
| assert_eq!(a, 2); |
| let a = addr.flags; |
| assert_eq!(a, VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits()); |
| let a = addr.descriptor; |
| assert_eq!(a, 0x1000); |
| let a = addr.used; |
| assert_eq!(a, 0x2000); |
| let a = addr.available; |
| assert_eq!(a, 0x3000); |
| let a = addr.log; |
| assert_eq!(a, 0x4000); |
| assert_eq!(addr.is_valid(), true); |
| } |
| |
| #[test] |
| fn check_user_vring_addr() { |
| let mut msg = |
| VhostUserVringAddr::new(0, VhostUserVringAddrFlags::all(), 0x0, 0x0, 0x0, 0x0); |
| assert!(msg.is_valid()); |
| |
| msg.descriptor = 1; |
| assert!(!msg.is_valid()); |
| msg.descriptor = 0; |
| |
| msg.available = 1; |
| assert!(!msg.is_valid()); |
| msg.available = 0; |
| |
| msg.used = 1; |
| assert!(!msg.is_valid()); |
| msg.used = 0; |
| |
| msg.flags |= 0x80000000; |
| assert!(!msg.is_valid()); |
| msg.flags &= !0x80000000; |
| } |
| |
| #[test] |
| fn check_user_config_msg() { |
| let mut msg = |
| VhostUserConfig::new(0, VHOST_USER_CONFIG_SIZE, VhostUserConfigFlags::WRITABLE); |
| |
| assert!(msg.is_valid()); |
| msg.size = 0; |
| assert!(!msg.is_valid()); |
| msg.size = 1; |
| assert!(msg.is_valid()); |
| msg.offset = u32::MAX; |
| assert!(!msg.is_valid()); |
| msg.offset = VHOST_USER_CONFIG_SIZE; |
| assert!(!msg.is_valid()); |
| msg.offset = VHOST_USER_CONFIG_SIZE - 1; |
| assert!(msg.is_valid()); |
| msg.size = 2; |
| assert!(!msg.is_valid()); |
| msg.size = 1; |
| msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits(); |
| assert!(msg.is_valid()); |
| msg.flags |= 0x4; |
| assert!(!msg.is_valid()); |
| } |
| |
| #[test] |
| fn test_vhost_user_fs_slave() { |
| let mut fs_slave = VhostUserFSSlaveMsg::default(); |
| |
| assert_eq!(fs_slave.is_valid(), true); |
| |
| fs_slave.fd_offset[0] = 0xffff_ffff_ffff_ffff; |
| fs_slave.len[0] = 0x1; |
| assert_eq!(fs_slave.is_valid(), false); |
| |
| assert_ne!( |
| VhostUserFSSlaveMsgFlags::MAP_R, |
| VhostUserFSSlaveMsgFlags::MAP_W |
| ); |
| assert_eq!(VhostUserFSSlaveMsgFlags::EMPTY.bits(), 0); |
| } |
| } |