Coverage Report

Created: 2025-12-31 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}