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::time::Duration;
9 use std::time::Instant;
10
11 use anyhow::Context;
12 use base::error;
13 use base::Event;
14 use base::EventToken;
15 use base::FileSync;
16 use base::RawDescriptor;
17 use base::WaitContext;
18 use base::WorkerThread;
19 use sync::Mutex;
20
21 use crate::serial::sys::InStreamType;
22 use crate::serial_device::SerialInput;
23 use crate::serial_device::SerialOptions;
24 use crate::virtio::console::device::ConsoleDevice;
25 use crate::virtio::console::port::ConsolePort;
26 use crate::virtio::console::port::ConsolePortInfo;
27 use crate::virtio::console::Console;
28 use crate::virtio::ProtectionType;
29 use crate::SerialDevice;
30
31 impl SerialDevice for Console {
new( protection_type: ProtectionType, _event: Event, input: Option<Box<dyn SerialInput>>, output: Option<Box<dyn io::Write + Send>>, _sync: Option<Box<dyn FileSync + Send>>, options: SerialOptions, keep_rds: Vec<RawDescriptor>, ) -> Console32 fn new(
33 protection_type: ProtectionType,
34 _event: Event,
35 input: Option<Box<dyn SerialInput>>,
36 output: Option<Box<dyn io::Write + Send>>,
37 // TODO(b/171331752): connect filesync functionality.
38 _sync: Option<Box<dyn FileSync + Send>>,
39 options: SerialOptions,
40 keep_rds: Vec<RawDescriptor>,
41 ) -> Console {
42 Console::new(
43 protection_type,
44 input,
45 output,
46 keep_rds,
47 options.pci_address,
48 )
49 }
50 }
51
52 impl SerialDevice for ConsoleDevice {
new( protection_type: ProtectionType, _event: Event, input: Option<Box<dyn SerialInput>>, output: Option<Box<dyn io::Write + Send>>, _sync: Option<Box<dyn FileSync + Send>>, options: SerialOptions, keep_rds: Vec<RawDescriptor>, ) -> ConsoleDevice53 fn new(
54 protection_type: ProtectionType,
55 _event: Event,
56 input: Option<Box<dyn SerialInput>>,
57 output: Option<Box<dyn io::Write + Send>>,
58 _sync: Option<Box<dyn FileSync + Send>>,
59 options: SerialOptions,
60 keep_rds: Vec<RawDescriptor>,
61 ) -> ConsoleDevice {
62 let info = ConsolePortInfo {
63 name: options.name,
64 console: options.console,
65 };
66 let port = ConsolePort::new(input, output, Some(info), keep_rds);
67 ConsoleDevice::new_single_port(protection_type, port)
68 }
69 }
70
71 impl SerialDevice for ConsolePort {
new( _protection_type: ProtectionType, _event: Event, input: Option<Box<dyn SerialInput>>, output: Option<Box<dyn io::Write + Send>>, _sync: Option<Box<dyn FileSync + Send>>, options: SerialOptions, keep_rds: Vec<RawDescriptor>, ) -> ConsolePort72 fn new(
73 _protection_type: ProtectionType,
74 _event: Event,
75 input: Option<Box<dyn SerialInput>>,
76 output: Option<Box<dyn io::Write + Send>>,
77 // TODO(b/171331752): connect filesync functionality.
78 _sync: Option<Box<dyn FileSync + Send>>,
79 options: SerialOptions,
80 keep_rds: Vec<RawDescriptor>,
81 ) -> ConsolePort {
82 let info = ConsolePortInfo {
83 name: options.name,
84 console: options.console,
85 };
86 ConsolePort::new(input, output, Some(info), keep_rds)
87 }
88 }
89
90 /// Starts a thread that reads input and sends the input back via the provided buffer.
91 ///
92 /// The caller should listen on `in_avail_evt` for events. When `in_avail_evt` signals that data
93 /// is available, the caller should lock `input_buffer` and read data out of the inner
94 /// `VecDeque`. The data should be removed from the beginning of the `VecDeque` as it is processed.
95 ///
96 /// # Arguments
97 ///
98 /// * `input` - Data source that the reader thread will wait on to send data back to the buffer
99 /// * `in_avail_evt` - Event triggered by the thread when new input is available on the buffer
spawn_input_thread( mut input: InStreamType, in_avail_evt: Event, input_buffer: Arc<Mutex<VecDeque<u8>>>, ) -> WorkerThread<InStreamType>100 pub(in crate::virtio::console) fn spawn_input_thread(
101 mut input: InStreamType,
102 in_avail_evt: Event,
103 input_buffer: Arc<Mutex<VecDeque<u8>>>,
104 ) -> WorkerThread<InStreamType> {
105 WorkerThread::start("v_console_input", move |kill_evt| {
106 // If there is already data, signal immediately.
107 if !input_buffer.lock().is_empty() {
108 in_avail_evt.signal().unwrap();
109 }
110 if let Err(e) = read_input(&mut input, &in_avail_evt, input_buffer, kill_evt) {
111 error!("console input thread exited with error: {:#}", e);
112 }
113 input
114 })
115 }
116
read_input( input: &mut InStreamType, thread_in_avail_evt: &Event, buffer: Arc<Mutex<VecDeque<u8>>>, kill_evt: Event, ) -> anyhow::Result<()>117 fn read_input(
118 input: &mut InStreamType,
119 thread_in_avail_evt: &Event,
120 buffer: Arc<Mutex<VecDeque<u8>>>,
121 kill_evt: Event,
122 ) -> anyhow::Result<()> {
123 #[derive(EventToken)]
124 enum Token {
125 ConsoleEvent,
126 Kill,
127 }
128
129 let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
130 (&kill_evt, Token::Kill),
131 (input.get_read_notifier(), Token::ConsoleEvent),
132 ])
133 .context("failed creating WaitContext")?;
134
135 let mut kill_timeout = None;
136 let mut rx_buf = [0u8; 1 << 12];
137 'wait: loop {
138 let events = wait_ctx.wait().context("Failed to wait for events")?;
139 for event in events.iter() {
140 match event.token {
141 Token::Kill => {
142 // Ignore the kill event until there are no other events to process so that
143 // we drain `input` as much as possible. The next `wait_ctx.wait()` call will
144 // immediately re-entry this case since we don't call `kill_evt.wait()`.
145 if events.iter().all(|e| matches!(e.token, Token::Kill)) {
146 break 'wait;
147 }
148 const TIMEOUT_DURATION: Duration = Duration::from_millis(500);
149 match kill_timeout {
150 None => {
151 kill_timeout = Some(Instant::now() + TIMEOUT_DURATION);
152 }
153 Some(t) => {
154 if Instant::now() >= t {
155 error!(
156 "failed to drain console input within {:?}, giving up",
157 TIMEOUT_DURATION
158 );
159 break 'wait;
160 }
161 }
162 }
163 }
164 Token::ConsoleEvent => {
165 match input.read(&mut rx_buf) {
166 Ok(0) => break 'wait, // Assume the stream of input has ended.
167 Ok(size) => {
168 buffer.lock().extend(&rx_buf[0..size]);
169 thread_in_avail_evt.signal().unwrap();
170 }
171 // Being interrupted is not an error, but everything else is.
172 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
173 Err(e) => {
174 return Err(e).context("failed to read console input");
175 }
176 }
177 }
178 }
179 }
180 }
181
182 Ok(())
183 }
184