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(())