devices: virtio: Add vhost-user-net master device

Add vhost-user virtio-net master device. Ctrl queue will be supported in a separate CL.

BUG=b:179755448
TEST=curl/ping worked with cloud-hypervisor's backend

Change-Id: Ibda3d93457be9841748b649e492d0fd11969fd4f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2717904
Tested-by: Keiichi Watanabe <keiichiw@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org>
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
This commit is contained in:
Keiichi Watanabe 2021-03-12 04:53:51 +09:00 committed by Commit Bot
parent f3a37f4953
commit 6068658584
6 changed files with 227 additions and 3 deletions

View file

@ -134,7 +134,7 @@ fn virtio_features_to_tap_offload(features: u64) -> c_uint {
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
#[repr(C)] #[repr(C)]
struct VirtioNetConfig { pub(crate) struct VirtioNetConfig {
mac: [u8; 6], mac: [u8; 6],
status: Le16, status: Le16,
max_vq_pairs: Le16, max_vq_pairs: Le16,

View file

@ -4,9 +4,11 @@
mod block; mod block;
mod handler; mod handler;
mod net;
mod worker; mod worker;
pub use self::block::*; pub use self::block::*;
pub use self::net::*;
use remain::sorted; use remain::sorted;
use thiserror::Error as ThisError; use thiserror::Error as ThisError;

View file

@ -0,0 +1,185 @@
// Copyright 2021 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 file.
use std::cell::RefCell;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::thread;
use std::u32;
use base::{error, Event, RawDescriptor};
use cros_async::Executor;
use virtio_sys::virtio_net;
use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::GuestMemory;
use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vmm_vhost::vhost_user::Master;
use crate::virtio::vhost::user::handler::VhostUserHandler;
use crate::virtio::vhost::user::worker::Worker;
use crate::virtio::vhost::user::Error;
use crate::virtio::{Interrupt, Queue, VirtioDevice, VirtioNetConfig, TYPE_NET};
type Result<T> = std::result::Result<T, Error>;
const QUEUE_SIZE: u16 = 256;
pub struct Net {
kill_evt: Option<Event>,
worker_thread: Option<thread::JoinHandle<Worker>>,
handler: RefCell<VhostUserHandler>,
queue_sizes: Vec<u16>,
}
impl Net {
pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Net> {
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
let vhost_user_net = Master::from_stream(socket, 16 /* # of queues */);
// TODO(b/182430355): Support VIRTIO_NET_F_CTRL_VQ and VIRTIO_NET_F_CTRL_GUEST_OFFLOADS.
let allow_features = 1 << crate::virtio::VIRTIO_F_VERSION_1
| 1 << virtio_net::VIRTIO_NET_F_CSUM
| 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
| 1 << virtio_net::VIRTIO_NET_F_MQ
| 1 << VIRTIO_RING_F_EVENT_IDX
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let allow_protocol_features =
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
let mut handler = VhostUserHandler::new(
vhost_user_net,
allow_features,
init_features,
allow_protocol_features,
)?;
let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 2 /* 1 rx + 1 tx */)?;
Ok(Net {
kill_evt: None,
worker_thread: None,
handler: RefCell::new(handler),
queue_sizes,
})
}
}
impl Drop for Net {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
if let Some(worker_thread) = self.worker_thread.take() {
let _ = worker_thread.join();
}
}
}
impl VirtioDevice for Net {
fn keep_rds(&self) -> Vec<RawDescriptor> {
Vec::new()
}
fn features(&self) -> u64 {
self.handler.borrow().avail_features
}
fn ack_features(&mut self, features: u64) {
if let Err(e) = self.handler.borrow_mut().ack_features(features) {
error!("failed to enable features 0x{:x}: {}", features, e);
}
}
fn device_type(&self) -> u32 {
TYPE_NET
}
fn queue_max_sizes(&self) -> &[u16] {
self.queue_sizes.as_slice()
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
if let Err(e) = self
.handler
.borrow_mut()
.read_config::<VirtioNetConfig>(offset, data)
{
error!("failed to read config: {}", e);
}
}
fn activate(
&mut self,
mem: GuestMemory,
interrupt: Interrupt,
queues: Vec<Queue>,
queue_evts: Vec<Event>,
) {
// TODO(b/182430355): Remove this check once ctrlq is supported.
if queues.len() % 2 != 0 {
error!(
"The number of queues must be an even number but {}",
queues.len()
);
}
if let Err(e) = self
.handler
.borrow_mut()
.activate(&mem, &interrupt, &queues, &queue_evts)
{
error!("failed to activate queues: {}", e);
return;
}
let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed creating kill Event pair: {}", e);
return;
}
};
self.kill_evt = Some(self_kill_evt);
let worker_result = thread::Builder::new()
.name("vhost_user_virtio_net".to_string())
.spawn(move || {
let ex = Executor::new().expect("failed to create an executor");
let mut worker = Worker {
queues,
mem,
kill_evt,
};
if let Err(e) = worker.run(&ex, interrupt) {
error!("failed to start a worker: {}", e);
}
worker
});
match worker_result {
Err(e) => {
error!("failed to spawn virtio_net worker: {}", e);
return;
}
Ok(join_handle) => {
self.worker_thread = Some(join_handle);
}
}
}
fn reset(&mut self) -> bool {
if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
error!("Failed to reset net device: {}", e);
false
} else {
true
}
}
}

View file

@ -241,6 +241,7 @@ pub struct Config {
pub gdb: Option<u32>, pub gdb: Option<u32>,
pub balloon_bias: i64, pub balloon_bias: i64,
pub vhost_user_blk: Vec<VhostUserOption>, pub vhost_user_blk: Vec<VhostUserOption>,
pub vhost_user_net: Vec<VhostUserOption>,
} }
impl Default for Config { impl Default for Config {
@ -306,6 +307,7 @@ impl Default for Config {
gdb: None, gdb: None,
balloon_bias: 0, balloon_bias: 0,
vhost_user_blk: Vec::new(), vhost_user_blk: Vec::new(),
vhost_user_net: Vec::new(),
} }
} }
} }

View file

@ -33,7 +33,9 @@ use libc::{self, c_int, gid_t, uid_t};
use acpi_tables::sdt::SDT; use acpi_tables::sdt::SDT;
use base::net::{UnixSeqpacket, UnixSeqpacketListener, UnlinkUnixSeqpacketListener}; use base::net::{UnixSeqpacket, UnixSeqpacketListener, UnlinkUnixSeqpacketListener};
use devices::virtio::vhost::user::{Block as VhostUserBlock, Error as VhostUserBlockError}; use devices::virtio::vhost::user::{
Block as VhostUserBlock, Error as VhostUserError, Net as VhostUserNet,
};
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use devices::virtio::EventDevice; use devices::virtio::EventDevice;
use devices::virtio::{self, Console, VirtioDevice}; use devices::virtio::{self, Console, VirtioDevice};
@ -192,7 +194,9 @@ pub enum Error {
Timer(base::Error), Timer(base::Error),
ValidateRawDescriptor(base::Error), ValidateRawDescriptor(base::Error),
VhostNetDeviceNew(virtio::vhost::Error), VhostNetDeviceNew(virtio::vhost::Error),
VhostUserBlockDeviceNew(VhostUserBlockError), VhostUserBlockDeviceNew(VhostUserError),
VhostUserNetDeviceNew(VhostUserError),
VhostUserNetWithNetArgs,
VhostVsockDeviceNew(virtio::vhost::Error), VhostVsockDeviceNew(virtio::vhost::Error),
VirtioPciDev(base::Error), VirtioPciDev(base::Error),
WaitContextAdd(base::Error), WaitContextAdd(base::Error),
@ -316,6 +320,15 @@ impl Display for Error {
VhostUserBlockDeviceNew(e) => { VhostUserBlockDeviceNew(e) => {
write!(f, "failed to set up vhost-user block device: {}", e) write!(f, "failed to set up vhost-user block device: {}", e)
} }
VhostUserNetDeviceNew(e) => {
write!(f, "failed to set up vhost-user net device: {}", e)
}
VhostUserNetWithNetArgs => {
write!(
f,
"vhost-user-net cannot be used with any of --host_ip, --netmask or --mac"
)
}
VhostVsockDeviceNew(e) => write!(f, "failed to set up virtual socket device: {}", e), VhostVsockDeviceNew(e) => write!(f, "failed to set up virtual socket device: {}", e),
VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", e), VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", e),
WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e), WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e),
@ -820,6 +833,17 @@ fn create_net_device(
}) })
} }
fn create_vhost_user_net_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
let dev = VhostUserNet::new(virtio::base_features(cfg.protected_vm), &opt.socket)
.map_err(Error::VhostUserNetDeviceNew)?;
Ok(VirtioDeviceStub {
dev: Box::new(dev),
// no sandbox here because virtqueue handling is exported to a different process.
jail: None,
})
}
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
fn create_gpu_device( fn create_gpu_device(
cfg: &Config, cfg: &Config,
@ -1425,9 +1449,16 @@ fn create_virtio_devices(
if let (Some(host_ip), Some(netmask), Some(mac_address)) = if let (Some(host_ip), Some(netmask), Some(mac_address)) =
(cfg.host_ip, cfg.netmask, cfg.mac_address) (cfg.host_ip, cfg.netmask, cfg.mac_address)
{ {
if !cfg.vhost_user_net.is_empty() {
return Err(Error::VhostUserNetWithNetArgs);
}
devs.push(create_net_device(cfg, host_ip, netmask, mac_address, mem)?); devs.push(create_net_device(cfg, host_ip, netmask, mac_address, mem)?);
} }
for net in &cfg.vhost_user_net {
devs.push(create_vhost_user_net_device(cfg, net)?);
}
#[cfg_attr(not(feature = "gpu"), allow(unused_mut))] #[cfg_attr(not(feature = "gpu"), allow(unused_mut))]
let mut resource_bridges = Vec::<virtio::resource_bridge::ResourceResponseSocket>::new(); let mut resource_bridges = Vec::<virtio::resource_bridge::ResourceResponseSocket>::new();

View file

@ -1588,6 +1588,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
"vhost-user-blk" => cfg.vhost_user_blk.push(VhostUserOption { "vhost-user-blk" => cfg.vhost_user_blk.push(VhostUserOption {
socket: PathBuf::from(value.unwrap()), socket: PathBuf::from(value.unwrap()),
}), }),
"vhost-user-net" => cfg.vhost_user_net.push(VhostUserOption {
socket: PathBuf::from(value.unwrap()),
}),
"help" => return Err(argument::Error::PrintHelp), "help" => return Err(argument::Error::PrintHelp),
_ => unreachable!(), _ => unreachable!(),
} }
@ -1798,6 +1801,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"), Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"),
Argument::value("balloon_bias_mib", "N", "Amount to bias balance of memory between host and guest as the balloon inflates, in MiB."), Argument::value("balloon_bias_mib", "N", "Amount to bias balance of memory between host and guest as the balloon inflates, in MiB."),
Argument::value("vhost-user-blk", "SOCKET_PATH", "Path to a socket for vhost-user block"), Argument::value("vhost-user-blk", "SOCKET_PATH", "Path to a socket for vhost-user block"),
Argument::value("vhost-user-net", "SOCKET_PATH", "Path to a socket for vhost-user net"),
Argument::short_flag('h', "help", "Print help message.")]; Argument::short_flag('h', "help", "Print help message.")];
let mut cfg = Config::default(); let mut cfg = Config::default();