devices/virtio/
console.rs

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
7pub mod control;
8pub mod device;
9pub mod input;
10pub mod output;
11pub mod port;
12pub mod worker;
13
14mod sys;
15
16use std::collections::BTreeMap;
17
18use anyhow::Context;
19use base::RawDescriptor;
20use hypervisor::ProtectionType;
21use snapshot::AnySnapshot;
22use vm_memory::GuestMemory;
23
24use crate::serial::sys::InStreamType;
25use crate::virtio::console::device::ConsoleDevice;
26use crate::virtio::console::device::ConsoleSnapshot;
27use crate::virtio::console::port::ConsolePort;
28use crate::virtio::DeviceType;
29use crate::virtio::Interrupt;
30use crate::virtio::Queue;
31use crate::virtio::VirtioDevice;
32use crate::PciAddress;
33
34const QUEUE_SIZE: u16 = 256;
35
36/// Virtio console device.
37pub struct Console {
38    console: ConsoleDevice,
39    max_queue_sizes: Vec<u16>,
40    pci_address: Option<PciAddress>,
41}
42
43impl Console {
44    fn new(
45        protection_type: ProtectionType,
46        input: Option<InStreamType>,
47        output: Option<Box<dyn std::io::Write + Send>>,
48        keep_rds: Vec<RawDescriptor>,
49        pci_address: Option<PciAddress>,
50        max_queue_sizes: Option<Vec<u16>>,
51    ) -> Console {
52        let port = ConsolePort::new(input, output, None, keep_rds);
53        let console = ConsoleDevice::new_single_port(protection_type, port);
54        let max_queue_sizes =
55            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        assert_eq!(max_queue_sizes.len(), console.max_queues());
60        for qs in &max_queue_sizes {
61            assert!(qs.is_power_of_two());
62        }
63
64        Console {
65            console,
66            max_queue_sizes,
67            pci_address,
68        }
69    }
70}
71
72impl VirtioDevice for Console {
73    fn keep_rds(&self) -> Vec<RawDescriptor> {
74        self.console.keep_rds()
75    }
76
77    fn features(&self) -> u64 {
78        self.console.features()
79    }
80
81    fn device_type(&self) -> DeviceType {
82        DeviceType::Console
83    }
84
85    fn queue_max_sizes(&self) -> &[u16] {
86        &self.max_queue_sizes
87    }
88
89    fn read_config(&self, offset: u64, data: &mut [u8]) {
90        self.console.read_config(offset, data);
91    }
92
93    fn on_device_sandboxed(&mut self) {
94        self.console.start_input_threads();
95    }
96
97    fn activate(
98        &mut self,
99        _mem: GuestMemory,
100        _interrupt: Interrupt,
101        queues: BTreeMap<usize, Queue>,
102    ) -> anyhow::Result<()> {
103        for (idx, queue) in queues.into_iter() {
104            self.console.start_queue(idx, queue)?
105        }
106        Ok(())
107    }
108
109    fn pci_address(&self) -> Option<PciAddress> {
110        self.pci_address
111    }
112
113    fn reset(&mut self) -> anyhow::Result<()> {
114        self.console.reset()
115    }
116
117    fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
118        // Stop and collect all the queues.
119        let mut queues = BTreeMap::new();
120        for idx in 0..self.console.max_queues() {
121            if let Some(queue) = self
122                .console
123                .stop_queue(idx)
124                .with_context(|| format!("failed to stop queue {idx}"))?
125            {
126                queues.insert(idx, queue);
127            }
128        }
129
130        if !queues.is_empty() {
131            Ok(Some(queues))
132        } else {
133            Ok(None)
134        }
135    }
136
137    fn virtio_wake(
138        &mut self,
139        queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
140    ) -> anyhow::Result<()> {
141        if let Some((_mem, _interrupt, queues)) = queues_state {
142            for (idx, queue) in queues.into_iter() {
143                self.console.start_queue(idx, queue)?;
144            }
145        }
146        Ok(())
147    }
148
149    fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
150        let snap = self.console.snapshot()?;
151        AnySnapshot::to_any(snap).context("failed to snapshot virtio console")
152    }
153
154    fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
155        let snap: ConsoleSnapshot =
156            AnySnapshot::from_any(data).context("failed to deserialize virtio console")?;
157        self.console.restore(&snap)
158    }
159}
160
161#[cfg(test)]
162mod 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}