//! Terminal driver implementation use core::{ pin::Pin, sync::atomic::{AtomicBool, Ordering}, task::{Context, Poll}, }; use abi::{ error::Error, io::{TerminalInputOptions, TerminalLineOptions, TerminalOptions, TerminalOutputOptions}, process::{ProcessGroupId, Signal}, }; use device_api::serial::SerialDevice; use futures_util::Future; use libk::task::process::ProcessImpl; use libk_util::{ring::RingBuffer, sync::IrqSafeSpinlock, waker::QueueWaker}; use crate::{proc::io::ProcessIoImpl, task::process::ProcessManagerImpl}; struct TerminalRing { buffer: IrqSafeSpinlock>, eof: AtomicBool, notify: QueueWaker, } impl TerminalRing { const CAPACITY: usize = 128; const fn new() -> Self { Self { buffer: IrqSafeSpinlock::new(RingBuffer::with_capacity(TerminalRing::CAPACITY)), eof: AtomicBool::new(false), notify: QueueWaker::new(), } } fn write(&self, ch: u8, signal: bool) { self.buffer.lock().write(ch); if signal { self.notify.wake_one(); } } fn signal(&self) { self.notify.wake_all(); } fn read_blocking(&self) -> impl Future> + '_ { struct F<'f> { ring: &'f TerminalRing, } impl<'f> Future for F<'f> { type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.ring.notify.register(cx.waker()); if self .ring .eof .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) .is_ok() { self.ring.notify.remove(cx.waker()); return Poll::Ready(None); } let mut lock = self.ring.buffer.lock(); if lock.is_readable() { self.ring.notify.remove(cx.waker()); Poll::Ready(Some(unsafe { lock.read_single_unchecked() })) } else { Poll::Pending } } } F { ring: self } } } struct TtyContextInner { config: TerminalOptions, process_group: Option, } /// Represents the context of a terminal device pub struct TtyContext { ring: TerminalRing, // AsyncRing, inner: IrqSafeSpinlock, } // TODO merge this code with PTY /// Terminal device interface pub trait TtyDevice: SerialDevice { /// Returns the ring buffer associated with the device fn context(&self) -> &TtyContext; /// Sets the process group to which signals from this terminal should be delivered fn set_signal_group(&self, id: ProcessGroupId) { self.context().inner.lock().process_group.replace(id); } /// Sends a single byte to the terminal fn line_send(&self, byte: u8) -> Result<(), Error> { let cx = self.context(); let inner = cx.inner.lock(); if byte == b'\n' && inner .config .output .contains(TerminalOutputOptions::NL_TO_CRNL) { self.send(b'\r').ok(); } drop(inner); self.send(byte) } /// Receives a single byte from the terminal fn recv_byte(&self, mut byte: u8) { let cx = self.context(); let inner = cx.inner.lock(); if byte == b'\r' && inner.config.input.contains(TerminalInputOptions::CR_TO_NL) { byte = b'\n'; } if byte == b'\n' { // TODO implement proper echo here let _echo = inner.config.line.contains(TerminalLineOptions::ECHO) || inner .config .line .contains(TerminalLineOptions::CANONICAL | TerminalLineOptions::ECHO_NL); if inner .config .output .contains(TerminalOutputOptions::NL_TO_CRNL) { self.send(b'\r').ok(); } self.send(byte).ok(); } else if inner.config.line.contains(TerminalLineOptions::ECHO) { if byte.is_ascii_control() { if byte != inner.config.chars.erase && byte != inner.config.chars.werase { self.send(b'^').ok(); self.send(byte + 0x40).ok(); } } else { self.send(byte).ok(); } } // byte == config.chars.interrupt if byte == inner.config.chars.interrupt && inner.config.line.contains(TerminalLineOptions::SIGNAL) { let pgrp = inner.process_group; cx.signal(); if let Some(pgrp) = pgrp { drop(inner); ProcessImpl::::signal_group( pgrp, Signal::Interrupted, ); return; } else { debugln!("Terminal has no process group attached"); } } let canonical = inner.config.line.contains(TerminalLineOptions::CANONICAL); drop(inner); cx.putc(byte, !canonical || byte == b'\n' || byte == b'\r'); } /// Reads and processes data from the terminal async fn line_read(&self, data: &mut [u8]) -> Result { let cx = self.context(); let mut inner = cx.inner.lock(); if data.is_empty() { return Ok(0); } if !inner.config.is_canonical() { drop(inner); let Some(byte) = cx.getc().await else { return Ok(0); }; 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(inner); let Some(byte) = cx.getc().await else { break; }; inner = cx.inner.lock(); if inner.config.is_canonical() { if byte == inner.config.chars.eof { break; } else if byte == inner.config.chars.erase { // Erase if off != 0 { self.raw_write(b"\x1b[D \x1b[D")?; off -= 1; rem += 1; } continue; } else if byte == inner.config.chars.werase { todo!() } } 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 fn line_write(&self, data: &[u8]) -> Result { for &byte in data { self.line_send(byte)?; } Ok(data.len()) } /// Writes raw data to the terminal bypassing the processing functions fn raw_write(&self, data: &[u8]) -> Result { for &byte in data { self.send(byte)?; } Ok(data.len()) } } impl TtyContext { /// Constructs a new [TtyContext] pub fn new() -> Self { Self { ring: TerminalRing::new(), // AsyncRing::new(0), inner: IrqSafeSpinlock::new(TtyContextInner { config: TerminalOptions::const_default(), process_group: None, }), } } /// Signals an event on the terminal pub fn signal(&self) { self.ring.signal() } /// Writes a single character to the terminal pub fn putc(&self, ch: u8, signal: bool) { self.ring.write(ch, signal); } /// Performs a blocking read of a single character from the terminal pub async fn getc(&self) -> Option { self.ring.read_blocking().await } /// Changes the configuration of the terminal pub fn set_config(&self, config: &TerminalOptions) -> Result<(), Error> { self.inner.lock().config = *config; Ok(()) } /// Returns the configuration of the terminal pub fn config(&self) -> TerminalOptions { self.inner.lock().config } }