• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&params, &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