mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-06 02:25:23 +00:00
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:
parent
f3a37f4953
commit
6068658584
6 changed files with 227 additions and 3 deletions
|
@ -134,7 +134,7 @@ fn virtio_features_to_tap_offload(features: u64) -> c_uint {
|
|||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[repr(C)]
|
||||
struct VirtioNetConfig {
|
||||
pub(crate) struct VirtioNetConfig {
|
||||
mac: [u8; 6],
|
||||
status: Le16,
|
||||
max_vq_pairs: Le16,
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
mod block;
|
||||
mod handler;
|
||||
mod net;
|
||||
mod worker;
|
||||
|
||||
pub use self::block::*;
|
||||
pub use self::net::*;
|
||||
|
||||
use remain::sorted;
|
||||
use thiserror::Error as ThisError;
|
||||
|
|
185
devices/src/virtio/vhost/user/net.rs
Normal file
185
devices/src/virtio/vhost/user/net.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -241,6 +241,7 @@ pub struct Config {
|
|||
pub gdb: Option<u32>,
|
||||
pub balloon_bias: i64,
|
||||
pub vhost_user_blk: Vec<VhostUserOption>,
|
||||
pub vhost_user_net: Vec<VhostUserOption>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -306,6 +307,7 @@ impl Default for Config {
|
|||
gdb: None,
|
||||
balloon_bias: 0,
|
||||
vhost_user_blk: Vec::new(),
|
||||
vhost_user_net: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
src/linux.rs
35
src/linux.rs
|
@ -33,7 +33,9 @@ use libc::{self, c_int, gid_t, uid_t};
|
|||
use acpi_tables::sdt::SDT;
|
||||
|
||||
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")]
|
||||
use devices::virtio::EventDevice;
|
||||
use devices::virtio::{self, Console, VirtioDevice};
|
||||
|
@ -192,7 +194,9 @@ pub enum Error {
|
|||
Timer(base::Error),
|
||||
ValidateRawDescriptor(base::Error),
|
||||
VhostNetDeviceNew(virtio::vhost::Error),
|
||||
VhostUserBlockDeviceNew(VhostUserBlockError),
|
||||
VhostUserBlockDeviceNew(VhostUserError),
|
||||
VhostUserNetDeviceNew(VhostUserError),
|
||||
VhostUserNetWithNetArgs,
|
||||
VhostVsockDeviceNew(virtio::vhost::Error),
|
||||
VirtioPciDev(base::Error),
|
||||
WaitContextAdd(base::Error),
|
||||
|
@ -316,6 +320,15 @@ impl Display for Error {
|
|||
VhostUserBlockDeviceNew(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),
|
||||
VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", 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")]
|
||||
fn create_gpu_device(
|
||||
cfg: &Config,
|
||||
|
@ -1425,9 +1449,16 @@ fn create_virtio_devices(
|
|||
if let (Some(host_ip), Some(netmask), Some(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)?);
|
||||
}
|
||||
|
||||
for net in &cfg.vhost_user_net {
|
||||
devs.push(create_vhost_user_net_device(cfg, net)?);
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "gpu"), allow(unused_mut))]
|
||||
let mut resource_bridges = Vec::<virtio::resource_bridge::ResourceResponseSocket>::new();
|
||||
|
||||
|
|
|
@ -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 {
|
||||
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),
|
||||
_ => 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("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-net", "SOCKET_PATH", "Path to a socket for vhost-user net"),
|
||||
Argument::short_flag('h', "help", "Print help message.")];
|
||||
|
||||
let mut cfg = Config::default();
|
||||
|
|
Loading…
Reference in a new issue