1 // Copyright 2022 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::collections::VecDeque;
6 use std::io;
7 use std::sync::Arc;
8 use std::thread;
9 use std::time::Duration;
10
11 use base::error;
12 use base::named_pipes;
13 use base::Event;
14 use base::FileSync;
15 use base::RawDescriptor;
16 use base::WorkerThread;
17 use sync::Mutex;
18
19 use crate::serial_device::SerialInput;
20 use crate::serial_device::SerialOptions;
21 use crate::virtio::console::Console;
22 use crate::virtio::ProtectionType;
23 use crate::SerialDevice;
24
25 impl SerialDevice for Console {
new( protection_type: ProtectionType, _event: Event, _input: Option<Box<dyn SerialInput>>, out: Option<Box<dyn io::Write + Send>>, _sync: Option<Box<dyn FileSync + Send>>, options: SerialOptions, keep_rds: Vec<RawDescriptor>, ) -> Console26 fn new(
27 protection_type: ProtectionType,
28 _event: Event,
29 _input: Option<Box<dyn SerialInput>>,
30 out: Option<Box<dyn io::Write + Send>>,
31 // TODO(b/171331752): connect filesync functionality.
32 _sync: Option<Box<dyn FileSync + Send>>,
33 options: SerialOptions,
34 keep_rds: Vec<RawDescriptor>,
35 ) -> Console {
36 Console::new(protection_type, None, out, keep_rds, options.pci_address)
37 }
38
39 /// Constructs a console with named pipe as input/output connections.
new_with_pipe( protection_type: ProtectionType, _interrupt_evt: Event, pipe_in: named_pipes::PipeConnection, pipe_out: named_pipes::PipeConnection, options: SerialOptions, keep_rds: Vec<RawDescriptor>, ) -> Console40 fn new_with_pipe(
41 protection_type: ProtectionType,
42 _interrupt_evt: Event,
43 pipe_in: named_pipes::PipeConnection,
44 pipe_out: named_pipes::PipeConnection,
45 options: SerialOptions,
46 keep_rds: Vec<RawDescriptor>,
47 ) -> Console {
48 Console::new(
49 protection_type,
50 Some(Box::new(pipe_in)),
51 Some(Box::new(pipe_out)),
52 keep_rds,
53 options.pci_address,
54 )
55 }
56 }
57
58 /// Platform-specific function to add a delay for reading rx.
59 ///
60 /// We can't issue blocking reads here and overlapped I/O is
61 /// incompatible with the call site where writes to this pipe are being
62 /// made, so instead we issue a small wait to prevent us from hogging
63 /// the CPU. This 20ms delay while typing doesn't seem to be noticeable.
read_delay_if_needed()64 fn read_delay_if_needed() {
65 thread::sleep(Duration::from_millis(20));
66 }
67
is_a_fatal_input_error(e: &io::Error) -> bool68 fn is_a_fatal_input_error(e: &io::Error) -> bool {
69 !matches!(
70 e.kind(),
71 // Being interrupted is not an error.
72 io::ErrorKind::Interrupted |
73 // Ignore two kinds of errors on Windows.
74 // - ErrorKind::Other when reading a named pipe before a client connects.
75 // - ErrorKind::WouldBlock when reading a named pipe (we don't use non-blocking I/O).
76 io::ErrorKind::Other | io::ErrorKind::WouldBlock
77 )
78 // Everything else is a fatal input error.
79 }
80
81 /// Starts a thread that reads rx and sends the input back via the returned buffer.
82 ///
83 /// The caller should listen on `in_avail_evt` for events. When `in_avail_evt` signals that data
84 /// is available, the caller should lock the returned `Mutex` and read data out of the inner
85 /// `VecDeque`. The data should be removed from the beginning of the `VecDeque` as it is processed.
86 ///
87 /// # Arguments
88 ///
89 /// * `rx` - Data source that the reader thread will wait on to send data back to the buffer
90 /// * `in_avail_evt` - Event triggered by the thread when new input is available on the buffer
spawn_input_thread( mut rx: Box<named_pipes::PipeConnection>, in_avail_evt: Event, input_buffer: Arc<Mutex<VecDeque<u8>>>, ) -> WorkerThread<Box<named_pipes::PipeConnection>>91 pub(in crate::virtio::console) fn spawn_input_thread(
92 mut rx: Box<named_pipes::PipeConnection>,
93 in_avail_evt: Event,
94 input_buffer: Arc<Mutex<VecDeque<u8>>>,
95 ) -> WorkerThread<Box<named_pipes::PipeConnection>> {
96 WorkerThread::start("v_console_input", move |kill_evt| {
97 // If there is already data, signal immediately.
98 if !input_buffer.lock().is_empty() {
99 in_avail_evt.signal().unwrap();
100 }
101
102 match rx.wait_for_client_connection_overlapped_blocking(&kill_evt) {
103 Err(e) if e.kind() == io::ErrorKind::Interrupted => return rx,
104 Err(e) => panic!("failed to wait for client: {}", e),
105 Ok(()) => (),
106 }
107
108 read_input(&mut rx, &in_avail_evt, input_buffer, kill_evt);
109 rx
110 })
111 }
112
read_input( rx: &mut Box<named_pipes::PipeConnection>, thread_in_avail_evt: &Event, buffer: Arc<Mutex<VecDeque<u8>>>, kill_evt: Event, )113 fn read_input(
114 rx: &mut Box<named_pipes::PipeConnection>,
115 thread_in_avail_evt: &Event,
116 buffer: Arc<Mutex<VecDeque<u8>>>,
117 kill_evt: Event,
118 ) {
119 let buffer_max_size = 1 << 12;
120 let mut rx_buf = Vec::with_capacity(buffer_max_size);
121
122 let mut read_overlapped =
123 named_pipes::OverlappedWrapper::new(true).expect("failed to create OverlappedWrapper");
124 loop {
125 let size = rx
126 .get_available_byte_count()
127 .expect("failed to get available byte count") as usize;
128 // Clamp to [1, buffer capacity]. Need to read at least one byte so that the read call
129 // blocks until data is available.
130 let size = std::cmp::min(std::cmp::max(size, 1), buffer_max_size);
131 rx_buf.resize(size, Default::default());
132 let res = rx.read_overlapped_blocking(&mut rx_buf, &mut read_overlapped, &kill_evt);
133
134 match res {
135 Ok(()) => {
136 buffer.lock().extend(&rx_buf[..]);
137 thread_in_avail_evt.signal().unwrap();
138 }
139 Err(e) if e.kind() == io::ErrorKind::Interrupted => {
140 // Exit event triggered
141 break;
142 }
143 Err(e) => {
144 // Being interrupted is not an error, but everything else is.
145 if is_a_fatal_input_error(&e) {
146 error!(
147 "failed to read for bytes to queue into console device: {}",
148 e
149 );
150 break;
151 }
152 }
153 }
154
155 // Depending on the platform, a short sleep is needed here (ie. Windows).
156 read_delay_if_needed();
157 }
158 }
159