vm-virtio: Implement reset() for virtio-console

The virtio specification defines a device can be reset, which was not
supported by this virtio-console implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind it
to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2019-10-04 09:38:43 -07:00 committed by Samuel Ortiz
parent dac7737919
commit 8225d4cd6e
2 changed files with 78 additions and 42 deletions

View File

@ -8,6 +8,7 @@ use std::cmp;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::ops::DerefMut;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::result; use std::result;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
@ -57,7 +58,7 @@ struct ConsoleEpollHandler {
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<VirtioInterrupt>,
in_buffer: Arc<Mutex<VecDeque<u8>>>, in_buffer: Arc<Mutex<VecDeque<u8>>>,
out: Box<dyn io::Write + Send>, out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
input_queue_evt: EventFd, input_queue_evt: EventFd,
output_queue_evt: EventFd, output_queue_evt: EventFd,
input_evt: EventFd, input_evt: EventFd,
@ -130,8 +131,13 @@ impl ConsoleEpollHandler {
let mem = self.mem.read().unwrap(); let mem = self.mem.read().unwrap();
for avail_desc in trans_queue.iter(&mem) { for avail_desc in trans_queue.iter(&mem) {
let len; let len;
let _ = mem.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize); let mut out = self.out.lock().unwrap();
let _ = self.out.flush(); let _ = mem.write_to(
avail_desc.addr,
&mut out.deref_mut(),
avail_desc.len as usize,
);
let _ = out.flush();
len = avail_desc.len; len = avail_desc.len;
used_desc_heads[used_count] = (avail_desc.index, len); used_desc_heads[used_count] = (avail_desc.index, len);
@ -321,13 +327,15 @@ pub struct Console {
acked_features: u64, acked_features: u64,
config: Arc<Mutex<VirtioConsoleConfig>>, config: Arc<Mutex<VirtioConsoleConfig>>,
input: Arc<ConsoleInput>, input: Arc<ConsoleInput>,
out: Option<Box<dyn io::Write + Send>>, out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
} }
impl Console { impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom. /// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new( pub fn new(
out: Option<Box<dyn io::Write + Send>>, out: Box<dyn io::Write + Send + Sync + 'static>,
cols: u16, cols: u16,
rows: u16, rows: u16,
) -> io::Result<(Console, Arc<ConsoleInput>)> { ) -> io::Result<(Console, Arc<ConsoleInput>)> {
@ -351,7 +359,9 @@ impl Console {
acked_features: 0u64, acked_features: 0u64,
config: console_config, config: console_config,
input: console_input.clone(), input: console_input.clone(),
out, out: Arc::new(Mutex::new(out)),
queue_evts: None,
interrupt_cb: None,
}, },
console_input, console_input,
)) ))
@ -456,6 +466,21 @@ impl VirtioDevice for Console {
}; };
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
// Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb.clone());
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
for queue_evt in queue_evts.iter() {
// Save the queue EventFD as we need to return it on reset
// but clone it to pass into the thread.
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate
})?);
}
self.queue_evts = Some(tmp_queue_evts);
self.input self.input
.acked_features .acked_features
.store(self.acked_features, Ordering::Relaxed); .store(self.acked_features, Ordering::Relaxed);
@ -466,31 +491,41 @@ impl VirtioDevice for Console {
} }
} }
if let Some(out) = self.out.take() { let mut handler = ConsoleEpollHandler {
let mut handler = ConsoleEpollHandler { queues,
queues, mem,
mem, interrupt_cb,
interrupt_cb, in_buffer: self.input.in_buffer.clone(),
in_buffer: self.input.in_buffer.clone(), out: self.out.clone(),
out, input_queue_evt: queue_evts.remove(0),
input_queue_evt: queue_evts.remove(0), output_queue_evt: queue_evts.remove(0),
output_queue_evt: queue_evts.remove(0), input_evt: self.input.input_evt.try_clone().unwrap(),
input_evt: self.input.input_evt.try_clone().unwrap(), config_evt: self.input.config_evt.try_clone().unwrap(),
config_evt: self.input.config_evt.try_clone().unwrap(), kill_evt,
kill_evt, };
};
let worker_result = thread::Builder::new() let worker_result = thread::Builder::new()
.name("virtio_console".to_string()) .name("virtio_console".to_string())
.spawn(move || handler.run()); .spawn(move || handler.run());
if let Err(e) = worker_result { if let Err(e) = worker_result {
error!("failed to spawn virtio_console worker: {}", e); error!("failed to spawn virtio_console worker: {}", e);
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
}
return Ok(());
} }
Err(ActivateError::BadActivate)
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
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);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
} }
} }

View File

@ -401,20 +401,21 @@ impl DeviceManager {
let mut virtio_devices: Vec<Box<dyn vm_virtio::VirtioDevice>> = Vec::new(); let mut virtio_devices: Vec<Box<dyn vm_virtio::VirtioDevice>> = Vec::new();
let console_writer: Option<Box<dyn io::Write + Send>> = match vm_info.vm_cfg.console.mode { // Create serial and virtio-console
ConsoleOutputMode::File => Some(Box::new( let console_writer: Option<Box<dyn io::Write + Send + Sync>> =
File::create(vm_info.vm_cfg.console.file.as_ref().unwrap()) match vm_info.vm_cfg.console.mode {
.map_err(DeviceManagerError::ConsoleOutputFileOpen)?, ConsoleOutputMode::File => Some(Box::new(
)), File::create(vm_info.vm_cfg.console.file.as_ref().unwrap())
ConsoleOutputMode::Tty => Some(Box::new(stdout())), .map_err(DeviceManagerError::ConsoleOutputFileOpen)?,
ConsoleOutputMode::Null => Some(Box::new(sink())), )),
ConsoleOutputMode::Off => None, ConsoleOutputMode::Tty => Some(Box::new(stdout())),
}; ConsoleOutputMode::Null => Some(Box::new(sink())),
ConsoleOutputMode::Off => None,
};
let (col, row) = get_win_size(); let (col, row) = get_win_size();
let console_input = if console_writer.is_some() { let console_input = if let Some(writer) = console_writer {
let (virtio_console_device, console_input) = let (virtio_console_device, console_input) = vm_virtio::Console::new(writer, col, row)
vm_virtio::Console::new(console_writer, col, row) .map_err(DeviceManagerError::CreateVirtioConsole)?;
.map_err(DeviceManagerError::CreateVirtioConsole)?;
virtio_devices virtio_devices
.push(Box::new(virtio_console_device) as Box<dyn vm_virtio::VirtioDevice>); .push(Box::new(virtio_console_device) as Box<dyn vm_virtio::VirtioDevice>);
Some(console_input) Some(console_input)