xref: /aosp_15_r20/external/crosvm/devices/src/virtio/console.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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