console.rs47.90%
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::fmt::Debug;16
use std::io::{self, ErrorKind, Read, Write};17
use std::mem::MaybeUninit;18
use std::sync::Arc;19
use std::thread::JoinHandle;20
21
use libc::{22
F_GETFL, F_SETFL, O_NONBLOCK, OPOST, STDIN_FILENO, STDOUT_FILENO, TCSANOW, cfmakeraw, fcntl,23
tcgetattr, tcsetattr, termios,24
};25
use mio::unix::SourceFd;26
use mio::{Events, Interest, Poll, Registry, Token};27
28
use crate::device::Result;29
use crate::ffi;30
use crate::sync::notifier::Notifier;31
32
pub 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)]39
struct StdinBackup {40
termios: Option<termios>,41
flag: Option<i32>,42
}43
44
impl 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
64
impl 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)]80
pub struct StdioConsole {81
_backup: StdinBackup,82
}83
84
impl 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
99
impl 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
106
impl 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
118
impl 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
134
pub trait UartRecv: Send + 'static {135
fn receive(&self, bytes: &[u8]);136
}137
138
const TOKEN_SHUTDOWN: Token = Token(1 << 63);139
140
struct ThreadWorker<U, C> {141
name: Arc<str>,142
uart: U,143
console: Arc<C>,144
poll: Poll,145
}146
147
impl<U, C> ThreadWorker<U, C>148
where149
U: UartRecv,150
C: Console,151
for<'a> &'a C: Read + Write,152
{153
fn read_input(&self) -> Result<usize> {12x154
let mut total_size = 0;12x155
let mut buf = [0u8; 16];12x156
loop {157
match self.console.as_ref().read(&mut buf) {24x158
Ok(0) => break,12x159
Err(e) if e.kind() == ErrorKind::WouldBlock => break,160
Ok(len) => {12x161
self.uart.receive(&buf[0..len]);12x162
total_size += len;12x163
}12x164
Err(e) => Err(e)?,165
}166
}167
Ok(total_size)12x168
}12x169
170
fn do_work_inner(&mut self) -> Result<()> {30x171
let mut events = Events::with_capacity(16);30x172
loop {173
self.poll.poll(&mut events, None)?;42x174
for event in events.iter() {42x175
if event.token() != C::TOKEN_INPUT {42x176
return Ok(());30x177
}12x178
self.read_input()?;12x179
}180
}181
}30x182
183
fn do_work(&mut self) {30x184
match self.do_work_inner() {30x185
Ok(()) => log::trace!("{}: done", self.name),30x186
Err(e) => log::error!("{}: {e:?}", self.name),187
}188
}30x189
}190
191
#[derive(Debug)]192
pub struct ConsoleThread {193
pub name: Arc<str>,194
worker_thread: Option<JoinHandle<()>>,195
exit_notifier: Notifier,196
}197
198
impl ConsoleThread {199
pub fn new<U, C>(name: Arc<str>, uart: U, console: Arc<C>) -> Result<Self>30x200
where30x201
U: UartRecv,30x202
C: Console,30x203
for<'a> &'a C: Read + Write,30x204
{205
let poll = Poll::new()?;30x206
let registry = poll.registry();30x207
let mut notifier = Notifier::new()?;30x208
registry.register(&mut notifier, TOKEN_SHUTDOWN, Interest::READABLE)?;30x209
console.activate(registry)?;30x210
let mut worker = ThreadWorker {30x211
name: name.clone(),30x212
uart,30x213
poll,30x214
console,30x215
};30x216
let worker_thread = std::thread::Builder::new()30x217
.name(name.to_string())30x218
.spawn(move || worker.do_work())?;30x219
let console = ConsoleThread {30x220
name,30x221
worker_thread: Some(worker_thread),30x222
exit_notifier: notifier,30x223
};30x224
Ok(console)30x225
}30x226
}227
228
impl Drop for ConsoleThread {229
fn drop(&mut self) {30x230
if let Err(e) = self.exit_notifier.notify() {30x231
log::error!("{}: {e:?}", self.name);232
return;233
}30x234
let Some(thread) = self.worker_thread.take() else {30x235
return;236
};237
if let Err(e) = thread.join() {30x238
log::error!("{}: {e:?}", self.name);239
}30x240
}30x241
}242
243
#[cfg(test)]244
#[path = "console_test.rs"]245
pub(crate) mod tests;246