1 // Copyright 2020 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 //! Virtio console device. 6 7 pub mod control; 8 pub mod device; 9 pub mod input; 10 pub mod output; 11 pub mod port; 12 pub mod worker; 13 14 mod sys; 15 16 use std::collections::BTreeMap; 17 18 use anyhow::Context; 19 use base::RawDescriptor; 20 use hypervisor::ProtectionType; 21 use vm_memory::GuestMemory; 22 23 use crate::serial::sys::InStreamType; 24 use crate::virtio::console::device::ConsoleDevice; 25 use crate::virtio::console::device::ConsoleSnapshot; 26 use crate::virtio::console::port::ConsolePort; 27 use crate::virtio::DeviceType; 28 use crate::virtio::Interrupt; 29 use crate::virtio::Queue; 30 use crate::virtio::VirtioDevice; 31 use crate::PciAddress; 32 33 const QUEUE_SIZE: u16 = 256; 34 35 /// Virtio console device. 36 pub struct Console { 37 console: ConsoleDevice, 38 queue_sizes: Vec<u16>, 39 pci_address: Option<PciAddress>, 40 } 41 42 impl Console { new( protection_type: ProtectionType, input: Option<InStreamType>, output: Option<Box<dyn std::io::Write + Send>>, keep_rds: Vec<RawDescriptor>, pci_address: Option<PciAddress>, ) -> Console43 fn new( 44 protection_type: ProtectionType, 45 input: Option<InStreamType>, 46 output: Option<Box<dyn std::io::Write + Send>>, 47 keep_rds: Vec<RawDescriptor>, 48 pci_address: Option<PciAddress>, 49 ) -> Console { 50 let port = ConsolePort::new(input, output, None, keep_rds); 51 let console = ConsoleDevice::new_single_port(protection_type, port); 52 let queue_sizes = vec![QUEUE_SIZE; console.max_queues()]; 53 54 Console { 55 console, 56 queue_sizes, 57 pci_address, 58 } 59 } 60 } 61 62 impl VirtioDevice for Console { keep_rds(&self) -> Vec<RawDescriptor>63 fn keep_rds(&self) -> Vec<RawDescriptor> { 64 self.console.keep_rds() 65 } 66 features(&self) -> u6467 fn features(&self) -> u64 { 68 self.console.features() 69 } 70 device_type(&self) -> DeviceType71 fn device_type(&self) -> DeviceType { 72 DeviceType::Console 73 } 74 queue_max_sizes(&self) -> &[u16]75 fn queue_max_sizes(&self) -> &[u16] { 76 &self.queue_sizes 77 } 78 read_config(&self, offset: u64, data: &mut [u8])79 fn read_config(&self, offset: u64, data: &mut [u8]) { 80 self.console.read_config(offset, data); 81 } 82 on_device_sandboxed(&mut self)83 fn on_device_sandboxed(&mut self) { 84 self.console.start_input_threads(); 85 } 86 activate( &mut self, _mem: GuestMemory, _interrupt: Interrupt, queues: BTreeMap<usize, Queue>, ) -> anyhow::Result<()>87 fn activate( 88 &mut self, 89 _mem: GuestMemory, 90 _interrupt: Interrupt, 91 queues: BTreeMap<usize, Queue>, 92 ) -> anyhow::Result<()> { 93 for (idx, queue) in queues.into_iter() { 94 self.console.start_queue(idx, queue)? 95 } 96 Ok(()) 97 } 98 pci_address(&self) -> Option<PciAddress>99 fn pci_address(&self) -> Option<PciAddress> { 100 self.pci_address 101 } 102 reset(&mut self) -> anyhow::Result<()>103 fn reset(&mut self) -> anyhow::Result<()> { 104 self.console.reset() 105 } 106 virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>>107 fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> { 108 // Stop and collect all the queues. 109 let mut queues = BTreeMap::new(); 110 for idx in 0..self.console.max_queues() { 111 if let Some(queue) = self 112 .console 113 .stop_queue(idx) 114 .with_context(|| format!("failed to stop queue {idx}"))? 115 { 116 queues.insert(idx, queue); 117 } 118 } 119 120 if !queues.is_empty() { 121 Ok(Some(queues)) 122 } else { 123 Ok(None) 124 } 125 } 126 virtio_wake( &mut self, queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>, ) -> anyhow::Result<()>127 fn virtio_wake( 128 &mut self, 129 queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>, 130 ) -> anyhow::Result<()> { 131 if let Some((_mem, _interrupt, queues)) = queues_state { 132 for (idx, queue) in queues.into_iter() { 133 self.console.start_queue(idx, queue)?; 134 } 135 } 136 Ok(()) 137 } 138 virtio_snapshot(&mut self) -> anyhow::Result<serde_json::Value>139 fn virtio_snapshot(&mut self) -> anyhow::Result<serde_json::Value> { 140 let snap = self.console.snapshot()?; 141 serde_json::to_value(snap).context("failed to snapshot virtio console") 142 } 143 virtio_restore(&mut self, data: serde_json::Value) -> anyhow::Result<()>144 fn virtio_restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { 145 let snap: ConsoleSnapshot = 146 serde_json::from_value(data).context("failed to deserialize virtio console")?; 147 self.console.restore(&snap) 148 } 149 } 150 151 #[cfg(test)] 152 mod tests { 153 #[cfg(windows)] 154 use base::windows::named_pipes; 155 use tempfile::tempfile; 156 use vm_memory::GuestAddress; 157 158 use super::*; 159 use crate::suspendable_virtio_tests; 160 161 struct ConsoleContext { 162 #[cfg(windows)] 163 input_pipe_client: named_pipes::PipeConnection, 164 } 165 modify_device(_context: &mut ConsoleContext, b: &mut Console)166 fn modify_device(_context: &mut ConsoleContext, b: &mut Console) { 167 let input_buffer = b.console.ports[0].clone_input_buffer(); 168 input_buffer.lock().push_back(0); 169 } 170 171 #[cfg(any(target_os = "android", target_os = "linux"))] create_device() -> (ConsoleContext, Console)172 fn create_device() -> (ConsoleContext, Console) { 173 let input = Box::new(tempfile().unwrap()); 174 let output = Box::new(tempfile().unwrap()); 175 176 let console = Console::new( 177 hypervisor::ProtectionType::Unprotected, 178 Some(input), 179 Some(output), 180 Vec::new(), 181 None, 182 ); 183 184 let context = ConsoleContext {}; 185 (context, console) 186 } 187 188 #[cfg(windows)] create_device() -> (ConsoleContext, Console)189 fn create_device() -> (ConsoleContext, Console) { 190 let (input_pipe_server, input_pipe_client) = named_pipes::pair( 191 &named_pipes::FramingMode::Byte, 192 &named_pipes::BlockingMode::NoWait, 193 0, 194 ) 195 .unwrap(); 196 197 let input = Box::new(input_pipe_server); 198 let output = Box::new(tempfile().unwrap()); 199 200 let console = Console::new( 201 hypervisor::ProtectionType::Unprotected, 202 Some(input), 203 Some(output), 204 Vec::new(), 205 None, 206 ); 207 208 let context = ConsoleContext { input_pipe_client }; 209 210 (context, console) 211 } 212 213 suspendable_virtio_tests!(console, create_device, 2, modify_device); 214 215 #[test] test_inactive_sleep_resume()216 fn test_inactive_sleep_resume() { 217 let (_ctx, mut device) = create_device(); 218 219 let input_buffer = device.console.ports[0].clone_input_buffer(); 220 221 // Initialize the device, starting the input thread, but don't activate any queues. 222 device.on_device_sandboxed(); 223 224 // No queues were started, so `virtio_sleep()` should return `None`. 225 let sleep_result = device.virtio_sleep().expect("failed to sleep"); 226 assert!(sleep_result.is_none()); 227 228 // Inject some input data. 229 input_buffer.lock().extend(b"Hello".iter()); 230 231 // Ensure snapshot does not fail and contains the buffered input data. 232 let snapshot = device.virtio_snapshot().expect("failed to snapshot"); 233 234 let snapshot_input_buffer = snapshot 235 .get("ports") 236 .unwrap() 237 .get(0) 238 .unwrap() 239 .get("input_buffer") 240 .unwrap() 241 .as_array() 242 .unwrap(); 243 244 assert_eq!(snapshot_input_buffer.len(), b"Hello".len()); 245 assert_eq!(snapshot_input_buffer[0].as_i64(), Some(b'H' as i64)); 246 assert_eq!(snapshot_input_buffer[1].as_i64(), Some(b'e' as i64)); 247 assert_eq!(snapshot_input_buffer[2].as_i64(), Some(b'l' as i64)); 248 assert_eq!(snapshot_input_buffer[3].as_i64(), Some(b'l' as i64)); 249 assert_eq!(snapshot_input_buffer[4].as_i64(), Some(b'o' as i64)); 250 251 // Wake up the device, which should start the input thread again. 252 device.virtio_wake(None).expect("failed to wake"); 253 } 254 } 255