Skip to content

Commit

Permalink
feat(virtio): Adding Virtio-net Support to Cloudlet (#22)
Browse files Browse the repository at this point in the history
* feat(virtio): configure net device
- MMIO Device Discovery by the kernel command line
- irq allocator

Signed-off-by: sylvain-pierrot <[email protected]>

* feat: add simple handler

Signed-off-by: sylvain-pierrot <[email protected]>

* refactor code architecture about devices + add queue/handlers

Signed-off-by: sylvain-pierrot <[email protected]>

* chore: fix traits for generic + refactor code

Signed-off-by: sylvain-pierrot <[email protected]>

* feat: add working device

Signed-off-by: sylvain-pierrot <[email protected]>

* refactor: remove all unwrap

Signed-off-by: sylvain-pierrot <[email protected]>

* fix: cargo clippy

Signed-off-by: sylvain-pierrot <[email protected]>

* refactor: remove unnecessary comments

Signed-off-by: sylvain-pierrot <[email protected]>

---------

Signed-off-by: sylvain-pierrot <[email protected]>
  • Loading branch information
sylvain-pierrot authored Apr 29, 2024
1 parent 5b3ffbb commit b547afa
Show file tree
Hide file tree
Showing 23 changed files with 1,011 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
members = ["src/api", "src/vmm", "src/cli", "src/fs-gen", "src/agent"]
members = ["src/agent", "src/api", "src/cli", "src/fs-gen", "src/vmm"]
resolver = "2"
10 changes: 7 additions & 3 deletions src/vmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-version = "1.76.0"
clap = { version = "4.5.1", features = ["derive", "env"] }
clap-verbosity-flag = "2.2.0"
epoll = "4.3.3"
event-manager = { version = "0.4.0", features = ["remote_endpoint"] }
kvm-bindings = { version = "0.7.0", features = ["fam-wrappers"] }
kvm-ioctls = "0.16.0"
libc = "0.2.153"
Expand All @@ -21,15 +22,18 @@ log = "0.4.20"
nix = { version = "0.28.0", features = ["term"] }
openpty = "0.2.0"
prost = "0.11"
tokio = { version = "1.37.0", features = ["full"] }
tonic = "0.9"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
virtio-bindings = "0.2.2"
virtio-device = { git = "https://github.com/rust-vmm/vm-virtio.git" }
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio.git" }
vm-allocator = "0.1.0"
vm-device = "0.1.0"
vm-memory = { version = "0.14.0", features = ["backend-mmap"] }
vm-memory = { version = "0.14.1", features = ["backend-mmap"] }
vm-superio = "0.7.0"
vmm-sys-util = "0.12.1"
tokio = { version= "1.37.0", features= ["full"]}

[build-dependencies]
tonic-build = "0.9"
tonic-build = "0.9"
27 changes: 27 additions & 0 deletions src/vmm/src/core/cpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use std::sync::{Arc, Mutex};
use std::{io, process};
use std::{result, u64};
use tracing::{error, info, warn};
use vm_device::bus::MmioAddress;
use vm_device::device_manager::{IoManager, MmioManager};
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::terminal::Terminal;

Expand Down Expand Up @@ -73,6 +75,7 @@ pub(crate) struct Vcpu {
/// KVM file descriptor for a vCPU.
pub vcpu_fd: VcpuFd,

device_mgr: Arc<Mutex<IoManager>>,
serial: Arc<Mutex<LumperSerial<Stdout>>>,
slip_pty: Arc<Mutex<SlipPty>>,
}
Expand All @@ -82,12 +85,14 @@ impl Vcpu {
pub fn new(
vm_fd: &VmFd,
index: u64,
device_mgr: Arc<Mutex<IoManager>>,
serial: Arc<Mutex<LumperSerial<Stdout>>>,
slip_pty: Arc<Mutex<SlipPty>>,
) -> Result<Self> {
Ok(Vcpu {
index,
vcpu_fd: vm_fd.create_vcpu(index).map_err(Error::KvmIoctl)?,
device_mgr,
serial,
slip_pty,
})
Expand Down Expand Up @@ -308,6 +313,28 @@ impl Vcpu {
warn!(address = addr, "Unsupported device read at {:x?}", addr);
}
},
VcpuExit::MmioRead(addr, data) => {
if self
.device_mgr
.try_lock()
.unwrap()
.mmio_read(MmioAddress(addr), data)
.is_err()
{
error!("Failed to read from mmio addr={} data={:#?}", addr, data);
}
}
VcpuExit::MmioWrite(addr, data) => {
if self
.device_mgr
.try_lock()
.unwrap()
.mmio_write(MmioAddress(addr), data)
.is_err()
{
error!("Failed to write to mmio");
}
}
_ => {
error!(?exit_reason, "Unhandled VM-Exit");
}
Expand Down
1 change: 1 addition & 0 deletions src/vmm/src/core/devices/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::io;

pub(crate) mod serial;
pub(crate) mod virtio;

#[derive(Debug)]
/// Devices errors.
Expand Down
189 changes: 189 additions & 0 deletions src/vmm/src/core/devices/virtio/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
pub mod net;
mod register;

use event_manager::{
Error as EvmgrError, MutEventSubscriber, RemoteEndpoint, Result as EvmgrResult, SubscriberId,
};
use kvm_ioctls::{IoEventAddress, VmFd};
use libc::EFD_NONBLOCK;
use std::{
io,
sync::{
atomic::{AtomicU8, Ordering},
Arc, Mutex,
},
};
use virtio_device::VirtioConfig;
use virtio_queue::{Queue, QueueT};
use vm_device::bus::{self, MmioRange};
use vmm_sys_util::{errno, eventfd::EventFd};

// Device-independent virtio features.
mod features {
pub const VIRTIO_F_RING_EVENT_IDX: u64 = 29;
pub const VIRTIO_F_VERSION_1: u64 = 32;
}

// This bit is set on the device interrupt status when notifying the driver about used
// queue events.
// TODO: There seem to be similar semantics when the PCI transport is used with MSI-X cap
// disabled. Let's figure out at some point if having MMIO as part of the name is necessary.
const VIRTIO_MMIO_INT_VRING: u8 = 0x01;

// The driver will write to the register at this offset in the MMIO region to notify the device
// about available queue events.
const VIRTIO_MMIO_QUEUE_NOTIFY_OFFSET: u64 = 0x50;

// TODO: Make configurable for each device maybe?
const QUEUE_MAX_SIZE: u16 = 256;

#[derive(Debug)]
#[allow(dead_code)]
pub enum Error {
AlreadyActivated,
BadFeatures(u64),
Bus(bus::Error),
Cmdline(linux_loader::cmdline::Error),
Endpoint(EvmgrError),
EventFd(io::Error),
Overflow,
IoEvent,
QueuesNotValid,
RegisterIoevent(errno::Error),
RegisterIrqfd(errno::Error),
RegisterMmioDevice(bus::Error),
Conversion,
Mutex,
Net,
}

pub type Result<T> = std::result::Result<T, Error>;
pub type Subscriber = Arc<Mutex<dyn MutEventSubscriber + Send>>;

#[derive(Copy, Clone)]
pub struct MmioConfig {
pub range: MmioRange,
// The interrupt assigned to the device.
pub gsi: u32,
}

pub struct Config {
virtio: VirtioConfig<Queue>,
pub mmio: MmioConfig,
endpoint: RemoteEndpoint<Subscriber>,
vm_fd: Arc<VmFd>,
pub irqfd: Arc<EventFd>,
}

impl Config {
pub fn new(
virtio: VirtioConfig<Queue>,
mmio: MmioConfig,
endpoint: RemoteEndpoint<Subscriber>,
vm_fd: Arc<VmFd>,
) -> Result<Self> {
let irqfd = Arc::new(EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?);

// vm_fd
// .register_irqfd(&irqfd, mmio.gsi)
// .map_err(Error::RegisterIrqfd)?;

Ok(Self {
virtio,
mmio,
endpoint,
vm_fd,
irqfd,
})
}

// Perform common initial steps for device activation based on the configuration, and return
// a `Vec` that contains `EventFd`s registered as ioeventfds, which are used to convey queue
// notifications coming from the driver.
pub fn prepare_activate(&self) -> Result<Vec<EventFd>> {
if self.virtio.queues.iter().all(|queue| !queue.ready()) {
return Err(Error::QueuesNotValid);
}

if self.virtio.device_activated {
return Err(Error::AlreadyActivated);
}

// We do not support legacy drivers.
if self.virtio.driver_features & (1 << features::VIRTIO_F_VERSION_1) == 0 {
return Err(Error::BadFeatures(self.virtio.driver_features));
}

let mut ioevents = Vec::new();

// Right now, we operate under the assumption all queues are marked ready by the device
// (which is true until we start supporting devices that can optionally make use of
// additional queues on top of the defaults).
for i in 0..self.virtio.queues.len() {
let fd = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;

// Register the queue event fd.
self.vm_fd
.register_ioevent(
&fd,
&IoEventAddress::Mmio(
self.mmio.range.base().0 + VIRTIO_MMIO_QUEUE_NOTIFY_OFFSET,
),
// The maximum number of queues should fit within an `u16` according to the
// standard, so the conversion below is always expected to succeed.
u32::try_from(i).map_err(|_| Error::Conversion)?,
)
.map_err(Error::RegisterIoevent)?;

ioevents.push(fd);
}

Ok(ioevents)
}

// Perform the final steps of device activation based on the inner configuration and the
// provided subscriber that's going to handle the device queues. We'll extend this when
// we start support devices that make use of multiple handlers (i.e. for multiple queues).
pub fn finalize_activate(&mut self, handler: Subscriber) -> Result<()> {
// Register the queue handler with the `EventManager`. We could record the `sub_id`
// (and/or keep a handler clone) for further interaction (i.e. to remove the subscriber at
// a later time, retrieve state, etc).
let _sub_id = self
.endpoint
.call_blocking(move |mgr| -> EvmgrResult<SubscriberId> {
Ok(mgr.add_subscriber(handler))
})
.map_err(Error::Endpoint)?;

self.virtio.device_activated = true;

Ok(())
}
}

/// Simple trait to model the operation of signalling the driver about used events
/// for the specified queue.
// TODO: Does this need renaming to be relevant for packed queues as well?
pub trait SignalUsedQueue {
// TODO: Should this return an error? This failing is not really recoverable at the interface
// level so the expectation is the implementation handles that transparently somehow.
fn signal_used_queue(&self, index: u16);
}

/// Uses a single irqfd as the basis of signalling any queue (useful for the MMIO transport,
/// where a single interrupt is shared for everything).
pub struct SingleFdSignalQueue {
pub irqfd: Arc<EventFd>,
pub interrupt_status: Arc<AtomicU8>,
}

impl SignalUsedQueue for SingleFdSignalQueue {
fn signal_used_queue(&self, _index: u16) {
self.interrupt_status
.fetch_or(VIRTIO_MMIO_INT_VRING, Ordering::SeqCst);

self.irqfd
.write(1)
.expect("Failed write to eventfd when signalling queue");
}
}
Loading

0 comments on commit b547afa

Please sign in to comment.