Alioth Code Coverage

console.rs0.00%

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::io::{ErrorKind, Result};
16use std::mem::MaybeUninit;
17use std::sync::Arc;
18use std::thread::JoinHandle;
19
20use libc::{
21 F_GETFL, F_SETFL, O_NONBLOCK, OPOST, STDIN_FILENO, STDOUT_FILENO, TCSANOW, cfmakeraw, fcntl,
22 tcgetattr, tcsetattr, termios,
23};
24use mio::unix::SourceFd;
25use mio::{Events, Interest, Poll, Token, Waker};
26
27use crate::ffi;
28
29struct StdinBackup {
30 termios: Option<termios>,
31 flag: Option<i32>,
32}
33
34impl StdinBackup {
35 fn new() -> StdinBackup {
36 let mut termios_backup = None;
37 let mut t = MaybeUninit::uninit();
38 match ffi!(unsafe { tcgetattr(STDIN_FILENO, t.as_mut_ptr()) }) {
39 Ok(_) => termios_backup = Some(unsafe { t.assume_init() }),
40 Err(e) => log::error!("tcgetattr() failed: {e:?}"),
41 }
42 let mut flag_backup = None;
43 match ffi! { unsafe { fcntl(STDIN_FILENO, F_GETFL) } } {
44 Ok(f) => flag_backup = Some(f),
45 Err(e) => log::error!("fcntl(STDIN_FILENO, F_GETFL) failed: {e:?}"),
46 }
47 StdinBackup {
48 termios: termios_backup,
49 flag: flag_backup,
50 }
51 }
52}
53
54impl Drop for StdinBackup {
55 fn drop(&mut self) {
56 if let Some(t) = self.termios.take()
57 && let Err(e) = ffi!(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, &t) })
58 {
59 log::error!("Restoring termios: {e:?}");
60 }
61 if let Some(f) = self.flag.take()
62 && let Err(e) = ffi!(unsafe { fcntl(STDIN_FILENO, F_SETFL, f) })
63 {
64 log::error!("Restoring stdin flag to {f:#x}: {e:?}")
65 }
66 }
67}
68
69pub trait UartRecv: Send + 'static {
70 fn receive(&self, bytes: &[u8]);
71}
72
73struct ConsoleWorker<U: UartRecv> {
74 name: Arc<str>,
75 uart: U,
76 poll: Poll,
77}
78
79impl<U: UartRecv> ConsoleWorker<U> {
80 fn setup_termios(&mut self) -> Result<()> {
81 let mut raw_termios = MaybeUninit::uninit();
82 ffi!(unsafe { tcgetattr(STDIN_FILENO, raw_termios.as_mut_ptr()) })?;
83 unsafe { cfmakeraw(raw_termios.as_mut_ptr()) };
84 unsafe { raw_termios.assume_init_mut().c_oflag |= OPOST };
85 ffi!(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, raw_termios.as_ptr()) })?;
86
87 let flag = ffi!(unsafe { fcntl(STDIN_FILENO, F_GETFL) })?;
88 ffi!(unsafe { fcntl(STDIN_FILENO, F_SETFL, flag | O_NONBLOCK) })?;
89 self.poll.registry().register(
90 &mut SourceFd(&STDIN_FILENO),
91 TOKEN_STDIN,
92 Interest::READABLE,
93 )?;
94
95 Ok(())
96 }
97
98 fn read_input(&self) -> Result<usize> {
99 let mut total_size = 0;
100 let mut buf = [0u8; 16];
101 loop {
102 match ffi!(unsafe { libc::read(STDIN_FILENO, buf.as_mut_ptr() as _, 16) }) {
103 Ok(0) => break,
104 Err(e) if e.kind() == ErrorKind::WouldBlock => break,
105 Ok(len) => {
106 self.uart.receive(&buf[0..len as usize]);
107 total_size += len as usize;
108 }
109 Err(e) => return Err(e),
110 }
111 }
112 Ok(total_size)
113 }
114
115 fn do_work_inner(&mut self) -> Result<()> {
116 self.setup_termios()?;
117 let mut events = Events::with_capacity(16);
118 loop {
119 self.poll.poll(&mut events, None)?;
120 for event in events.iter() {
121 if event.token() == TOKEN_SHUTDOWN {
122 return Ok(());
123 }
124 self.read_input()?;
125 }
126 }
127 }
128
129 fn do_work(&mut self) {
130 log::trace!("{}: start", self.name);
131 let _backup = StdinBackup::new();
132 if let Err(e) = self.do_work_inner() {
133 log::error!("{}: {e:?}", self.name)
134 } else {
135 log::trace!("{}: done", self.name)
136 }
137 }
138}
139
140#[derive(Debug)]
141pub struct Console {
142 pub name: Arc<str>,
143 worker_thread: Option<JoinHandle<()>>,
144 exit_waker: Waker,
145}
146
147const TOKEN_SHUTDOWN: Token = Token(1);
148const TOKEN_STDIN: Token = Token(0);
149
150impl Console {
151 pub fn new(name: impl Into<Arc<str>>, uart: impl UartRecv) -> Result<Self> {
152 let name = name.into();
153 let poll = Poll::new()?;
154 let waker = Waker::new(poll.registry(), TOKEN_SHUTDOWN)?;
155 let mut worker = ConsoleWorker {
156 name: name.clone(),
157 uart,
158 poll,
159 };
160 let worker_thread = std::thread::Builder::new()
161 .name(name.to_string())
162 .spawn(move || worker.do_work())?;
163 let console = Console {
164 name,
165 worker_thread: Some(worker_thread),
166 exit_waker: waker,
167 };
168 Ok(console)
169 }
170
171 pub fn transmit(&self, bytes: &[u8]) {
172 let ret =
173 ffi!(unsafe { libc::write(STDOUT_FILENO, bytes.as_ptr() as *const _, bytes.len()) });
174 if let Err(e) = ret {
175 log::error!("{}: cannot write {bytes:#02x?}: {e:?}", self.name)
176 }
177 }
178}
179
180impl Drop for Console {
181 fn drop(&mut self) {
182 if let Err(e) = self.exit_waker.wake() {
183 log::error!("{}: {e:?}", self.name);
184 return;
185 }
186 let Some(thread) = self.worker_thread.take() else {
187 return;
188 };
189 if let Err(e) = thread.join() {
190 log::error!("{}: {e:?}", self.name);
191 }
192 }
193}
194