feature(tty): (mostly) implement ICANON mode
This commit is contained in:
+71
-41
@@ -1,8 +1,9 @@
|
||||
//! Teletype (TTY) device facilities
|
||||
use crate::proc::wait::Wait;
|
||||
use crate::dev::serial::SerialDevice;
|
||||
use crate::proc::wait::Wait;
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use error::Errno;
|
||||
use syscall::termios::{Termios, TermiosIflag, TermiosLflag, TermiosOflag};
|
||||
use vfs::CharDevice;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -17,6 +18,7 @@ struct CharRingInner<const N: usize> {
|
||||
pub struct CharRing<const N: usize> {
|
||||
wait_read: Wait,
|
||||
wait_write: Wait,
|
||||
config: IrqSafeSpinLock<Termios>,
|
||||
inner: IrqSafeSpinLock<CharRingInner<N>>,
|
||||
}
|
||||
|
||||
@@ -24,7 +26,9 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
|
||||
fn ring(&self) -> &CharRing<N>;
|
||||
|
||||
fn line_send(&self, byte: u8) -> Result<(), Errno> {
|
||||
if byte == b'\n' {
|
||||
let config = self.ring().config.lock();
|
||||
|
||||
if byte == b'\n' && config.oflag.contains(TermiosOflag::ONLCR) {
|
||||
self.send(b'\r').ok();
|
||||
}
|
||||
|
||||
@@ -32,68 +36,98 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
|
||||
}
|
||||
|
||||
fn recv_byte(&self, mut byte: u8) {
|
||||
if byte == b'\r' {
|
||||
// TODO ICRNL conv option
|
||||
let ring = self.ring();
|
||||
let config = ring.config.lock();
|
||||
|
||||
if byte == b'\r' && config.iflag.contains(TermiosIflag::ICRNL) {
|
||||
byte = b'\n';
|
||||
}
|
||||
|
||||
if byte == 4 {
|
||||
// TODO handle EOF
|
||||
self.ring().signal_eof();
|
||||
return;
|
||||
if byte == b'\n' {
|
||||
if config.lflag.contains(TermiosLflag::ECHO)
|
||||
|| (config.is_canon() && config.lflag.contains(TermiosLflag::ECHONL))
|
||||
{
|
||||
if byte == b'\n' && config.oflag.contains(TermiosOflag::ONLCR) {
|
||||
self.send(b'\r').ok();
|
||||
}
|
||||
self.send(byte).ok();
|
||||
}
|
||||
} else if config.lflag.contains(TermiosLflag::ECHO) {
|
||||
let echoe = (byte == config.chars.erase || byte == config.chars.werase)
|
||||
&& config.lflag.contains(TermiosLflag::ECHOE);
|
||||
let echok = byte == config.chars.kill && config.lflag.contains(TermiosLflag::ECHOE);
|
||||
|
||||
if byte.is_ascii_control() {
|
||||
if !echoe && !echok {
|
||||
self.send(b'^').ok();
|
||||
self.send(byte + 0x40).ok();
|
||||
}
|
||||
} else {
|
||||
self.send(byte).ok();
|
||||
}
|
||||
}
|
||||
|
||||
self.ring().putc(byte, false).ok();
|
||||
|
||||
match byte {
|
||||
b'\n' => {
|
||||
// TODO ECHONL option
|
||||
self.line_send(byte).ok();
|
||||
// TODO ICANON
|
||||
}
|
||||
0x17 | 0x7F => (),
|
||||
0x0C => {
|
||||
// TODO ECHO option && ICANON option
|
||||
self.raw_write(b"^L").ok();
|
||||
},
|
||||
0x1B => {
|
||||
// TODO ECHO option && ICANON option
|
||||
self.raw_write(b"^[").ok();
|
||||
},
|
||||
_ => {
|
||||
// TODO ECHO option
|
||||
self.line_send(byte).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Line discipline function
|
||||
fn line_read(&self, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
let ring = self.ring();
|
||||
let mut config = ring.config.lock();
|
||||
let mut rem = data.len();
|
||||
let mut off = 0;
|
||||
|
||||
if !config.is_canon() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
while rem != 0 {
|
||||
drop(config);
|
||||
let byte = match ring.getc() {
|
||||
Ok(ch) => ch,
|
||||
Err(Errno::Interrupt) => {
|
||||
todo!()
|
||||
}
|
||||
Err(Errno::EndOfFile) => {
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
config = ring.config.lock();
|
||||
|
||||
if byte == 0x7F {
|
||||
if off > 0 {
|
||||
if byte == config.chars.eof && config.is_canon() {
|
||||
break;
|
||||
}
|
||||
if byte == config.chars.erase && config.is_canon() {
|
||||
if off > 0 && config.lflag.contains(TermiosLflag::ECHOE) {
|
||||
self.raw_write(b"\x1B[D \x1B[D").ok();
|
||||
off -= 1;
|
||||
rem += 1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
if byte == config.chars.werase && config.is_canon() {
|
||||
if off > 0 && config.lflag.contains(TermiosLflag::ECHOE) {
|
||||
let idx = data[..off].iter().rposition(|&ch| ch == b' ').unwrap_or(0);
|
||||
let len = off;
|
||||
|
||||
for i in idx..len {
|
||||
self.raw_write(b"\x1B[D \x1B[D").ok();
|
||||
off -= 1;
|
||||
rem += 1;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
if byte == config.chars.kill && config.is_canon() {
|
||||
if off > 0 && config.lflag.contains(TermiosLflag::ECHOK) {
|
||||
while off != 0 {
|
||||
self.raw_write(b"\x1B[D \x1B[D").ok();
|
||||
off -= 1;
|
||||
rem += 1;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if byte >= b' ' {
|
||||
// TODO tty options
|
||||
}
|
||||
|
||||
data[off] = byte;
|
||||
@@ -156,6 +190,7 @@ impl<const N: usize> CharRing<N> {
|
||||
data: [0; N],
|
||||
flags: 0,
|
||||
}),
|
||||
config: IrqSafeSpinLock::new(Termios::new()),
|
||||
wait_read: Wait::new(),
|
||||
wait_write: Wait::new(),
|
||||
}
|
||||
@@ -195,9 +230,4 @@ impl<const N: usize> CharRing<N> {
|
||||
self.wait_read.wakeup_one();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn signal_eof(&self) {
|
||||
self.inner.lock().flags |= 1 << 0;
|
||||
self.wait_read.wakeup_one();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ extern crate bitflags;
|
||||
|
||||
pub mod abi;
|
||||
pub mod stat;
|
||||
pub mod termios;
|
||||
|
||||
#[cfg(feature = "user")]
|
||||
pub mod calls;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
bitflags! {
|
||||
pub struct TermiosIflag: u32 {
|
||||
/// Translate NL to CR on input
|
||||
const INLCR = 1 << 0;
|
||||
/// Translate CR to NL on input
|
||||
const ICRNL = 1 << 1;
|
||||
}
|
||||
|
||||
pub struct TermiosOflag: u32 {
|
||||
/// Translate NL to CR-NL on output
|
||||
const ONLCR = 1 << 0;
|
||||
}
|
||||
|
||||
pub struct TermiosLflag: u32 {
|
||||
/// Signal processing (INTR, QUIT, SUSP)
|
||||
const ISIG = 1 << 0;
|
||||
/// Canonical mode
|
||||
const ICANON = 1 << 1;
|
||||
/// Echo input characters
|
||||
const ECHO = 1 << 2;
|
||||
/// If ICANON also set, ERASE erases chars, WERASE erases words
|
||||
const ECHOE = 1 << 3;
|
||||
/// If ICANON also set, KILL erases line
|
||||
const ECHOK = 1 << 4;
|
||||
/// If ICANON also set, echo NL even if ECHO is not set
|
||||
const ECHONL = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TermiosChars {
|
||||
pub eof: u8,
|
||||
pub erase: u8,
|
||||
pub intr: u8,
|
||||
pub kill: u8,
|
||||
pub vlnext: u8,
|
||||
pub werase: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Termios {
|
||||
pub iflag: TermiosIflag,
|
||||
pub oflag: TermiosOflag,
|
||||
pub lflag: TermiosLflag,
|
||||
pub chars: TermiosChars
|
||||
}
|
||||
|
||||
impl TermiosChars {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
eof: 0x04,
|
||||
erase: 0x7F,
|
||||
intr: 0x03,
|
||||
kill: 0x15,
|
||||
vlnext: 0x16,
|
||||
werase: 0x17,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Termios {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
iflag: TermiosIflag::ICRNL,
|
||||
oflag: TermiosOflag::ONLCR,
|
||||
// TODO prettify this
|
||||
lflag: unsafe {
|
||||
TermiosLflag::from_bits_unchecked(
|
||||
(1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5),
|
||||
)
|
||||
},
|
||||
chars: TermiosChars::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_canon(&self) -> bool {
|
||||
self.lflag.contains(TermiosLflag::ICANON)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@ fn main() -> i32 {
|
||||
loop {
|
||||
print!("> ");
|
||||
let line = readline(libusr::sys::STDIN_FILENO, &mut buf).unwrap();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
let line = line.trim_end_matches('\n');
|
||||
|
||||
println!(":: {:?}", line);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user