feature(tty): (mostly) implement ICANON mode

This commit is contained in:
2021-11-08 17:25:27 +02:00
parent 9a15c1a1d3
commit e516af6f3f
4 changed files with 156 additions and 41 deletions
+71 -41
View File
@@ -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();
}
}
+1
View File
@@ -6,6 +6,7 @@ extern crate bitflags;
pub mod abi;
pub mod stat;
pub mod termios;
#[cfg(feature = "user")]
pub mod calls;
+80
View File
@@ -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)
}
}
+4
View File
@@ -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);