blob: d07b6395eb954f85646af86fa12ebee22489017c [file] [log] [blame]
// 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 LICENSE file.
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use std::thread;
use byteorder::{ByteOrder, LittleEndian};
use ::vhost::Vsock as VhostVsockHandle;
use sys_util::{error, warn, EventFd, GuestMemory};
use virtio_sys::vhost;
use super::worker::Worker;
use super::{Error, Result};
use crate::virtio::{Queue, VirtioDevice, TYPE_VSOCK};
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 3;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
pub struct Vsock {
worker_kill_evt: Option<EventFd>,
kill_evt: Option<EventFd>,
vhost_handle: Option<VhostVsockHandle>,
cid: u64,
interrupt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
}
impl Vsock {
/// Create a new virtio-vsock device with the given VM cid.
pub fn new(cid: u64, mem: &GuestMemory) -> Result<Vsock> {
let kill_evt = EventFd::new().map_err(Error::CreateKillEventFd)?;
let handle = VhostVsockHandle::new(mem).map_err(Error::VhostOpen)?;
let avail_features = 1 << vhost::VIRTIO_F_NOTIFY_ON_EMPTY
| 1 << vhost::VIRTIO_RING_F_INDIRECT_DESC
| 1 << vhost::VIRTIO_RING_F_EVENT_IDX
| 1 << vhost::VHOST_F_LOG_ALL
| 1 << vhost::VIRTIO_F_ANY_LAYOUT
| 1 << vhost::VIRTIO_F_VERSION_1;
Ok(Vsock {
worker_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?),
kill_evt: Some(kill_evt),
vhost_handle: Some(handle),
cid,
interrupt: Some(EventFd::new().map_err(Error::VhostIrqCreate)?),
avail_features,
acked_features: 0,
})
}
pub fn new_for_testing(cid: u64, features: u64) -> Vsock {
Vsock {
worker_kill_evt: None,
kill_evt: None,
vhost_handle: None,
cid,
interrupt: None,
avail_features: features,
acked_features: 0,
}
}
pub fn acked_features(&self) -> u64 {
self.acked_features
}
}
impl Drop for Vsock {
fn drop(&mut self) {
// Only kill the child if it claimed its eventfd.
if self.worker_kill_evt.is_none() {
if let Some(kill_evt) = &self.kill_evt {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
}
}
}
impl VirtioDevice for Vsock {
fn keep_fds(&self) -> Vec<RawFd> {
let mut keep_fds = Vec::new();
if let Some(handle) = &self.vhost_handle {
keep_fds.push(handle.as_raw_fd());
}
if let Some(interrupt) = &self.interrupt {
keep_fds.push(interrupt.as_raw_fd());
}
if let Some(worker_kill_evt) = &self.worker_kill_evt {
keep_fds.push(worker_kill_evt.as_raw_fd());
}
keep_fds
}
fn device_type(&self) -> u32 {
TYPE_VSOCK
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.avail_features
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
match offset {
0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid),
0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffffffff) as u32),
4 if data.len() == 4 => {
LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffffffff) as u32)
}
_ => warn!(
"vsock: virtio-vsock received invalid read request of {} bytes at offset {}",
data.len(),
offset
),
}
}
fn ack_features(&mut self, value: u64) {
let mut v = value;
// Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("vsock: virtio-vsock got unknown feature ack: {:x}", v);
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn activate(
&mut self,
_: GuestMemory,
interrupt_evt: EventFd,
interrupt_resample_evt: EventFd,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
error!("net: expected {} queues, got {}", NUM_QUEUES, queues.len());
return;
}
if let Some(vhost_handle) = self.vhost_handle.take() {
if let Some(interrupt) = self.interrupt.take() {
if let Some(kill_evt) = self.worker_kill_evt.take() {
let acked_features = self.acked_features;
let cid = self.cid;
let worker_result = thread::Builder::new()
.name("vhost_vsock".to_string())
.spawn(move || {
// The third vq is an event-only vq that is not handled by the vhost
// subsystem (but still needs to exist). Split it off here.
let vhost_queues = queues[..2].to_vec();
let mut worker = Worker::new(
vhost_queues,
vhost_handle,
interrupt,
status,
interrupt_evt,
interrupt_resample_evt,
acked_features,
);
let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> {
handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?;
handle.start().map_err(Error::VhostVsockStart)?;
Ok(())
};
let result =
worker.run(queue_evts, QUEUE_SIZES, kill_evt, activate_vqs);
if let Err(e) = result {
error!("vsock worker thread exited with error: {:?}", e);
}
});
if let Err(e) = worker_result {
error!("failed to spawn vhost_vsock worker: {}", e);
return;
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use byteorder::{ByteOrder, LittleEndian};
#[test]
fn ack_features() {
let cid = 5;
let features: u64 = (1 << 20) | (1 << 49) | (1 << 2) | (1 << 19);
let mut acked_features: u64 = 0;
let mut unavailable_features: u64 = 0;
let mut vsock = Vsock::new_for_testing(cid, features);
assert_eq!(acked_features, vsock.acked_features());
acked_features |= 1 << 2;
vsock.ack_features(acked_features);
assert_eq!(acked_features, vsock.acked_features());
acked_features |= 1 << 49;
vsock.ack_features(acked_features);
assert_eq!(acked_features, vsock.acked_features());
acked_features |= 1 << 60;
unavailable_features |= 1 << 60;
vsock.ack_features(acked_features);
assert_eq!(
acked_features & !unavailable_features,
vsock.acked_features()
);
acked_features |= 1 << 1;
unavailable_features |= 1 << 1;
vsock.ack_features(acked_features);
assert_eq!(
acked_features & !unavailable_features,
vsock.acked_features()
);
}
#[test]
fn read_config() {
let cid = 0xfca9a559fdcb9756;
let vsock = Vsock::new_for_testing(cid, 0);
let mut buf = [0 as u8; 8];
vsock.read_config(0, &mut buf);
assert_eq!(cid, LittleEndian::read_u64(&buf));
vsock.read_config(0, &mut buf[..4]);
assert_eq!((cid & 0xffffffff) as u32, LittleEndian::read_u32(&buf[..4]));
vsock.read_config(4, &mut buf[..4]);
assert_eq!((cid >> 32) as u32, LittleEndian::read_u32(&buf[..4]));
let data: [u8; 8] = [8, 226, 5, 46, 159, 59, 89, 77];
buf.copy_from_slice(&data);
vsock.read_config(12, &mut buf);
assert_eq!(&buf, &data);
}
#[test]
fn features() {
let cid = 5;
let features: u64 = 0xfc195ae8db88cff9;
let vsock = Vsock::new_for_testing(cid, features);
assert_eq!(features, vsock.features());
}
}