diff --git a/src/device/tty.rs b/src/device/tty.rs index df3ca931..799ddbae 100644 --- a/src/device/tty.rs +++ b/src/device/tty.rs @@ -1,5 +1,8 @@ //! Terminal driver implementation -use abi::error::Error; +use abi::{ + error::Error, + io::{TerminalInputOptions, TerminalLineOptions, TerminalOptions, TerminalOutputOptions}, +}; use crate::{proc::wait::Wait, sync::IrqSafeSpinlock}; @@ -18,6 +21,7 @@ pub struct CharRing { wait_read: Wait, wait_write: Wait, inner: IrqSafeSpinlock>, + config: IrqSafeSpinlock, } /// Terminal device interface @@ -37,26 +41,88 @@ pub trait TtyDevice: SerialDevice { /// Sends a single byte to the terminal fn line_send(&self, byte: u8) -> Result<(), Error> { + let config = self.ring().config.lock(); + + if byte == b'\n' && config.output.contains(TerminalOutputOptions::NL_TO_CRNL) { + self.send(b'\r').ok(); + } + self.send(byte) } /// Receives a single byte from the terminal - fn recv_byte(&self, byte: u8) { + fn recv_byte(&self, mut byte: u8) { let ring = self.ring(); + let config = ring.config.lock(); + + if byte == b'\r' && config.input.contains(TerminalInputOptions::CR_TO_NL) { + byte = b'\n'; + } + + if byte == b'\n' { + let echo = config.line.contains(TerminalLineOptions::ECHO) + || config + .line + .contains(TerminalLineOptions::CANONICAL | TerminalLineOptions::ECHO_NL); + if config.output.contains(TerminalOutputOptions::NL_TO_CRNL) { + self.send(b'\r').ok(); + } + self.send(byte).ok(); + } else if config.line.contains(TerminalLineOptions::ECHO) { + // TODO proper control + if byte.is_ascii_control() { + self.send(b'^').ok(); + self.send(byte + 0x40).ok(); + } else { + self.send(byte).ok(); + } + } + + // TODO handle signals + ring.putc(byte, false).ok(); } /// Reads and processes data from the terminal fn line_read(&'static self, data: &mut [u8]) -> Result { let ring = self.ring(); + let mut config = ring.config.lock(); if data.is_empty() { return Ok(0); } - let byte = ring.getc()?; - data[0] = byte; - Ok(1) + if !config.is_canonical() { + let byte = ring.getc()?; + data[0] = byte; + Ok(1) + } else { + let mut rem = data.len(); + let mut off = 0; + + // Run until either end of buffer or return condition is reached + while rem != 0 { + drop(config); + let byte = ring.getc()?; + config = ring.config.lock(); + + if byte == config.chars.eof && config.is_canonical() { + break; + } + + // TODO handle special characters + + data[off] = byte; + off += 1; + rem -= 1; + + if byte == b'\n' || byte == b'\r' { + break; + } + } + + Ok(off) + } } /// Processes and writes the data to the terminal @@ -109,13 +175,42 @@ impl CharRing { }), wait_read: Wait::new("char_ring_read"), wait_write: Wait::new("char_ring_write"), + config: IrqSafeSpinlock::new(TerminalOptions::const_default()), } } /// Returns `true` if the buffer has data to read pub fn is_readable(&self) -> bool { let inner = self.inner.lock(); - inner.is_readable() || inner.flags != 0 + let config = self.config.lock(); + + if config.is_canonical() { + let mut rd = inner.rd; + let mut count = 0usize; + + loop { + let readable = if rd <= inner.wr { + (inner.wr - rd) > 0 + } else { + (inner.wr + (N - rd)) > 0 + }; + + if !readable { + break; + } + + let byte = inner.data[rd]; + if byte == b'\n' { + count += 1; + } + + rd = (rd + 1) % N; + } + + count != 0 || inner.flags != 0 + } else { + inner.is_readable() || inner.flags != 0 + } } /// Reads a single character from the buffer, blocking until available