// Copyright 2020 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Virtio console device. pub mod control; pub mod device; pub mod input; pub mod output; pub mod port; pub mod worker; mod sys; use std::collections::BTreeMap; use anyhow::Context; use base::RawDescriptor; use hypervisor::ProtectionType; use snapshot::AnySnapshot; use vm_memory::GuestMemory; use crate::serial::sys::InStreamType; use crate::virtio::console::device::ConsoleDevice; use crate::virtio::console::device::ConsoleSnapshot; use crate::virtio::console::port::ConsolePort; use crate::virtio::DeviceType; use crate::virtio::Interrupt; use crate::virtio::Queue; use crate::virtio::VirtioDevice; use crate::PciAddress; const QUEUE_SIZE: u16 = 256; /// Virtio console device. pub struct Console { console: ConsoleDevice, queue_sizes: Vec, pci_address: Option, } impl Console { fn new( protection_type: ProtectionType, input: Option, output: Option>, keep_rds: Vec, pci_address: Option, ) -> Console { let port = ConsolePort::new(input, output, None, keep_rds); let console = ConsoleDevice::new_single_port(protection_type, port); let queue_sizes = vec![QUEUE_SIZE; console.max_queues()]; Console { console, queue_sizes, pci_address, } } } impl VirtioDevice for Console { fn keep_rds(&self) -> Vec { self.console.keep_rds() } fn features(&self) -> u64 { self.console.features() } fn device_type(&self) -> DeviceType { DeviceType::Console } fn queue_max_sizes(&self) -> &[u16] { &self.queue_sizes } fn read_config(&self, offset: u64, data: &mut [u8]) { self.console.read_config(offset, data); } fn on_device_sandboxed(&mut self) { self.console.start_input_threads(); } fn activate( &mut self, _mem: GuestMemory, _interrupt: Interrupt, queues: BTreeMap, ) -> anyhow::Result<()> { for (idx, queue) in queues.into_iter() { self.console.start_queue(idx, queue)? } Ok(()) } fn pci_address(&self) -> Option { self.pci_address } fn reset(&mut self) -> anyhow::Result<()> { self.console.reset() } fn virtio_sleep(&mut self) -> anyhow::Result>> { // Stop and collect all the queues. let mut queues = BTreeMap::new(); for idx in 0..self.console.max_queues() { if let Some(queue) = self .console .stop_queue(idx) .with_context(|| format!("failed to stop queue {idx}"))? { queues.insert(idx, queue); } } if !queues.is_empty() { Ok(Some(queues)) } else { Ok(None) } } fn virtio_wake( &mut self, queues_state: Option<(GuestMemory, Interrupt, BTreeMap)>, ) -> anyhow::Result<()> { if let Some((_mem, _interrupt, queues)) = queues_state { for (idx, queue) in queues.into_iter() { self.console.start_queue(idx, queue)?; } } Ok(()) } fn virtio_snapshot(&mut self) -> anyhow::Result { let snap = self.console.snapshot()?; AnySnapshot::to_any(snap).context("failed to snapshot virtio console") } fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> { let snap: ConsoleSnapshot = AnySnapshot::from_any(data).context("failed to deserialize virtio console")?; self.console.restore(&snap) } } #[cfg(test)] mod tests { #[cfg(windows)] use base::windows::named_pipes; use tempfile::tempfile; use super::*; use crate::suspendable_virtio_tests; struct ConsoleContext { #[cfg(windows)] input_pipe_client: named_pipes::PipeConnection, } fn modify_device(_context: &mut ConsoleContext, b: &mut Console) { let input_buffer = b.console.ports[0].clone_input_buffer(); input_buffer.lock().push_back(0); } #[cfg(any(target_os = "android", target_os = "linux"))] fn create_device() -> (ConsoleContext, Console) { let input = Box::new(tempfile().unwrap()); let output = Box::new(tempfile().unwrap()); let console = Console::new( hypervisor::ProtectionType::Unprotected, Some(input), Some(output), Vec::new(), None, ); let context = ConsoleContext {}; (context, console) } #[cfg(windows)] fn create_device() -> (ConsoleContext, Console) { let (input_pipe_server, input_pipe_client) = named_pipes::pair( &named_pipes::FramingMode::Byte, &named_pipes::BlockingMode::NoWait, 0, ) .unwrap(); let input = Box::new(input_pipe_server); let output = Box::new(tempfile().unwrap()); let console = Console::new( hypervisor::ProtectionType::Unprotected, Some(input), Some(output), Vec::new(), None, ); let context = ConsoleContext { input_pipe_client }; (context, console) } suspendable_virtio_tests!(console, create_device, 2, modify_device); #[test] fn test_inactive_sleep_resume() { let (_ctx, mut device) = create_device(); let input_buffer = device.console.ports[0].clone_input_buffer(); // Initialize the device, starting the input thread, but don't activate any queues. device.on_device_sandboxed(); // No queues were started, so `virtio_sleep()` should return `None`. let sleep_result = device.virtio_sleep().expect("failed to sleep"); assert!(sleep_result.is_none()); // Inject some input data. input_buffer.lock().extend(b"Hello".iter()); // Ensure snapshot does not fail and contains the buffered input data. let snapshot = device.virtio_snapshot().expect("failed to snapshot"); let snapshot: ConsoleSnapshot = AnySnapshot::from_any(snapshot).expect("failed to deserialize snapshot"); assert_eq!(snapshot.ports[0].input_buffer, b"Hello"); // Wake up the device, which should start the input thread again. device.virtio_wake(None).expect("failed to wake"); } }