console.rs0.00%
1
// Copyright 2024 Google LLC2
//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 at6
//7
// https://www.apache.org/licenses/LICENSE-2.08
//9
// Unless required by applicable law or agreed to in writing, software10
// 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 and13
// limitations under the License.14
15
use std::io::{ErrorKind, Result};16
use std::mem::MaybeUninit;17
use std::sync::Arc;18
use std::thread::JoinHandle;19
20
use libc::{21
F_GETFL, F_SETFL, O_NONBLOCK, OPOST, STDIN_FILENO, STDOUT_FILENO, TCSANOW, cfmakeraw, fcntl,22
tcgetattr, tcsetattr, termios,23
};24
use mio::unix::SourceFd;25
use mio::{Events, Interest, Poll, Token, Waker};26
27
use crate::ffi;28
29
struct StdinBackup {30
termios: Option<termios>,31
flag: Option<i32>,32
}33
34
impl 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
54
impl 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
69
pub trait UartRecv: Send + 'static {70
fn receive(&self, bytes: &[u8]);71
}72
73
struct ConsoleWorker<U: UartRecv> {74
name: Arc<str>,75
uart: U,76
poll: Poll,77
}78
79
impl<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)]141
pub struct Console {142
pub name: Arc<str>,143
worker_thread: Option<JoinHandle<()>>,144
exit_waker: Waker,145
}146
147
const TOKEN_SHUTDOWN: Token = Token(1);148
const TOKEN_STDIN: Token = Token(0);149
150
impl 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
180
impl 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