1 // Copyright 2021 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 use std::path::PathBuf;
6
7 use anyhow::anyhow;
8 use anyhow::bail;
9 use anyhow::Context;
10 use argh::FromArgs;
11 use base::error;
12 use base::Event;
13 use base::RawDescriptor;
14 use base::Terminal;
15 use cros_async::Executor;
16 use hypervisor::ProtectionType;
17 use vm_memory::GuestMemory;
18 use vmm_vhost::message::VhostUserProtocolFeatures;
19 use vmm_vhost::message::VhostUserVirtioFeatures;
20 use vmm_vhost::VhostUserSlaveReqHandler;
21 use zerocopy::AsBytes;
22
23 use crate::virtio;
24 use crate::virtio::console::asynchronous::ConsoleDevice;
25 use crate::virtio::console::virtio_console_config;
26 use crate::virtio::copy_config;
27 use crate::virtio::vhost::user::device::handler::sys::Doorbell;
28 use crate::virtio::vhost::user::device::handler::DeviceRequestHandler;
29 use crate::virtio::vhost::user::device::handler::VhostUserBackend;
30 use crate::virtio::vhost::user::device::handler::VhostUserPlatformOps;
31 use crate::virtio::vhost::user::device::listener::sys::VhostUserListener;
32 use crate::virtio::vhost::user::device::listener::VhostUserListenerTrait;
33 use crate::virtio::vhost::user::device::VhostUserDevice;
34 use crate::SerialHardware;
35 use crate::SerialParameters;
36 use crate::SerialType;
37
38 const MAX_QUEUE_NUM: usize = 2 /* transmit and receive queues */;
39
40 /// Console device for use with vhost-user. Will set stdin back to canon mode if we are getting
41 /// input from it.
42 pub struct VhostUserConsoleDevice {
43 console: ConsoleDevice,
44 /// Whether we should set stdin to raw mode because we are getting user input from there.
45 raw_stdin: bool,
46 }
47
48 impl Drop for VhostUserConsoleDevice {
drop(&mut self)49 fn drop(&mut self) {
50 if self.raw_stdin {
51 // Restore terminal capabilities back to what they were before
52 match std::io::stdin().set_canon_mode() {
53 Ok(()) => (),
54 Err(e) => error!("failed to restore canonical mode for terminal: {:#}", e),
55 }
56 }
57 }
58 }
59
60 impl VhostUserDevice for VhostUserConsoleDevice {
max_queue_num(&self) -> usize61 fn max_queue_num(&self) -> usize {
62 MAX_QUEUE_NUM
63 }
64
into_req_handler( self: Box<Self>, ops: Box<dyn VhostUserPlatformOps>, ex: &Executor, ) -> anyhow::Result<Box<dyn VhostUserSlaveReqHandler>>65 fn into_req_handler(
66 self: Box<Self>,
67 ops: Box<dyn VhostUserPlatformOps>,
68 ex: &Executor,
69 ) -> anyhow::Result<Box<dyn VhostUserSlaveReqHandler>> {
70 if self.raw_stdin {
71 // Set stdin() to raw mode so we can send over individual keystrokes unbuffered
72 std::io::stdin()
73 .set_raw_mode()
74 .context("failed to set terminal in raw mode")?;
75 }
76
77 let backend = ConsoleBackend {
78 device: *self,
79 acked_features: 0,
80 acked_protocol_features: VhostUserProtocolFeatures::empty(),
81 ex: ex.clone(),
82 };
83
84 let handler = DeviceRequestHandler::new(Box::new(backend), ops);
85 Ok(Box::new(std::sync::Mutex::new(handler)))
86 }
87 }
88
89 struct ConsoleBackend {
90 device: VhostUserConsoleDevice,
91 acked_features: u64,
92 acked_protocol_features: VhostUserProtocolFeatures,
93 ex: Executor,
94 }
95
96 impl VhostUserBackend for ConsoleBackend {
max_queue_num(&self) -> usize97 fn max_queue_num(&self) -> usize {
98 self.device.max_queue_num()
99 }
100
features(&self) -> u64101 fn features(&self) -> u64 {
102 self.device.console.avail_features() | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
103 }
104
ack_features(&mut self, value: u64) -> anyhow::Result<()>105 fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
106 let unrequested_features = value & !self.features();
107 if unrequested_features != 0 {
108 bail!("invalid features are given: {:#x}", unrequested_features);
109 }
110
111 self.acked_features |= value;
112
113 Ok(())
114 }
115
acked_features(&self) -> u64116 fn acked_features(&self) -> u64 {
117 self.acked_features
118 }
119
protocol_features(&self) -> VhostUserProtocolFeatures120 fn protocol_features(&self) -> VhostUserProtocolFeatures {
121 VhostUserProtocolFeatures::CONFIG
122 }
123
ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()>124 fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
125 let features = VhostUserProtocolFeatures::from_bits(features)
126 .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
127 let supported = self.protocol_features();
128 self.acked_protocol_features = features & supported;
129 Ok(())
130 }
131
acked_protocol_features(&self) -> u64132 fn acked_protocol_features(&self) -> u64 {
133 self.acked_protocol_features.bits()
134 }
135
read_config(&self, offset: u64, data: &mut [u8])136 fn read_config(&self, offset: u64, data: &mut [u8]) {
137 let config = virtio_console_config {
138 max_nr_ports: 1.into(),
139 ..Default::default()
140 };
141 copy_config(data, 0, config.as_bytes(), offset);
142 }
143
reset(&mut self)144 fn reset(&mut self) {
145 for queue_num in 0..self.max_queue_num() {
146 self.stop_queue(queue_num);
147 }
148 }
149
start_queue( &mut self, idx: usize, queue: virtio::Queue, mem: GuestMemory, doorbell: Doorbell, kick_evt: Event, ) -> anyhow::Result<()>150 fn start_queue(
151 &mut self,
152 idx: usize,
153 queue: virtio::Queue,
154 mem: GuestMemory,
155 doorbell: Doorbell,
156 kick_evt: Event,
157 ) -> anyhow::Result<()> {
158 match idx {
159 // ReceiveQueue
160 0 => self
161 .device
162 .console
163 .start_receive_queue(&self.ex, mem, queue, doorbell, kick_evt),
164 // TransmitQueue
165 1 => self
166 .device
167 .console
168 .start_transmit_queue(&self.ex, mem, queue, doorbell, kick_evt),
169 _ => bail!("attempted to start unknown queue: {}", idx),
170 }
171 }
172
stop_queue(&mut self, idx: usize)173 fn stop_queue(&mut self, idx: usize) {
174 match idx {
175 0 => {
176 if let Err(e) = self.device.console.stop_receive_queue() {
177 error!("error while stopping rx queue: {}", e);
178 }
179 }
180 1 => {
181 if let Err(e) = self.device.console.stop_transmit_queue() {
182 error!("error while stopping tx queue: {}", e);
183 }
184 }
185 _ => error!("attempted to stop unknown queue: {}", idx),
186 };
187 }
188 }
189
190 #[derive(FromArgs)]
191 #[argh(subcommand, name = "console")]
192 /// Console device
193 pub struct Options {
194 #[argh(option, arg_name = "PATH")]
195 /// path to a vhost-user socket
196 socket: Option<String>,
197 #[argh(option, arg_name = "STRING")]
198 /// VFIO-PCI device name (e.g. '0000:00:07.0')
199 vfio: Option<String>,
200 #[argh(option, arg_name = "OUTFILE")]
201 /// path to a file
202 output_file: Option<PathBuf>,
203 #[argh(option, arg_name = "INFILE")]
204 /// path to a file
205 input_file: Option<PathBuf>,
206 /// whether we are logging to syslog or not
207 #[argh(switch)]
208 syslog: bool,
209 }
210
211 /// Return a new vhost-user console device. `params` are the device's configuration, and `keep_rds`
212 /// is a vector into which `RawDescriptors` that need to survive a fork are added, in case the
213 /// device is meant to run within a child process.
create_vu_console_device( params: &SerialParameters, keep_rds: &mut Vec<RawDescriptor>, ) -> anyhow::Result<VhostUserConsoleDevice>214 pub fn create_vu_console_device(
215 params: &SerialParameters,
216 keep_rds: &mut Vec<RawDescriptor>,
217 ) -> anyhow::Result<VhostUserConsoleDevice> {
218 let device = params.create_serial_device::<ConsoleDevice>(
219 ProtectionType::Unprotected,
220 // We need to pass an event as per Serial Device API but we don't really use it anyway.
221 &Event::new()?,
222 keep_rds,
223 )?;
224
225 Ok(VhostUserConsoleDevice {
226 console: device,
227 raw_stdin: params.stdin,
228 })
229 }
230
231 /// Starts a vhost-user console device.
232 /// Returns an error if the given `args` is invalid or the device fails to run.
run_console_device(opts: Options) -> anyhow::Result<()>233 pub fn run_console_device(opts: Options) -> anyhow::Result<()> {
234 let type_ = match opts.output_file {
235 Some(_) => {
236 if opts.syslog {
237 bail!("--output-file and --syslog options cannot be used together.");
238 }
239 SerialType::File
240 }
241 None => {
242 if opts.syslog {
243 SerialType::Syslog
244 } else {
245 SerialType::Stdout
246 }
247 }
248 };
249
250 let params = SerialParameters {
251 type_,
252 hardware: SerialHardware::VirtioConsole,
253 // Required only if type_ is SerialType::File or SerialType::UnixSocket
254 path: opts.output_file,
255 input: opts.input_file,
256 num: 1,
257 console: true,
258 earlycon: false,
259 // We don't use stdin if syslog mode is enabled
260 stdin: !opts.syslog,
261 out_timestamp: false,
262 ..Default::default()
263 };
264
265 // We won't jail the device and can simply ignore `keep_rds`.
266 let device = Box::new(create_vu_console_device(¶ms, &mut Vec::new())?);
267 let ex = Executor::new().context("Failed to create executor")?;
268
269 let listener = VhostUserListener::new_from_socket_or_vfio(
270 &opts.socket,
271 &opts.vfio,
272 device.max_queue_num(),
273 None,
274 )?;
275
276 listener.run_device(ex, device)
277 }
278