osdev5/kernel/src/dev/tty.rs

257 lines
7.4 KiB
Rust

//! Teletype (TTY) device facilities
use crate::dev::serial::SerialDevice;
use crate::proc::wait::Wait;
use crate::sync::IrqSafeSpinLock;
use libsys::error::Errno;
use libsys::{
termios::{Termios, TermiosIflag, TermiosLflag, TermiosOflag},
ioctl::IoctlCmd
};
use core::mem::size_of;
use crate::syscall::arg::validate_user_ptr_struct;
#[derive(Debug)]
struct CharRingInner<const N: usize> {
rd: usize,
wr: usize,
data: [u8; N],
flags: u8,
}
/// Ring buffer for TTYs
pub struct CharRing<const N: usize> {
wait_read: Wait,
wait_write: Wait,
config: IrqSafeSpinLock<Termios>,
inner: IrqSafeSpinLock<CharRingInner<N>>,
}
pub trait TtyDevice<const N: usize>: SerialDevice {
fn ring(&self) -> &CharRing<N>;
fn tty_ioctl(&self, cmd: IoctlCmd, ptr: usize, _len: usize) -> Result<usize, Errno> {
match cmd {
IoctlCmd::TtyGetAttributes => {
// TODO validate size
let res = validate_user_ptr_struct::<Termios>(ptr)?;
*res = self.ring().config.lock().clone();
Ok(size_of::<Termios>())
},
IoctlCmd::TtySetAttributes => {
let src = validate_user_ptr_struct::<Termios>(ptr)?;
*self.ring().config.lock() = src.clone();
Ok(size_of::<Termios>())
},
_ => Err(Errno::InvalidArgument)
}
}
fn line_send(&self, byte: u8) -> Result<(), Errno> {
let config = self.ring().config.lock();
if byte == b'\n' && config.oflag.contains(TermiosOflag::ONLCR) {
self.send(b'\r').ok();
}
self.send(byte)
}
fn recv_byte(&self, mut byte: u8) {
let ring = self.ring();
let config = ring.config.lock();
if byte == b'\r' && config.iflag.contains(TermiosIflag::ICRNL) {
byte = b'\n';
}
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();
}
/// Line discipline function
fn line_read(&self, data: &mut [u8]) -> Result<usize, Errno> {
let ring = self.ring();
let mut config = ring.config.lock();
if data.len() == 0 {
return Ok(0);
}
if !config.is_canon() {
drop(config);
let byte = ring.getc()?;
data[0] = byte;
return Ok(1);
} else {
let mut rem = data.len();
let mut off = 0;
// Perform canonical read
while rem != 0 {
drop(config);
let byte = ring.getc()?;
config = ring.config.lock();
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 _ 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;
}
data[off] = byte;
off += 1;
rem -= 1;
if byte == b'\n' || byte == b'\r' {
break;
}
}
Ok(off)
}
}
fn line_write(&self, data: &[u8]) -> Result<usize, Errno> {
for &byte in data.iter() {
self.line_send(byte)?;
}
Ok(data.len())
}
fn raw_write(&self, data: &[u8]) -> Result<usize, Errno> {
for &byte in data.iter() {
self.send(byte)?;
}
Ok(data.len())
}
}
impl<const N: usize> CharRingInner<N> {
#[inline]
const fn is_readable(&self) -> bool {
if self.rd <= self.wr {
(self.wr - self.rd) > 0
} else {
(self.wr + (N - self.rd)) > 0
}
}
#[inline]
fn read_unchecked(&mut self) -> u8 {
let res = self.data[self.rd];
self.rd = (self.rd + 1) % N;
res
}
#[inline]
fn write_unchecked(&mut self, ch: u8) {
self.data[self.wr] = ch;
self.wr = (self.wr + 1) % N;
}
}
impl<const N: usize> CharRing<N> {
/// Returns a new fixed-size ring buffer
pub const fn new() -> Self {
Self {
inner: IrqSafeSpinLock::new(CharRingInner {
rd: 0,
wr: 0,
data: [0; N],
flags: 0,
}),
config: IrqSafeSpinLock::new(Termios::new()),
wait_read: Wait::new(),
wait_write: Wait::new(),
}
}
/// Performs a blocking read of a single byte from the buffer
pub fn getc(&self) -> Result<u8, Errno> {
let mut lock = self.inner.lock();
loop {
if !lock.is_readable() && lock.flags == 0 {
drop(lock);
self.wait_read.wait(None)?;
lock = self.inner.lock();
} else {
break;
}
}
if lock.flags != 0 {
if lock.flags & (1 << 0) != 0 {
lock.flags &= !(1 << 0);
return Err(Errno::EndOfFile);
}
todo!();
}
let byte = lock.read_unchecked();
self.wait_write.wakeup_one();
Ok(byte)
}
/// Puts a single byte to the buffer
pub fn putc(&self, ch: u8, blocking: bool) -> Result<(), Errno> {
let mut lock = self.inner.lock();
if blocking {
todo!()
}
lock.write_unchecked(ch);
self.wait_read.wakeup_one();
Ok(())
}
}