Alioth Code Coverage

console.rs47.90%

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Debug;
16use std::io::{self, ErrorKind, Read, Write};
17use std::mem::MaybeUninit;
18use std::sync::Arc;
19use std::thread::JoinHandle;
20
21use libc::{
22 F_GETFL, F_SETFL, O_NONBLOCK, OPOST, STDIN_FILENO, STDOUT_FILENO, TCSANOW, cfmakeraw, fcntl,
23 tcgetattr, tcsetattr, termios,
24};
25use mio::unix::SourceFd;
26use mio::{Events, Interest, Poll, Registry, Token};
27
28use crate::device::Result;
29use crate::ffi;
30use crate::sync::notifier::Notifier;
31
32pub trait Console: Debug + Send + Sync + 'static {
33 const TOKEN_INPUT: Token;
34 fn activate(&self, registry: &Registry) -> io::Result<()>;
35 fn deactivate(&self, registry: &Registry) -> io::Result<()>;
36}
37
38#[derive(Debug)]
39struct StdinBackup {
40 termios: Option<termios>,
41 flag: Option<i32>,
42}
43
44impl StdinBackup {
45 fn new() -> StdinBackup {
46 let mut termios_backup = None;
47 let mut t = MaybeUninit::uninit();
48 match ffi!(unsafe { tcgetattr(STDIN_FILENO, t.as_mut_ptr()) }) {
49 Ok(_) => termios_backup = Some(unsafe { t.assume_init() }),
50 Err(e) => log::error!("tcgetattr() failed: {e:?}"),
51 }
52 let mut flag_backup = None;
53 match ffi! { unsafe { fcntl(STDIN_FILENO, F_GETFL) } } {
54 Ok(f) => flag_backup = Some(f),
55 Err(e) => log::error!("fcntl(STDIN_FILENO, F_GETFL) failed: {e:?}"),
56 }
57 StdinBackup {
58 termios: termios_backup,
59 flag: flag_backup,
60 }
61 }
62}
63
64impl Drop for StdinBackup {
65 fn drop(&mut self) {
66 if let Some(t) = self.termios.take()
67 && let Err(e) = ffi!(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, &t) })
68 {
69 log::error!("Restoring termios: {e:?}");
70 }
71 if let Some(f) = self.flag.take()
72 && let Err(e) = ffi!(unsafe { fcntl(STDIN_FILENO, F_SETFL, f) })
73 {
74 log::error!("Restoring stdin flag to {f:#x}: {e:?}")
75 }
76 }
77}
78
79#[derive(Debug)]
80pub struct StdioConsole {
81 _backup: StdinBackup,
82}
83
84impl StdioConsole {
85 pub fn new() -> Result<Self> {
86 let backup = StdinBackup::new();
87 let mut raw_termios = MaybeUninit::uninit();
88 ffi!(unsafe { tcgetattr(STDIN_FILENO, raw_termios.as_mut_ptr()) })?;
89 unsafe { cfmakeraw(raw_termios.as_mut_ptr()) };
90 unsafe { raw_termios.assume_init_mut().c_oflag |= OPOST };
91 ffi!(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, raw_termios.as_ptr()) })?;
92
93 let flag = ffi!(unsafe { fcntl(STDIN_FILENO, F_GETFL) })?;
94 ffi!(unsafe { fcntl(STDIN_FILENO, F_SETFL, flag | O_NONBLOCK) })?;
95 Ok(StdioConsole { _backup: backup })
96 }
97}
98
99impl Read for &StdioConsole {
100 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
101 let count = ffi!(unsafe { libc::read(STDIN_FILENO, buf.as_mut_ptr() as _, 16) })?;
102 Ok(count as usize)
103 }
104}
105
106impl Write for &StdioConsole {
107 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
108 let count =
109 ffi!(unsafe { libc::write(STDOUT_FILENO, buf.as_ptr() as *const _, buf.len()) })?;
110 Ok(count as usize)
111 }
112
113 fn flush(&mut self) -> io::Result<()> {
114 Ok(())
115 }
116}
117
118impl Console for StdioConsole {
119 const TOKEN_INPUT: Token = Token(0);
120
121 fn activate(&self, registry: &Registry) -> io::Result<()> {
122 registry.register(
123 &mut SourceFd(&STDIN_FILENO),
124 Self::TOKEN_INPUT,
125 Interest::READABLE,
126 )
127 }
128
129 fn deactivate(&self, registry: &Registry) -> io::Result<()> {
130 registry.deregister(&mut SourceFd(&STDIN_FILENO))
131 }
132}
133
134pub trait UartRecv: Send + 'static {
135 fn receive(&self, bytes: &[u8]);
136}
137
138const TOKEN_SHUTDOWN: Token = Token(1 << 63);
139
140struct ThreadWorker<U, C> {
141 name: Arc<str>,
142 uart: U,
143 console: Arc<C>,
144 poll: Poll,
145}
146
147impl<U, C> ThreadWorker<U, C>
148where
149 U: UartRecv,
150 C: Console,
151 for<'a> &'a C: Read + Write,
152{
153 fn read_input(&self) -> Result<usize> {12x
154 let mut total_size = 0;12x
155 let mut buf = [0u8; 16];12x
156 loop {
157 match self.console.as_ref().read(&mut buf) {24x
158 Ok(0) => break,12x
159 Err(e) if e.kind() == ErrorKind::WouldBlock => break,
160 Ok(len) => {12x
161 self.uart.receive(&buf[0..len]);12x
162 total_size += len;12x
163 }12x
164 Err(e) => Err(e)?,
165 }
166 }
167 Ok(total_size)12x
168 }12x
169
170 fn do_work_inner(&mut self) -> Result<()> {30x
171 let mut events = Events::with_capacity(16);30x
172 loop {
173 self.poll.poll(&mut events, None)?;42x
174 for event in events.iter() {42x
175 if event.token() != C::TOKEN_INPUT {42x
176 return Ok(());30x
177 }12x
178 self.read_input()?;12x
179 }
180 }
181 }30x
182
183 fn do_work(&mut self) {30x
184 match self.do_work_inner() {30x
185 Ok(()) => log::trace!("{}: done", self.name),30x
186 Err(e) => log::error!("{}: {e:?}", self.name),
187 }
188 }30x
189}
190
191#[derive(Debug)]
192pub struct ConsoleThread {
193 pub name: Arc<str>,
194 worker_thread: Option<JoinHandle<()>>,
195 exit_notifier: Notifier,
196}
197
198impl ConsoleThread {
199 pub fn new<U, C>(name: Arc<str>, uart: U, console: Arc<C>) -> Result<Self>30x
200 where30x
201 U: UartRecv,30x
202 C: Console,30x
203 for<'a> &'a C: Read + Write,30x
204 {
205 let poll = Poll::new()?;30x
206 let registry = poll.registry();30x
207 let mut notifier = Notifier::new()?;30x
208 registry.register(&mut notifier, TOKEN_SHUTDOWN, Interest::READABLE)?;30x
209 console.activate(registry)?;30x
210 let mut worker = ThreadWorker {30x
211 name: name.clone(),30x
212 uart,30x
213 poll,30x
214 console,30x
215 };30x
216 let worker_thread = std::thread::Builder::new()30x
217 .name(name.to_string())30x
218 .spawn(move || worker.do_work())?;30x
219 let console = ConsoleThread {30x
220 name,30x
221 worker_thread: Some(worker_thread),30x
222 exit_notifier: notifier,30x
223 };30x
224 Ok(console)30x
225 }30x
226}
227
228impl Drop for ConsoleThread {
229 fn drop(&mut self) {30x
230 if let Err(e) = self.exit_notifier.notify() {30x
231 log::error!("{}: {e:?}", self.name);
232 return;
233 }30x
234 let Some(thread) = self.worker_thread.take() else {30x
235 return;
236 };
237 if let Err(e) = thread.join() {30x
238 log::error!("{}: {e:?}", self.name);
239 }30x
240 }30x
241}
242
243#[cfg(test)]
244#[path = "console_test.rs"]
245pub(crate) mod tests;
246