xref: /cloud-hypervisor/virtio-devices/src/console.rs (revision f67b3f79ea19c9a66e04074cbbf5d292f6529e43)
1 // Copyright 2019 Intel Corporation. All Rights Reserved.
2 // SPDX-License-Identifier: Apache-2.0
3 
4 use super::Error as DeviceError;
5 use super::{
6     ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, Queue, VirtioCommon,
7     VirtioDevice, VirtioDeviceType, VirtioInterruptType, EPOLL_HELPER_EVENT_LAST,
8     VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
9 };
10 use crate::seccomp_filters::Thread;
11 use crate::thread_helper::spawn_virtio_thread;
12 use crate::GuestMemoryMmap;
13 use crate::VirtioInterrupt;
14 use libc::{EFD_NONBLOCK, TIOCGWINSZ};
15 use seccompiler::SeccompAction;
16 use std::cmp;
17 use std::collections::VecDeque;
18 use std::fs::File;
19 use std::io;
20 use std::io::{Read, Write};
21 use std::os::unix::io::AsRawFd;
22 use std::result;
23 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
24 use std::sync::{Arc, Barrier, Mutex};
25 use versionize::{VersionMap, Versionize, VersionizeResult};
26 use versionize_derive::Versionize;
27 use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic};
28 use vm_migration::VersionMapped;
29 use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
30 use vmm_sys_util::eventfd::EventFd;
31 
32 const QUEUE_SIZE: u16 = 256;
33 const NUM_QUEUES: usize = 2;
34 const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
35 
36 // New descriptors are pending on the virtio queue.
37 const INPUT_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
38 const OUTPUT_QUEUE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2;
39 // Some input from the VMM is ready to be injected into the VM.
40 const INPUT_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3;
41 // Console configuration change event is triggered.
42 const CONFIG_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 4;
43 // File written to (input ready)
44 const FILE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 5;
45 // Console resized
46 const RESIZE_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 6;
47 
48 //Console size feature bit
49 const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
50 
51 #[derive(Copy, Clone, Debug, Versionize)]
52 #[repr(C, packed)]
53 pub struct VirtioConsoleConfig {
54     cols: u16,
55     rows: u16,
56     max_nr_ports: u32,
57     emerg_wr: u32,
58 }
59 
60 impl Default for VirtioConsoleConfig {
61     fn default() -> Self {
62         VirtioConsoleConfig {
63             cols: 0,
64             rows: 0,
65             max_nr_ports: 1,
66             emerg_wr: 0,
67         }
68     }
69 }
70 
71 // Safe because it only has data and has no implicit padding.
72 unsafe impl ByteValued for VirtioConsoleConfig {}
73 
74 struct ConsoleEpollHandler {
75     queues: Vec<Queue>,
76     mem: GuestMemoryAtomic<GuestMemoryMmap>,
77     interrupt_cb: Arc<dyn VirtioInterrupt>,
78     in_buffer: Arc<Mutex<VecDeque<u8>>>,
79     resizer: Arc<ConsoleResizer>,
80     endpoint: Endpoint,
81     input_queue_evt: EventFd,
82     output_queue_evt: EventFd,
83     input_evt: EventFd,
84     config_evt: EventFd,
85     resize_pipe: Option<File>,
86     kill_evt: EventFd,
87     pause_evt: EventFd,
88 }
89 
90 pub enum Endpoint {
91     File(File),
92     FilePair(File, File),
93     Null,
94 }
95 
96 impl Endpoint {
97     fn out_file(&self) -> Option<&File> {
98         match self {
99             Self::File(f) => Some(f),
100             Self::FilePair(f, _) => Some(f),
101             Self::Null => None,
102         }
103     }
104 
105     fn in_file(&self) -> Option<&File> {
106         match self {
107             Self::File(_) => None,
108             Self::FilePair(_, f) => Some(f),
109             Self::Null => None,
110         }
111     }
112 }
113 
114 impl Clone for Endpoint {
115     fn clone(&self) -> Self {
116         match self {
117             Self::File(f) => Self::File(f.try_clone().unwrap()),
118             Self::FilePair(f_out, f_in) => {
119                 Self::FilePair(f_out.try_clone().unwrap(), f_in.try_clone().unwrap())
120             }
121             Self::Null => Self::Null,
122         }
123     }
124 }
125 
126 impl ConsoleEpollHandler {
127     /*
128      * Each port of virtio console device has one receive
129      * queue. One or more empty buffers are placed by the
130      * driver in the receive queue for incoming data. Here,
131      * we place the input data to these empty buffers.
132      */
133     fn process_input_queue(&mut self) -> bool {
134         let mut in_buffer = self.in_buffer.lock().unwrap();
135         let recv_queue = &mut self.queues[0]; //receiveq
136         let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
137         let mut used_count = 0;
138 
139         if in_buffer.is_empty() {
140             return false;
141         }
142 
143         let mem = self.mem.memory();
144         for avail_desc in recv_queue.iter(&mem) {
145             let len = cmp::min(avail_desc.len as u32, in_buffer.len() as u32);
146             let source_slice = in_buffer.drain(..len as usize).collect::<Vec<u8>>();
147             if let Err(e) = mem.write_slice(&source_slice[..], avail_desc.addr) {
148                 error!("Failed to write slice: {:?}", e);
149                 recv_queue.go_to_previous_position();
150                 break;
151             }
152 
153             used_desc_heads[used_count] = (avail_desc.index, len);
154             used_count += 1;
155 
156             if in_buffer.is_empty() {
157                 break;
158             }
159         }
160 
161         for &(desc_index, len) in &used_desc_heads[..used_count] {
162             recv_queue.add_used(&mem, desc_index, len);
163         }
164 
165         used_count > 0
166     }
167 
168     /*
169      * Each port of virtio console device has one transmit
170      * queue. For outgoing data, characters are placed in
171      * the transmit queue by the driver. Therefore, here
172      * we read data from the transmit queue and flush them
173      * to the referenced address.
174      */
175     fn process_output_queue(&mut self) -> bool {
176         let trans_queue = &mut self.queues[1]; //transmitq
177         let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
178         let mut used_count = 0;
179 
180         let mem = self.mem.memory();
181         for avail_desc in trans_queue.iter(&mem) {
182             let len;
183             if let Some(ref mut out) = self.endpoint.out_file() {
184                 let _ = mem.write_to(avail_desc.addr, out, avail_desc.len as usize);
185                 let _ = out.flush();
186             }
187             len = avail_desc.len;
188             used_desc_heads[used_count] = (avail_desc.index, len);
189             used_count += 1;
190         }
191 
192         for &(desc_index, len) in &used_desc_heads[..used_count] {
193             trans_queue.add_used(&mem, desc_index, len);
194         }
195         used_count > 0
196     }
197 
198     fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
199         self.interrupt_cb
200             .trigger(&VirtioInterruptType::Queue, Some(&self.queues[0]))
201             .map_err(|e| {
202                 error!("Failed to signal used queue: {:?}", e);
203                 DeviceError::FailedSignalingUsedQueue(e)
204             })
205     }
206 
207     fn run(
208         &mut self,
209         paused: Arc<AtomicBool>,
210         paused_sync: Arc<Barrier>,
211     ) -> result::Result<(), EpollHelperError> {
212         let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?;
213         helper.add_event(self.input_queue_evt.as_raw_fd(), INPUT_QUEUE_EVENT)?;
214         helper.add_event(self.output_queue_evt.as_raw_fd(), OUTPUT_QUEUE_EVENT)?;
215         helper.add_event(self.input_evt.as_raw_fd(), INPUT_EVENT)?;
216         helper.add_event(self.config_evt.as_raw_fd(), CONFIG_EVENT)?;
217         if let Some(resize_pipe) = self.resize_pipe.as_ref() {
218             helper.add_event(resize_pipe.as_raw_fd(), RESIZE_EVENT)?;
219         }
220         if let Some(in_file) = self.endpoint.in_file() {
221             helper.add_event(in_file.as_raw_fd(), FILE_EVENT)?;
222         }
223         helper.run(paused, paused_sync, self)?;
224 
225         Ok(())
226     }
227 }
228 
229 impl EpollHelperHandler for ConsoleEpollHandler {
230     fn handle_event(&mut self, _helper: &mut EpollHelper, event: &epoll::Event) -> bool {
231         let ev_type = event.data as u16;
232         match ev_type {
233             INPUT_QUEUE_EVENT => {
234                 if let Err(e) = self.input_queue_evt.read() {
235                     error!("Failed to get queue event: {:?}", e);
236                     return true;
237                 } else if self.process_input_queue() {
238                     if let Err(e) = self.signal_used_queue() {
239                         error!("Failed to signal used queue: {:?}", e);
240                         return true;
241                     }
242                 }
243             }
244             OUTPUT_QUEUE_EVENT => {
245                 if let Err(e) = self.output_queue_evt.read() {
246                     error!("Failed to get queue event: {:?}", e);
247                     return true;
248                 } else {
249                     self.process_output_queue();
250                 }
251             }
252             INPUT_EVENT => {
253                 if let Err(e) = self.input_evt.read() {
254                     error!("Failed to get input event: {:?}", e);
255                     return true;
256                 } else if self.process_input_queue() {
257                     if let Err(e) = self.signal_used_queue() {
258                         error!("Failed to signal used queue: {:?}", e);
259                         return true;
260                     }
261                 }
262             }
263             CONFIG_EVENT => {
264                 if let Err(e) = self.config_evt.read() {
265                     error!("Failed to get config event: {:?}", e);
266                     return true;
267                 } else if let Err(e) = self
268                     .interrupt_cb
269                     .trigger(&VirtioInterruptType::Config, None)
270                 {
271                     error!("Failed to signal console driver: {:?}", e);
272                     return true;
273                 }
274             }
275             RESIZE_EVENT => {
276                 if let Err(e) = self.resize_pipe.as_ref().unwrap().read_exact(&mut [0]) {
277                     error!("Failed to get resize event: {:?}", e);
278                     return true;
279                 }
280 
281                 self.resizer.update_console_size();
282             }
283             FILE_EVENT => {
284                 let mut input = [0u8; 64];
285                 if let Some(ref mut in_file) = self.endpoint.in_file() {
286                     if let Ok(count) = in_file.read(&mut input) {
287                         let mut in_buffer = self.in_buffer.lock().unwrap();
288                         in_buffer.extend(&input[..count]);
289                     }
290 
291                     if self.process_input_queue() {
292                         if let Err(e) = self.signal_used_queue() {
293                             error!("Failed to signal used queue: {:?}", e);
294                             return true;
295                         }
296                     }
297                 }
298             }
299             _ => {
300                 error!("Unknown event for virtio-console");
301                 return true;
302             }
303         }
304         false
305     }
306 }
307 
308 /// Resize handler
309 pub struct ConsoleResizer {
310     config_evt: EventFd,
311     tty: Option<File>,
312     config: Arc<Mutex<VirtioConsoleConfig>>,
313     acked_features: AtomicU64,
314 }
315 
316 impl ConsoleResizer {
317     pub fn update_console_size(&self) {
318         if let Some(tty) = self.tty.as_ref() {
319             let (cols, rows) = get_win_size(tty);
320             self.config.lock().unwrap().update_console_size(cols, rows);
321             if self
322                 .acked_features
323                 .fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::AcqRel)
324                 != 0
325             {
326                 // Send the interrupt to the driver
327                 let _ = self.config_evt.write(1);
328             }
329         }
330     }
331 }
332 
333 impl VirtioConsoleConfig {
334     pub fn update_console_size(&mut self, cols: u16, rows: u16) {
335         self.cols = cols;
336         self.rows = rows;
337     }
338 }
339 
340 /// Virtio device for exposing console to the guest OS through virtio.
341 pub struct Console {
342     common: VirtioCommon,
343     id: String,
344     config: Arc<Mutex<VirtioConsoleConfig>>,
345     resizer: Arc<ConsoleResizer>,
346     resize_pipe: Option<File>,
347     endpoint: Endpoint,
348     seccomp_action: SeccompAction,
349     in_buffer: Arc<Mutex<VecDeque<u8>>>,
350     exit_evt: EventFd,
351 }
352 
353 #[derive(Versionize)]
354 pub struct ConsoleState {
355     avail_features: u64,
356     acked_features: u64,
357     config: VirtioConsoleConfig,
358     in_buffer: Vec<u8>,
359 }
360 
361 fn get_win_size(tty: &dyn AsRawFd) -> (u16, u16) {
362     #[repr(C)]
363     #[derive(Default)]
364     struct WindowSize {
365         rows: u16,
366         cols: u16,
367         xpixel: u16,
368         ypixel: u16,
369     }
370     let ws: WindowSize = WindowSize::default();
371 
372     unsafe {
373         libc::ioctl(tty.as_raw_fd(), TIOCGWINSZ, &ws);
374     }
375 
376     (ws.cols, ws.rows)
377 }
378 
379 impl VersionMapped for ConsoleState {}
380 
381 impl Console {
382     /// Create a new virtio console device that gets random data from /dev/urandom.
383     pub fn new(
384         id: String,
385         endpoint: Endpoint,
386         resize_pipe: Option<File>,
387         iommu: bool,
388         seccomp_action: SeccompAction,
389         exit_evt: EventFd,
390     ) -> io::Result<(Console, Arc<ConsoleResizer>)> {
391         let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
392 
393         if iommu {
394             avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
395         }
396 
397         let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
398         let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::default()));
399         let resizer = Arc::new(ConsoleResizer {
400             config_evt,
401             config: console_config.clone(),
402             tty: endpoint.out_file().as_ref().map(|t| t.try_clone().unwrap()),
403             acked_features: AtomicU64::new(0),
404         });
405 
406         resizer.update_console_size();
407 
408         Ok((
409             Console {
410                 common: VirtioCommon {
411                     device_type: VirtioDeviceType::Console as u32,
412                     queue_sizes: QUEUE_SIZES.to_vec(),
413                     avail_features,
414                     paused_sync: Some(Arc::new(Barrier::new(2))),
415                     min_queues: NUM_QUEUES as u16,
416                     ..Default::default()
417                 },
418                 id,
419                 config: console_config,
420                 resizer: resizer.clone(),
421                 resize_pipe,
422                 endpoint,
423                 seccomp_action,
424                 in_buffer: Arc::new(Mutex::new(VecDeque::new())),
425                 exit_evt,
426             },
427             resizer,
428         ))
429     }
430 
431     fn state(&self) -> ConsoleState {
432         ConsoleState {
433             avail_features: self.common.avail_features,
434             acked_features: self.common.acked_features,
435             config: *(self.config.lock().unwrap()),
436             in_buffer: self.in_buffer.lock().unwrap().clone().into(),
437         }
438     }
439 
440     fn set_state(&mut self, state: &ConsoleState) {
441         self.common.avail_features = state.avail_features;
442         self.common.acked_features = state.acked_features;
443         *(self.config.lock().unwrap()) = state.config;
444         *(self.in_buffer.lock().unwrap()) = state.in_buffer.clone().into();
445     }
446 }
447 
448 impl Drop for Console {
449     fn drop(&mut self) {
450         if let Some(kill_evt) = self.common.kill_evt.take() {
451             // Ignore the result because there is nothing we can do about it.
452             let _ = kill_evt.write(1);
453         }
454     }
455 }
456 
457 impl VirtioDevice for Console {
458     fn device_type(&self) -> u32 {
459         self.common.device_type
460     }
461 
462     fn queue_max_sizes(&self) -> &[u16] {
463         &self.common.queue_sizes
464     }
465 
466     fn features(&self) -> u64 {
467         self.common.avail_features
468     }
469 
470     fn ack_features(&mut self, value: u64) {
471         self.common.ack_features(value)
472     }
473 
474     fn read_config(&self, offset: u64, data: &mut [u8]) {
475         self.read_config_from_slice(self.config.lock().unwrap().as_slice(), offset, data);
476     }
477 
478     fn activate(
479         &mut self,
480         mem: GuestMemoryAtomic<GuestMemoryMmap>,
481         interrupt_cb: Arc<dyn VirtioInterrupt>,
482         queues: Vec<Queue>,
483         mut queue_evts: Vec<EventFd>,
484     ) -> ActivateResult {
485         self.common.activate(&queues, &queue_evts, &interrupt_cb)?;
486         self.resizer
487             .acked_features
488             .store(self.common.acked_features, Ordering::Relaxed);
489 
490         if self.common.feature_acked(VIRTIO_CONSOLE_F_SIZE) {
491             if let Err(e) = interrupt_cb.trigger(&VirtioInterruptType::Config, None) {
492                 error!("Failed to signal console driver: {:?}", e);
493             }
494         }
495 
496         let (kill_evt, pause_evt) = self.common.dup_eventfds();
497         let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
498 
499         let mut handler = ConsoleEpollHandler {
500             queues,
501             mem,
502             interrupt_cb,
503             in_buffer: self.in_buffer.clone(),
504             endpoint: self.endpoint.clone(),
505             input_queue_evt: queue_evts.remove(0),
506             output_queue_evt: queue_evts.remove(0),
507             input_evt,
508             config_evt: self.resizer.config_evt.try_clone().unwrap(),
509             resize_pipe: self.resize_pipe.as_ref().map(|p| p.try_clone().unwrap()),
510             resizer: Arc::clone(&self.resizer),
511             kill_evt,
512             pause_evt,
513         };
514 
515         let paused = self.common.paused.clone();
516         let paused_sync = self.common.paused_sync.clone();
517         let mut epoll_threads = Vec::new();
518 
519         spawn_virtio_thread(
520             &self.id,
521             &self.seccomp_action,
522             Thread::VirtioConsole,
523             &mut epoll_threads,
524             &self.exit_evt,
525             move || {
526                 if let Err(e) = handler.run(paused, paused_sync.unwrap()) {
527                     error!("Error running worker: {:?}", e);
528                 }
529             },
530         )?;
531 
532         self.common.epoll_threads = Some(epoll_threads);
533 
534         event!("virtio-device", "activated", "id", &self.id);
535         Ok(())
536     }
537 
538     fn reset(&mut self) -> Option<Arc<dyn VirtioInterrupt>> {
539         let result = self.common.reset();
540         event!("virtio-device", "reset", "id", &self.id);
541         result
542     }
543 }
544 
545 impl Pausable for Console {
546     fn pause(&mut self) -> result::Result<(), MigratableError> {
547         self.common.pause()
548     }
549 
550     fn resume(&mut self) -> result::Result<(), MigratableError> {
551         self.common.resume()
552     }
553 }
554 
555 impl Snapshottable for Console {
556     fn id(&self) -> String {
557         self.id.clone()
558     }
559 
560     fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
561         Snapshot::new_from_versioned_state(&self.id, &self.state())
562     }
563 
564     fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
565         self.set_state(&snapshot.to_versioned_state(&self.id)?);
566         Ok(())
567     }
568 }
569 impl Transportable for Console {}
570 impl Migratable for Console {}
571