mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-11-09 21:30:07 +00:00
dae0b2ef72
Due to the amount of code currently duplicated across virtio devices, the stats for this commit is on the large side but it's mostly more duplicated code, unfortunately. Migratable and Snapshotable placeholder implementations are provided as well, making all virtio devices Migratable. Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
348 lines
12 KiB
Rust
348 lines
12 KiB
Rust
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// 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 THIRD-PARTY file.
|
|
|
|
mod csm;
|
|
mod device;
|
|
mod packet;
|
|
mod unix;
|
|
|
|
pub use self::device::Vsock;
|
|
pub use self::unix::VsockUnixBackend;
|
|
pub use self::unix::VsockUnixError;
|
|
|
|
use std::os::unix::io::RawFd;
|
|
|
|
use packet::VsockPacket;
|
|
|
|
mod defs {
|
|
|
|
/// Max vsock packet data/buffer size.
|
|
pub const MAX_PKT_BUF_SIZE: usize = 64 * 1024;
|
|
|
|
pub mod uapi {
|
|
|
|
/// Vsock packet operation IDs.
|
|
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
|
|
///
|
|
/// Connection request.
|
|
pub const VSOCK_OP_REQUEST: u16 = 1;
|
|
/// Connection response.
|
|
pub const VSOCK_OP_RESPONSE: u16 = 2;
|
|
/// Connection reset.
|
|
pub const VSOCK_OP_RST: u16 = 3;
|
|
/// Connection clean shutdown.
|
|
pub const VSOCK_OP_SHUTDOWN: u16 = 4;
|
|
/// Connection data (read/write).
|
|
pub const VSOCK_OP_RW: u16 = 5;
|
|
/// Flow control credit update.
|
|
pub const VSOCK_OP_CREDIT_UPDATE: u16 = 6;
|
|
/// Flow control credit update request.
|
|
pub const VSOCK_OP_CREDIT_REQUEST: u16 = 7;
|
|
|
|
/// Vsock packet flags.
|
|
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
|
|
///
|
|
/// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will receive no more data.
|
|
pub const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1;
|
|
/// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will send no more data.
|
|
pub const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2;
|
|
|
|
/// Vsock packet type.
|
|
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
|
|
///
|
|
/// Stream / connection-oriented packet (the only currently valid type).
|
|
pub const VSOCK_TYPE_STREAM: u16 = 1;
|
|
|
|
pub const VSOCK_HOST_CID: u64 = 2;
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum VsockError {
|
|
/// The vsock data/buffer virtio descriptor length is smaller than expected.
|
|
BufDescTooSmall,
|
|
/// The vsock data/buffer virtio descriptor is expected, but missing.
|
|
BufDescMissing,
|
|
/// Chained GuestMemory error.
|
|
GuestMemory,
|
|
/// Bounds check failed on guest memory pointer.
|
|
GuestMemoryBounds,
|
|
/// The vsock header descriptor length is too small.
|
|
HdrDescTooSmall(u32),
|
|
/// The vsock header `len` field holds an invalid value.
|
|
InvalidPktLen(u32),
|
|
/// A data fetch was attempted when no data was available.
|
|
NoData,
|
|
/// A data buffer was expected for the provided packet, but it is missing.
|
|
PktBufMissing,
|
|
/// Encountered an unexpected write-only virtio descriptor.
|
|
UnreadableDescriptor,
|
|
/// Encountered an unexpected read-only virtio descriptor.
|
|
UnwritableDescriptor,
|
|
}
|
|
type Result<T> = std::result::Result<T, VsockError>;
|
|
|
|
#[derive(Debug)]
|
|
pub enum VsockEpollHandlerError {
|
|
/// The vsock data/buffer virtio descriptor length is smaller than expected.
|
|
BufDescTooSmall,
|
|
/// The vsock data/buffer virtio descriptor is expected, but missing.
|
|
BufDescMissing,
|
|
/// Chained GuestMemory error.
|
|
GuestMemory,
|
|
/// Bounds check failed on guest memory pointer.
|
|
GuestMemoryBounds,
|
|
/// The vsock header descriptor length is too small.
|
|
HdrDescTooSmall(u32),
|
|
/// The vsock header `len` field holds an invalid value.
|
|
InvalidPktLen(u32),
|
|
/// A data fetch was attempted when no data was available.
|
|
NoData,
|
|
/// A data buffer was expected for the provided packet, but it is missing.
|
|
PktBufMissing,
|
|
/// Encountered an unexpected write-only virtio descriptor.
|
|
UnreadableDescriptor,
|
|
/// Encountered an unexpected read-only virtio descriptor.
|
|
UnwritableDescriptor,
|
|
}
|
|
|
|
/// A passive, event-driven object, that needs to be notified whenever an epoll-able event occurs.
|
|
/// An event-polling control loop will use `get_polled_fd()` and `get_polled_evset()` to query
|
|
/// the listener for the file descriptor and the set of events it's interested in. When such an
|
|
/// event occurs, the control loop will route the event to the listener via `notify()`.
|
|
///
|
|
pub trait VsockEpollListener {
|
|
/// Get the file descriptor the listener needs polled.
|
|
fn get_polled_fd(&self) -> RawFd;
|
|
|
|
/// Get the set of events for which the listener wants to be notified.
|
|
fn get_polled_evset(&self) -> epoll::Events;
|
|
|
|
/// Notify the listener that one ore more events have occurred.
|
|
fn notify(&mut self, evset: epoll::Events);
|
|
}
|
|
|
|
/// Any channel that handles vsock packet traffic: sending and receiving packets. Since we're
|
|
/// implementing the device model here, our responsibility is to always process the sending of
|
|
/// packets (i.e. the TX queue). So, any locally generated data, addressed to the driver (e.g.
|
|
/// a connection response or RST), will have to be queued, until we get to processing the RX queue.
|
|
///
|
|
/// Note: `recv_pkt()` and `send_pkt()` are named analogous to `Read::read()` and `Write::write()`,
|
|
/// respectively. I.e.
|
|
/// - `recv_pkt(&mut pkt)` will read data from the channel, and place it into `pkt`; and
|
|
/// - `send_pkt(&pkt)` will fetch data from `pkt`, and place it into the channel.
|
|
pub trait VsockChannel {
|
|
/// Read/receive an incoming packet from the channel.
|
|
fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()>;
|
|
|
|
/// Write/send a packet through the channel.
|
|
fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()>;
|
|
|
|
/// Checks whether there is pending incoming data inside the channel, meaning that a subsequent
|
|
/// call to `recv_pkt()` won't fail.
|
|
fn has_pending_rx(&self) -> bool;
|
|
}
|
|
|
|
/// The vsock backend, which is basically an epoll-event-driven vsock channel, that needs to be
|
|
/// sendable through a mpsc channel (the latter due to how `vmm::EpollContext` works).
|
|
/// Currently, the only implementation we have is `crate::virtio::unix::muxer::VsockMuxer`, which
|
|
/// translates guest-side vsock connections to host-side Unix domain socket connections.
|
|
pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use libc::EFD_NONBLOCK;
|
|
|
|
use super::device::{VsockEpollHandler, RX_QUEUE_EVENT, TX_QUEUE_EVENT};
|
|
use super::packet::VSOCK_PKT_HDR_SIZE;
|
|
use super::*;
|
|
|
|
use std::os::unix::io::AsRawFd;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::{Arc, RwLock};
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
use crate::device::{VirtioInterrupt, VirtioInterruptType};
|
|
use crate::queue::tests::VirtQueue as GuestQ;
|
|
use crate::queue::Queue;
|
|
use crate::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
|
|
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
|
|
|
pub struct TestBackend {
|
|
pub evfd: EventFd,
|
|
pub rx_err: Option<VsockError>,
|
|
pub tx_err: Option<VsockError>,
|
|
pub pending_rx: bool,
|
|
pub rx_ok_cnt: usize,
|
|
pub tx_ok_cnt: usize,
|
|
pub evset: Option<epoll::Events>,
|
|
}
|
|
impl TestBackend {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
evfd: EventFd::new(EFD_NONBLOCK).unwrap(),
|
|
rx_err: None,
|
|
tx_err: None,
|
|
pending_rx: false,
|
|
rx_ok_cnt: 0,
|
|
tx_ok_cnt: 0,
|
|
evset: None,
|
|
}
|
|
}
|
|
pub fn set_rx_err(&mut self, err: Option<VsockError>) {
|
|
self.rx_err = err;
|
|
}
|
|
pub fn set_tx_err(&mut self, err: Option<VsockError>) {
|
|
self.tx_err = err;
|
|
}
|
|
pub fn set_pending_rx(&mut self, prx: bool) {
|
|
self.pending_rx = prx;
|
|
}
|
|
}
|
|
impl VsockChannel for TestBackend {
|
|
fn recv_pkt(&mut self, _pkt: &mut VsockPacket) -> Result<()> {
|
|
match self.rx_err.take() {
|
|
None => {
|
|
self.rx_ok_cnt += 1;
|
|
Ok(())
|
|
}
|
|
Some(e) => Err(e),
|
|
}
|
|
}
|
|
fn send_pkt(&mut self, _pkt: &VsockPacket) -> Result<()> {
|
|
match self.tx_err.take() {
|
|
None => {
|
|
self.tx_ok_cnt += 1;
|
|
Ok(())
|
|
}
|
|
Some(e) => Err(e),
|
|
}
|
|
}
|
|
fn has_pending_rx(&self) -> bool {
|
|
self.pending_rx
|
|
}
|
|
}
|
|
impl VsockEpollListener for TestBackend {
|
|
fn get_polled_fd(&self) -> RawFd {
|
|
self.evfd.as_raw_fd()
|
|
}
|
|
fn get_polled_evset(&self) -> epoll::Events {
|
|
epoll::Events::EPOLLIN
|
|
}
|
|
fn notify(&mut self, evset: epoll::Events) {
|
|
self.evset = Some(evset);
|
|
}
|
|
}
|
|
impl VsockBackend for TestBackend {}
|
|
|
|
pub struct TestContext {
|
|
pub cid: u64,
|
|
pub mem: GuestMemoryMmap,
|
|
pub mem_size: usize,
|
|
pub device: Vsock<TestBackend>,
|
|
}
|
|
|
|
impl TestContext {
|
|
pub fn new() -> Self {
|
|
const CID: u64 = 52;
|
|
const MEM_SIZE: usize = 1024 * 1024 * 128;
|
|
Self {
|
|
cid: CID,
|
|
mem: GuestMemoryMmap::new(&[(GuestAddress(0), MEM_SIZE)]).unwrap(),
|
|
mem_size: MEM_SIZE,
|
|
device: Vsock::new(CID, TestBackend::new(), false).unwrap(),
|
|
}
|
|
}
|
|
|
|
pub fn create_epoll_handler_context(&self) -> EpollHandlerContext {
|
|
const QSIZE: u16 = 2;
|
|
|
|
let guest_rxvq = GuestQ::new(GuestAddress(0x0010_0000), &self.mem, QSIZE as u16);
|
|
let guest_txvq = GuestQ::new(GuestAddress(0x0020_0000), &self.mem, QSIZE as u16);
|
|
let guest_evvq = GuestQ::new(GuestAddress(0x0030_0000), &self.mem, QSIZE as u16);
|
|
let rxvq = guest_rxvq.create_queue();
|
|
let txvq = guest_txvq.create_queue();
|
|
let evvq = guest_evvq.create_queue();
|
|
|
|
// Set up one available descriptor in the RX queue.
|
|
guest_rxvq.dtable[0].set(
|
|
0x0040_0000,
|
|
VSOCK_PKT_HDR_SIZE as u32,
|
|
VIRTQ_DESC_F_WRITE | VIRTQ_DESC_F_NEXT,
|
|
1,
|
|
);
|
|
guest_rxvq.dtable[1].set(0x0040_1000, 4096, VIRTQ_DESC_F_WRITE, 0);
|
|
guest_rxvq.avail.ring[0].set(0);
|
|
guest_rxvq.avail.idx.set(1);
|
|
|
|
// Set up one available descriptor in the TX queue.
|
|
guest_txvq.dtable[0].set(0x0050_0000, VSOCK_PKT_HDR_SIZE as u32, VIRTQ_DESC_F_NEXT, 1);
|
|
guest_txvq.dtable[1].set(0x0050_1000, 4096, 0, 0);
|
|
guest_txvq.avail.ring[0].set(0);
|
|
guest_txvq.avail.idx.set(1);
|
|
|
|
let queues = vec![rxvq, txvq, evvq];
|
|
let queue_evts = vec![
|
|
EventFd::new(EFD_NONBLOCK).unwrap(),
|
|
EventFd::new(EFD_NONBLOCK).unwrap(),
|
|
EventFd::new(EFD_NONBLOCK).unwrap(),
|
|
];
|
|
let interrupt_cb = Arc::new(Box::new(
|
|
move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()),
|
|
) as VirtioInterrupt);
|
|
|
|
EpollHandlerContext {
|
|
guest_rxvq,
|
|
guest_txvq,
|
|
guest_evvq,
|
|
handler: VsockEpollHandler {
|
|
mem: Arc::new(RwLock::new(self.mem.clone())),
|
|
queues,
|
|
queue_evts,
|
|
kill_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
|
|
pause_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
|
|
interrupt_cb,
|
|
backend: Arc::new(RwLock::new(TestBackend::new())),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct EpollHandlerContext<'a> {
|
|
pub handler: VsockEpollHandler<TestBackend>,
|
|
pub guest_rxvq: GuestQ<'a>,
|
|
pub guest_txvq: GuestQ<'a>,
|
|
pub guest_evvq: GuestQ<'a>,
|
|
}
|
|
|
|
impl<'a> EpollHandlerContext<'a> {
|
|
pub fn signal_txq_event(&mut self) {
|
|
self.handler.queue_evts[1].write(1).unwrap();
|
|
self.handler
|
|
.handle_event(
|
|
TX_QUEUE_EVENT,
|
|
epoll::Events::EPOLLIN,
|
|
Arc::new(AtomicBool::new(false)),
|
|
)
|
|
.unwrap();
|
|
}
|
|
pub fn signal_rxq_event(&mut self) {
|
|
self.handler.queue_evts[0].write(1).unwrap();
|
|
self.handler
|
|
.handle_event(
|
|
RX_QUEUE_EVENT,
|
|
epoll::Events::EPOLLIN,
|
|
Arc::new(AtomicBool::new(false)),
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|