devices/virtio/console/
device.rs

1// Copyright 2024 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 and vhost-user-console device shared backend implementation
6
7use base::RawDescriptor;
8use data_model::Le32;
9use hypervisor::ProtectionType;
10use serde::Deserialize;
11use serde::Serialize;
12use zerocopy::IntoBytes;
13
14use crate::virtio::base_features;
15use crate::virtio::console::port::ConsolePort;
16use crate::virtio::console::port::ConsolePortSnapshot;
17use crate::virtio::console::worker::WorkerHandle;
18use crate::virtio::console::worker::WorkerPort;
19use crate::virtio::copy_config;
20use crate::virtio::device_constants::console::virtio_console_config;
21use crate::virtio::device_constants::console::VIRTIO_CONSOLE_F_MULTIPORT;
22use crate::virtio::Queue;
23
24pub struct ConsoleDevice {
25    avail_features: u64,
26    pub(crate) ports: Vec<ConsolePort>,
27    worker: Option<WorkerHandle>,
28}
29
30#[derive(Serialize, Deserialize)]
31pub struct ConsoleSnapshot {
32    avail_features: u64,
33    pub(super) ports: Vec<ConsolePortSnapshot>,
34}
35
36impl ConsoleDevice {
37    /// Create a console device that does not support the multiport feature.
38    pub fn new_single_port(protection_type: ProtectionType, port: ConsolePort) -> ConsoleDevice {
39        ConsoleDevice {
40            avail_features: base_features(protection_type),
41            ports: vec![port],
42            worker: None,
43        }
44    }
45
46    /// Create a console device with the multiport feature enabled.
47    pub fn new_multi_port(
48        protection_type: ProtectionType,
49        ports: Vec<ConsolePort>,
50    ) -> ConsoleDevice {
51        // Port 0 must always exist.
52        assert!(!ports.is_empty());
53
54        let avail_features = base_features(protection_type) | (1 << VIRTIO_CONSOLE_F_MULTIPORT);
55
56        ConsoleDevice {
57            avail_features,
58            ports,
59            worker: None,
60        }
61    }
62
63    pub fn features(&self) -> u64 {
64        self.avail_features
65    }
66
67    pub fn max_ports(&self) -> usize {
68        self.ports.len()
69    }
70
71    /// Returns the maximum number of queues supported by this device.
72    pub fn max_queues(&self) -> usize {
73        // The port 0 receive and transmit queues always exist;
74        // other queues only exist if VIRTIO_CONSOLE_F_MULTIPORT is set.
75        let num_queues = self.ports.len().max(1);
76        if self.avail_features & (1 << VIRTIO_CONSOLE_F_MULTIPORT) != 0 {
77            // Each port has two queues (tx & rx), plus 2 for control receiveq and transmitq.
78            num_queues * 2 + 2
79        } else {
80            // port0 receiveq + transmitq
81            2
82        }
83    }
84
85    pub fn read_config(&self, offset: u64, data: &mut [u8]) {
86        let max_nr_ports = self.max_ports();
87        let config = virtio_console_config {
88            max_nr_ports: Le32::from(max_nr_ports as u32),
89            ..Default::default()
90        };
91        copy_config(data, 0, config.as_bytes(), offset);
92    }
93
94    pub fn keep_rds(&self) -> Vec<RawDescriptor> {
95        self.ports.iter().flat_map(ConsolePort::keep_rds).collect()
96    }
97
98    fn ensure_worker_started(&mut self) -> &mut WorkerHandle {
99        self.worker.get_or_insert_with(|| {
100            let ports = self
101                .ports
102                .iter_mut()
103                .map(WorkerPort::from_console_port)
104                .collect();
105            WorkerHandle::new(ports).expect("failed to create console worker")
106        })
107    }
108
109    fn ensure_worker_stopped(&mut self) {
110        if let Some(worker) = self.worker.take() {
111            let ports = worker.stop();
112            for (worker_port, port) in ports.into_iter().zip(self.ports.iter_mut()) {
113                worker_port.into_console_port(port);
114            }
115        }
116    }
117
118    pub fn start_queue(&mut self, idx: usize, queue: Queue) -> anyhow::Result<()> {
119        let worker = self.ensure_worker_started();
120        worker.start_queue(idx, queue)
121    }
122
123    pub fn stop_queue(&mut self, idx: usize) -> anyhow::Result<Option<Queue>> {
124        match self.worker.as_mut() {
125            Some(worker) => worker.stop_queue(idx),
126            None => Ok(None),
127        }
128    }
129
130    pub fn reset(&mut self) -> anyhow::Result<()> {
131        for idx in 0..self.max_queues() {
132            let _ = self.stop_queue(idx);
133        }
134        self.ensure_worker_stopped();
135        Ok(())
136    }
137
138    pub fn start_input_threads(&mut self) {
139        for port in self.ports.iter_mut() {
140            port.start_input_thread();
141        }
142    }
143
144    pub fn stop_input_threads(&mut self) {
145        for port in self.ports.iter_mut() {
146            port.stop_input_thread();
147        }
148    }
149
150    pub fn snapshot(&mut self) -> anyhow::Result<ConsoleSnapshot> {
151        let mut ports = Vec::new();
152        for port in &mut self.ports {
153            ports.push(port.snapshot());
154        }
155
156        Ok(ConsoleSnapshot {
157            avail_features: self.avail_features,
158            ports,
159        })
160    }
161
162    pub fn restore(&mut self, snap: &ConsoleSnapshot) -> anyhow::Result<()> {
163        anyhow::ensure!(
164            self.avail_features == snap.avail_features,
165            "Virtio console incorrect features for restore: Expected: {}, Actual: {}",
166            self.avail_features,
167            snap.avail_features,
168        );
169
170        for (port, port_snap) in self.ports.iter_mut().zip(snap.ports.iter()) {
171            port.restore(port_snap);
172        }
173
174        Ok(())
175    }
176}