vm-virtio: Implement console size config feature

One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.

During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.

Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
This commit is contained in:
fazlamehrab 2019-07-23 12:18:20 -07:00 committed by Sebastien Boeuf
parent d9a355f85a
commit df5058ec0a
5 changed files with 200 additions and 29 deletions

35
Cargo.lock generated
View File

@ -16,6 +16,11 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "arc-swap"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arch"
version = "0.1.0"
@ -723,12 +728,12 @@ name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.4.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -755,7 +760,7 @@ dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -890,6 +895,24 @@ dependencies = [
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "signal-hook"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "signal-hook-registry"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arc-swap 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smallvec"
version = "0.6.10"
@ -1150,6 +1173,7 @@ dependencies = [
"net_util 0.1.0",
"pci 0.1.0",
"qcow 0.1.0",
"signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"vfio 0.0.1",
"vm-allocator 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
@ -1206,6 +1230,7 @@ dependencies = [
[metadata]
"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arc-swap 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1507f9b80b3ef096751728cf3f43bb0111ec906e44f5d8587e02c10643b9a2cd"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
@ -1279,7 +1304,7 @@ dependencies = [
"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
"checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
@ -1301,6 +1326,8 @@ dependencies = [
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5626ac617da2f2d9c48af5515a21d5a480dbd151e01bb1c355e26a3e68113"
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
"checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68"
"checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum ssh2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dee822d619a700f98c4de3b5931f272ecc7cf2e924ceb2df47b61df4ae033a0c"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"

View File

@ -19,7 +19,8 @@ use super::{
VirtioInterruptType, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use vm_memory::{Bytes, GuestMemoryMmap};
use std::sync::atomic::{AtomicU64, Ordering};
use vm_memory::{ByteValued, Bytes, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
@ -33,6 +34,23 @@ const OUTPUT_QUEUE_EVENT: DeviceEventT = 1;
const INPUT_EVENT: DeviceEventT = 2;
// The device has been dropped.
const KILL_EVENT: DeviceEventT = 3;
// Console configuration change event is triggered.
const CONFIG_EVENT: DeviceEventT = 4;
//Console size feature bit
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct VirtioConsoleConfig {
cols: u16,
rows: u16,
max_nr_ports: u32,
emerg_wr: u32,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioConsoleConfig {}
struct ConsoleEpollHandler {
queues: Vec<Queue>,
@ -43,6 +61,7 @@ struct ConsoleEpollHandler {
input_queue_evt: EventFd,
output_queue_evt: EventFd,
input_evt: EventFd,
config_evt: EventFd,
kill_evt: EventFd,
}
@ -158,6 +177,14 @@ impl ConsoleEpollHandler {
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.config_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
@ -216,6 +243,17 @@ impl ConsoleEpollHandler {
}
}
}
CONFIG_EVENT => {
if let Err(e) = self.config_evt.read() {
error!("Failed to get config event: {:?}", e);
break 'epoll;
} else if let Err(e) =
(self.interrupt_cb)(&VirtioInterruptType::Config, None)
{
error!("Failed to signal console driver: {:?}", e);
}
}
KILL_EVENT => {
debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll;
@ -231,19 +269,13 @@ impl ConsoleEpollHandler {
}
}
/// Virtio device for exposing console to the guest OS through virtio.
pub struct Console {
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
input: Arc<ConsoleInput>,
out: Option<Box<io::Write + Send>>,
}
/// Input device.
pub struct ConsoleInput {
input_evt: EventFd,
config_evt: EventFd,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
config: Arc<Mutex<VirtioConsoleConfig>>,
acked_features: AtomicU64,
}
impl ConsoleInput {
@ -252,18 +284,64 @@ impl ConsoleInput {
in_buffer.extend(input);
let _ = self.input_evt.write(1);
}
pub fn update_console_size(&self, cols: u16, rows: u16) {
if self
.acked_features
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::SeqCst)
!= 0
{
self.config.lock().unwrap().update_console_size(cols, rows);
//Send the interrupt to the driver
let _ = self.config_evt.write(1);
}
}
}
impl VirtioConsoleConfig {
pub fn new(cols: u16, rows: u16) -> Self {
VirtioConsoleConfig {
cols,
rows,
max_nr_ports: 1u32,
emerg_wr: 0u32,
}
}
pub fn update_console_size(&mut self, cols: u16, rows: u16) {
self.cols = cols;
self.rows = rows;
}
}
/// Virtio device for exposing console to the guest OS through virtio.
pub struct Console {
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
config: Arc<Mutex<VirtioConsoleConfig>>,
input: Arc<ConsoleInput>,
out: Option<Box<io::Write + Send>>,
}
impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new(out: Option<Box<io::Write + Send>>) -> io::Result<(Console, Arc<ConsoleInput>)> {
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
pub fn new(
out: Option<Box<io::Write + Send>>,
cols: u16,
rows: u16,
) -> io::Result<(Console, Arc<ConsoleInput>)> {
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::new(cols, rows)));
let console_input = Arc::new(ConsoleInput {
input_evt,
config_evt,
in_buffer: Arc::new(Mutex::new(VecDeque::new())),
config: console_config.clone(),
acked_features: AtomicU64::new(0),
});
Ok((
@ -271,6 +349,7 @@ impl Console {
kill_evt: None,
avail_features,
acked_features: 0u64,
config: console_config,
input: console_input.clone(),
out,
},
@ -331,12 +410,24 @@ impl VirtioDevice for Console {
self.acked_features |= v;
}
fn read_config(&self, _offset: u64, _data: &mut [u8]) {
warn!("Device specific configuration is not defined yet");
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config = self.config.lock().unwrap();
let config_slice = config.as_slice();
let config_len = config_slice.len() as u64;
if offset >= config_len {
error!("Failed to read config space");
return;
}
if let Some(end) = offset.checked_add(data.len() as u64) {
// This write can't fail, offset and end are checked against config_len.
data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
.unwrap();
}
}
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
warn!("Device specific configuration is not defined yet");
warn!("No device specific configration requires write");
}
fn activate(
@ -365,6 +456,16 @@ impl VirtioDevice for Console {
};
self.kill_evt = Some(self_kill_evt);
self.input
.acked_features
.store(self.acked_features, Ordering::Relaxed);
if (self.acked_features & (1u64 << VIRTIO_CONSOLE_F_SIZE)) != 0 {
if let Err(e) = (interrupt_cb)(&VirtioInterruptType::Config, None) {
error!("Failed to signal console driver: {:?}", e);
}
}
if let Some(out) = self.out.take() {
let mut handler = ConsoleEpollHandler {
queues,
@ -375,6 +476,7 @@ impl VirtioDevice for Console {
input_queue_evt: queue_evts.remove(0),
output_queue_evt: queue_evts.remove(0),
input_evt: self.input.input_evt.try_clone().unwrap(),
config_evt: self.input.config_evt.try_clone().unwrap(),
kill_evt,
};

View File

@ -143,4 +143,5 @@ pub enum Error {
EpollCreateFd(io::Error),
EpollCtl(io::Error),
EpollWait(io::Error),
FailedSignalingDriver(io::Error),
}

View File

@ -19,6 +19,7 @@ vfio = { path = "../vfio" }
vm-virtio = { path = "../vm-virtio" }
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
signal-hook = "0.1.10"
[dependencies.linux-loader]
git = "https://github.com/rust-vmm/linux-loader"

View File

@ -16,6 +16,7 @@ extern crate kvm_ioctls;
extern crate libc;
extern crate linux_loader;
extern crate net_util;
extern crate signal_hook;
extern crate vfio;
extern crate vm_allocator;
extern crate vm_memory;
@ -31,13 +32,14 @@ use kvm_bindings::{
};
use kvm_ioctls::*;
use libc::O_TMPFILE;
use libc::{c_void, siginfo_t, EFD_NONBLOCK};
use libc::{c_void, siginfo_t, EFD_NONBLOCK, TIOCGWINSZ};
use linux_loader::loader::KernelLoader;
use net_util::Tap;
use pci::{
InterruptDelivery, InterruptParameters, PciConfigIo, PciDevice, PciInterruptPin, PciRoot,
};
use qcow::{self, ImageType, QcowFile};
use signal_hook::{iterator::Signals, SIGWINCH};
use std::ffi::CString;
use std::fs::{File, OpenOptions};
use std::io::{self, sink, stdout};
@ -491,6 +493,23 @@ impl UserIoapicIrq {
}
}
pub fn get_win_size() -> (u16, u16) {
#[repr(C)]
struct WS {
rows: u16,
cols: u16,
};
let ws: WS = WS {
rows: 0u16,
cols: 0u16,
};
unsafe {
libc::ioctl(0, TIOCGWINSZ, &ws);
}
(ws.cols, ws.rows)
}
impl devices::Interrupt for UserIoapicIrq {
fn deliver(&self) -> result::Result<(), io::Error> {
self.ioapic
@ -512,7 +531,7 @@ struct DeviceManager {
// Serial port on 0x3f8
serial: Option<Arc<Mutex<devices::legacy::Serial>>>,
console: Option<Arc<vm_virtio::ConsoleInput>>,
console_input: Option<Arc<vm_virtio::ConsoleInput>>,
// i8042 device for exit
i8042: Arc<Mutex<devices::legacy::I8042Device>>,
@ -604,9 +623,11 @@ impl DeviceManager {
ConsoleOutputMode::Null => Some(Box::new(sink())),
ConsoleOutputMode::Off => None,
};
let (col, row) = get_win_size();
let console = if console_writer.is_some() {
let (virtio_console_device, console) = vm_virtio::Console::new(console_writer)
.map_err(DeviceManagerError::CreateVirtioConsole)?;
let (virtio_console_device, console_input) =
vm_virtio::Console::new(console_writer, col, row)
.map_err(DeviceManagerError::CreateVirtioConsole)?;
DeviceManager::add_virtio_pci_device(
Box::new(virtio_console_device),
vm_info.memory.clone(),
@ -616,7 +637,7 @@ impl DeviceManager {
&mut buses,
&interrupt_info,
)?;
Some(console)
Some(console_input)
} else {
None
};
@ -638,7 +659,7 @@ impl DeviceManager {
io_bus,
mmio_bus,
serial,
console,
console_input: console,
i8042,
exit_evt,
ioapic,
@ -1447,7 +1468,7 @@ impl<'a> Vm<'a> {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
let epoll_fd = self.epoll.as_raw_fd();
if (self.devices.serial.is_some() || self.devices.console.is_some()) && self.on_tty {
if (self.devices.serial.is_some() || self.devices.console_input.is_some()) && self.on_tty {
io::stdin()
.lock()
.set_raw_mode()
@ -1503,11 +1524,11 @@ impl<'a> Vm<'a> {
.map_err(Error::Serial)?;
}
if self.devices.console.is_some()
if self.devices.console_input.is_some()
&& self.config.console.mode.input_enabled()
{
self.devices
.console
.console_input
.as_ref()
.unwrap()
.queue_input_bytes(&out[..count]);
@ -1530,6 +1551,15 @@ impl<'a> Vm<'a> {
Ok(())
}
fn os_signal_handler(signals: Signals, console_input_clone: Arc<vm_virtio::ConsoleInput>) {
for signal in signals.forever() {
if signal == SIGWINCH {
let (col, row) = get_win_size();
console_input_clone.update_console_size(col, row);
}
}
}
pub fn start(&mut self, entry_addr: GuestAddress) -> Result<()> {
self.devices.register_devices()?;
@ -1581,6 +1611,16 @@ impl<'a> Vm<'a> {
// Unblock all CPU threads.
vcpu_thread_barrier.wait();
if let Some(console_input) = &self.devices.console_input {
let console_input_clone = console_input.clone();
let signals = Signals::new(&[SIGWINCH]);
match signals {
Ok(sig) => {
thread::spawn(move || Vm::os_signal_handler(sig, console_input_clone));
}
Err(e) => error!("Signal not found {}", e),
}
}
self.control_loop()?;
Ok(())