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