From df5058ec0a67e435799019f9ac16ae1161c4cf3e Mon Sep 17 00:00:00 2001 From: fazlamehrab Date: Tue, 23 Jul 2019 12:18:20 -0700 Subject: [PATCH] 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 --- Cargo.lock | 35 ++++++++-- vm-virtio/src/console.rs | 134 ++++++++++++++++++++++++++++++++++----- vm-virtio/src/lib.rs | 1 + vmm/Cargo.toml | 1 + vmm/src/vm.rs | 58 ++++++++++++++--- 5 files changed, 200 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d020f7f8..e14708ed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/vm-virtio/src/console.rs b/vm-virtio/src/console.rs index 12d242975..d91ae6529 100755 --- a/vm-virtio/src/console.rs +++ b/vm-virtio/src/console.rs @@ -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, @@ -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, - avail_features: u64, - acked_features: u64, - input: Arc, - out: Option>, -} - /// Input device. pub struct ConsoleInput { input_evt: EventFd, + config_evt: EventFd, in_buffer: Arc>>, + config: Arc>, + 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, + avail_features: u64, + acked_features: u64, + config: Arc>, + input: Arc, + out: Option>, } impl Console { /// Create a new virtio console device that gets random data from /dev/urandom. - pub fn new(out: Option>) -> io::Result<(Console, Arc)> { - let avail_features = 1u64 << VIRTIO_F_VERSION_1; + pub fn new( + out: Option>, + cols: u16, + rows: u16, + ) -> io::Result<(Console, Arc)> { + 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, }; diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs index 72a2529fb..63c50d6cf 100755 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -143,4 +143,5 @@ pub enum Error { EpollCreateFd(io::Error), EpollCtl(io::Error), EpollWait(io::Error), + FailedSignalingDriver(io::Error), } diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 1a563d863..1dc4a70ea 100755 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -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" diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 6508227d3..a47f767f5 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -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>>, - console: Option>, + console_input: Option>, // i8042 device for exit i8042: Arc>, @@ -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) { + 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(())