mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vm-virtio, virtio-devices: Split device implementation from virt queues
Split the generic virtio code (queues and device type) from the VirtioDevice trait, transport and device implementations. This also simplifies the feature handling in vhost_user_backend as the vm-virtio crate is no longer has any features. Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,129 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
/// This module implements our vsock connection state machine. The heavy lifting is done by
|
||||
/// `connection::VsockConnection`, while this file only defines some constants and helper structs.
|
||||
///
|
||||
mod connection;
|
||||
mod txbuf;
|
||||
|
||||
pub use connection::VsockConnection;
|
||||
|
||||
pub mod defs {
|
||||
/// Vsock connection TX buffer capacity.
|
||||
pub const CONN_TX_BUF_SIZE: u32 = 64 * 1024;
|
||||
|
||||
/// When the guest thinks we have less than this amount of free buffer space,
|
||||
/// we will send them a credit update packet.
|
||||
pub const CONN_CREDIT_UPDATE_THRESHOLD: u32 = 4 * 1024;
|
||||
|
||||
/// Connection request timeout, in millis.
|
||||
pub const CONN_REQUEST_TIMEOUT_MS: u64 = 2000;
|
||||
|
||||
/// Connection graceful shutdown timeout, in millis.
|
||||
pub const CONN_SHUTDOWN_TIMEOUT_MS: u64 = 2000;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Attempted to push data to a full TX buffer.
|
||||
TxBufFull,
|
||||
/// An I/O error occurred, when attempting to flush the connection TX buffer.
|
||||
TxBufFlush(std::io::Error),
|
||||
/// An I/O error occurred, when attempting to write data to the host-side stream.
|
||||
StreamWrite(std::io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A vsock connection state.
|
||||
///
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ConnState {
|
||||
/// The connection has been initiated by the host end, but is yet to be confirmed by the guest.
|
||||
LocalInit,
|
||||
/// The connection has been initiated by the guest, but we are yet to confirm it, by sending
|
||||
/// a response packet (VSOCK_OP_RESPONSE).
|
||||
PeerInit,
|
||||
/// The connection handshake has been performed successfully, and data can now be exchanged.
|
||||
Established,
|
||||
/// The host (AF_UNIX) socket was closed.
|
||||
LocalClosed,
|
||||
/// A VSOCK_OP_SHUTDOWN packet was received from the guest. The tuple represents the guest R/W
|
||||
/// indication: (will_not_recv_anymore_data, will_not_send_anymore_data).
|
||||
PeerClosed(bool, bool),
|
||||
/// The connection is scheduled to be forcefully terminated as soon as possible.
|
||||
Killed,
|
||||
}
|
||||
|
||||
/// An RX indication, used by `VsockConnection` to schedule future `recv_pkt()` responses.
|
||||
/// For instance, after being notified that there is available data to be read from the host stream
|
||||
/// (via `notify()`), the connection will store a `PendingRx::Rw` to be later inspected by
|
||||
/// `recv_pkt()`.
|
||||
///
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum PendingRx {
|
||||
/// We need to yield a connection request packet (VSOCK_OP_REQUEST).
|
||||
Request = 0,
|
||||
/// We need to yield a connection response packet (VSOCK_OP_RESPONSE).
|
||||
Response = 1,
|
||||
/// We need to yield a forceful connection termination packet (VSOCK_OP_RST).
|
||||
Rst = 2,
|
||||
/// We need to yield a data packet (VSOCK_OP_RW), by reading from the AF_UNIX socket.
|
||||
Rw = 3,
|
||||
/// We need to yield a credit update packet (VSOCK_OP_CREDIT_UPDATE).
|
||||
CreditUpdate = 4,
|
||||
}
|
||||
impl PendingRx {
|
||||
/// Transform the enum value into a bitmask, that can be used for set operations.
|
||||
///
|
||||
fn into_mask(self) -> u16 {
|
||||
1u16 << (self as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of RX indications (`PendingRx` items).
|
||||
///
|
||||
struct PendingRxSet {
|
||||
data: u16,
|
||||
}
|
||||
|
||||
impl PendingRxSet {
|
||||
/// Insert an item into the set.
|
||||
///
|
||||
fn insert(&mut self, it: PendingRx) {
|
||||
self.data |= it.into_mask();
|
||||
}
|
||||
|
||||
/// Remove an item from the set and return:
|
||||
/// - true, if the item was in the set; or
|
||||
/// - false, if the item wasn't in the set.
|
||||
///
|
||||
fn remove(&mut self, it: PendingRx) -> bool {
|
||||
let ret = self.contains(it);
|
||||
self.data &= !it.into_mask();
|
||||
ret
|
||||
}
|
||||
|
||||
/// Check if an item is present in this set.
|
||||
///
|
||||
fn contains(&self, it: PendingRx) -> bool {
|
||||
self.data & it.into_mask() != 0
|
||||
}
|
||||
|
||||
/// Check if the set is empty.
|
||||
///
|
||||
fn is_empty(&self) -> bool {
|
||||
self.data == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a set containing only one item.
|
||||
///
|
||||
impl From<PendingRx> for PendingRxSet {
|
||||
fn from(it: PendingRx) -> Self {
|
||||
Self {
|
||||
data: it.into_mask(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::io::Write;
|
||||
use std::mem;
|
||||
use std::num::Wrapping;
|
||||
|
||||
use super::defs;
|
||||
use super::{Error, Result};
|
||||
|
||||
/// A simple ring-buffer implementation, used by vsock connections to buffer TX (guest -> host)
|
||||
/// data. Memory for this buffer is allocated lazily, since buffering will only be needed when
|
||||
/// the host can't read fast enough.
|
||||
///
|
||||
pub struct TxBuf {
|
||||
/// The actual u8 buffer - only allocated after the first push.
|
||||
data: Option<Box<[u8; Self::SIZE]>>,
|
||||
/// Ring-buffer head offset - where new data is pushed to.
|
||||
head: Wrapping<u32>,
|
||||
/// Ring-buffer tail offset - where data is flushed from.
|
||||
tail: Wrapping<u32>,
|
||||
}
|
||||
|
||||
impl TxBuf {
|
||||
/// Total buffer size, in bytes.
|
||||
///
|
||||
const SIZE: usize = defs::CONN_TX_BUF_SIZE as usize;
|
||||
|
||||
/// Ring-buffer constructor.
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: None,
|
||||
head: Wrapping(0),
|
||||
tail: Wrapping(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the used length of this buffer - number of bytes that have been pushed in, but not
|
||||
/// yet flushed out.
|
||||
///
|
||||
pub fn len(&self) -> usize {
|
||||
(self.head - self.tail).0 as usize
|
||||
}
|
||||
|
||||
/// Push a byte slice onto the ring-buffer.
|
||||
///
|
||||
/// Either the entire source slice will be pushed to the ring-buffer, or none of it, if
|
||||
/// there isn't enough room, in which case `Err(Error::TxBufFull)` is returned.
|
||||
///
|
||||
pub fn push(&mut self, src: &[u8]) -> Result<()> {
|
||||
// Error out if there's no room to push the entire slice.
|
||||
if self.len() + src.len() > Self::SIZE {
|
||||
return Err(Error::TxBufFull);
|
||||
}
|
||||
|
||||
// We're using a closure here to return the boxed slice, instead of a value (i.e.
|
||||
// `get_or_insert_with()` instead of `get_or_insert()`), because we only want the box
|
||||
// created when `self.data` is None. If we were to use `get_or_insert(box)`, the box
|
||||
// argument would always get evaluated (which implies a heap allocation), even though
|
||||
// it would later be discarded (when `self.data.is_some()`). Apparently, clippy fails
|
||||
// to see this, and insists on issuing some warning.
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let data = self.data.get_or_insert_with(||
|
||||
// Using uninitialized memory here is quite safe, since we never read from any
|
||||
// area of the buffer before writing to it. First we push, then we flush only
|
||||
// what had been prviously pushed.
|
||||
Box::new(unsafe {mem::MaybeUninit::<[u8; Self::SIZE]>::uninit().assume_init()}));
|
||||
|
||||
// Buffer head, as an offset into the data slice.
|
||||
let head_ofs = self.head.0 as usize % Self::SIZE;
|
||||
|
||||
// Pushing a slice to this buffer can take either one or two slice copies: - one copy,
|
||||
// if the slice fits between `head_ofs` and `Self::SIZE`; or - two copies, if the
|
||||
// ring-buffer head wraps around.
|
||||
|
||||
// First copy length: we can only go from the head offset up to the total buffer size.
|
||||
let len = std::cmp::min(Self::SIZE - head_ofs, src.len());
|
||||
data[head_ofs..(head_ofs + len)].copy_from_slice(&src[..len]);
|
||||
|
||||
// If the slice didn't fit, the buffer head will wrap around, and pushing continues
|
||||
// from the start of the buffer (`&self.data[0]`).
|
||||
if len < src.len() {
|
||||
data[..(src.len() - len)].copy_from_slice(&src[len..]);
|
||||
}
|
||||
|
||||
// Either way, we've just pushed exactly `src.len()` bytes, so that's the amount by
|
||||
// which the (wrapping) buffer head needs to move forward.
|
||||
self.head += Wrapping(src.len() as u32);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flush the contents of the ring-buffer to a writable stream.
|
||||
///
|
||||
/// Return the number of bytes that have been transferred out of the ring-buffer and into
|
||||
/// the writable stream.
|
||||
///
|
||||
pub fn flush_to<W>(&mut self, sink: &mut W) -> Result<usize>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
// Nothing to do, if this buffer holds no data.
|
||||
if self.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Buffer tail, as an offset into the buffer data slice.
|
||||
let tail_ofs = self.tail.0 as usize % Self::SIZE;
|
||||
|
||||
// Flushing the buffer can take either one or two writes:
|
||||
// - one write, if the tail doesn't need to wrap around to reach the head; or
|
||||
// - two writes, if the tail would wrap around: tail to slice end, then slice end to
|
||||
// head.
|
||||
|
||||
// First write length: the lesser of tail to slice end, or tail to head.
|
||||
let len_to_write = std::cmp::min(Self::SIZE - tail_ofs, self.len());
|
||||
|
||||
// It's safe to unwrap here, since we've already checked if the buffer was empty.
|
||||
let data = self.data.as_ref().unwrap();
|
||||
|
||||
// Issue the first write and absorb any `WouldBlock` error (we can just try again
|
||||
// later).
|
||||
let written = sink
|
||||
.write(&data[tail_ofs..(tail_ofs + len_to_write)])
|
||||
.map_err(Error::TxBufFlush)?;
|
||||
|
||||
// Move the buffer tail ahead by the amount (of bytes) we were able to flush out.
|
||||
self.tail += Wrapping(written as u32);
|
||||
|
||||
// If we weren't able to flush out as much as we tried, there's no point in attempting
|
||||
// our second write.
|
||||
if written < len_to_write {
|
||||
return Ok(written);
|
||||
}
|
||||
|
||||
// Attempt our second write. This will return immediately if a second write isn't
|
||||
// needed, since checking for an empty buffer is the first thing we do in this
|
||||
// function.
|
||||
//
|
||||
// Interesting corner case: if we've already written some data in the first pass,
|
||||
// and then the second write fails, we will consider the flush action a success
|
||||
// and return the number of bytes written in the first pass.
|
||||
Ok(written + self.flush_to(sink).unwrap_or(0))
|
||||
}
|
||||
|
||||
/// Check if the buffer holds any data that hasn't yet been flushed out.
|
||||
///
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Error as IoError;
|
||||
use std::io::Result as IoResult;
|
||||
use std::io::{ErrorKind, Write};
|
||||
|
||||
struct TestSink {
|
||||
data: Vec<u8>,
|
||||
err: Option<IoError>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl TestSink {
|
||||
const DEFAULT_CAPACITY: usize = 2 * TxBuf::SIZE;
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::with_capacity(Self::DEFAULT_CAPACITY),
|
||||
err: None,
|
||||
capacity: Self::DEFAULT_CAPACITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestSink {
|
||||
fn write(&mut self, src: &[u8]) -> IoResult<usize> {
|
||||
if self.err.is_some() {
|
||||
return Err(self.err.take().unwrap());
|
||||
}
|
||||
let len_to_push = std::cmp::min(self.capacity - self.data.len(), src.len());
|
||||
self.data.extend_from_slice(&src[..len_to_push]);
|
||||
Ok(len_to_push)
|
||||
}
|
||||
fn flush(&mut self) -> IoResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSink {
|
||||
fn clear(&mut self) {
|
||||
self.data = Vec::with_capacity(self.capacity);
|
||||
self.err = None;
|
||||
}
|
||||
fn set_err(&mut self, err: IoError) {
|
||||
self.err = Some(err);
|
||||
}
|
||||
fn set_capacity(&mut self, capacity: usize) {
|
||||
self.capacity = capacity;
|
||||
if self.data.len() > self.capacity {
|
||||
self.data.resize(self.capacity, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_nowrap() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
assert!(txbuf.is_empty());
|
||||
|
||||
assert!(txbuf.data.is_none());
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
txbuf.push(&[5, 6, 7, 8]).unwrap();
|
||||
txbuf.flush_to(&mut sink).unwrap();
|
||||
assert_eq!(sink.data, [1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_wrap() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
let mut tmp: Vec<u8> = Vec::new();
|
||||
|
||||
tmp.resize(TxBuf::SIZE - 2, 0);
|
||||
txbuf.push(tmp.as_slice()).unwrap();
|
||||
txbuf.flush_to(&mut sink).unwrap();
|
||||
sink.clear();
|
||||
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 4);
|
||||
assert_eq!(sink.data, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_error() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut tmp = Vec::with_capacity(TxBuf::SIZE);
|
||||
|
||||
tmp.resize(TxBuf::SIZE - 1, 0);
|
||||
txbuf.push(tmp.as_slice()).unwrap();
|
||||
match txbuf.push(&[1, 2]) {
|
||||
Err(Error::TxBufFull) => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incomplete_flush() {
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
|
||||
sink.set_capacity(2);
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 2);
|
||||
assert_eq!(txbuf.len(), 2);
|
||||
assert_eq!(sink.data, [1, 2]);
|
||||
|
||||
sink.set_capacity(4);
|
||||
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 2);
|
||||
assert!(txbuf.is_empty());
|
||||
assert_eq!(sink.data, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flush_error() {
|
||||
const EACCESS: i32 = 13;
|
||||
|
||||
let mut txbuf = TxBuf::new();
|
||||
let mut sink = TestSink::new();
|
||||
|
||||
txbuf.push(&[1, 2, 3, 4]).unwrap();
|
||||
let io_err = IoError::from_raw_os_error(EACCESS);
|
||||
sink.set_err(io_err);
|
||||
match txbuf.flush_to(&mut sink) {
|
||||
Err(Error::TxBufFlush(ref err)) if err.kind() == ErrorKind::PermissionDenied => (),
|
||||
other => panic!("Unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user