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