2023-07-18 18:03:45 +03:00
|
|
|
//! Terminal driver implementation
|
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},
|
2023-07-27 16:24:52 +03:00
|
|
|
process::Signal,
|
2023-07-20 16:19:23 +03:00
|
|
|
};
|
2023-07-18 18:03:45 +03:00
|
|
|
|
2023-07-27 16:24:52 +03:00
|
|
|
use crate::{
|
|
|
|
proc::wait::Wait,
|
|
|
|
sync::IrqSafeSpinlock,
|
|
|
|
task::{process::Process, ProcessId},
|
|
|
|
};
|
2023-07-18 18:03:45 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
use super::serial::SerialDevice;
|
2023-08-02 19:53:54 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
#[cfg(feature = "fb_console")]
|
|
|
|
pub mod combined {
|
|
|
|
use abi::{error::Error, io::DeviceRequest};
|
|
|
|
use vfs::CharDevice;
|
2023-08-02 20:43:21 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
use crate::device::{
|
|
|
|
display::{console::DisplayConsole, fb_console::FramebufferConsole},
|
|
|
|
input::KeyboardDevice,
|
|
|
|
serial::SerialDevice,
|
|
|
|
Device,
|
|
|
|
};
|
2023-08-02 19:53:54 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
use super::{CharRing, TtyDevice};
|
2023-08-02 19:53:54 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
// TODO rewrite this
|
|
|
|
/// Helper device to combine a display and a keyboard input into a single terminal
|
|
|
|
pub struct CombinedTerminal {
|
|
|
|
#[allow(dead_code)]
|
|
|
|
input: &'static dyn KeyboardDevice,
|
|
|
|
output: &'static dyn DisplayConsole,
|
2023-08-02 19:53:54 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
input_ring: CharRing<16>,
|
2023-08-02 19:53:54 +03:00
|
|
|
}
|
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
impl CombinedTerminal {
|
|
|
|
/// Create a combined terminal device from a keyboard and console output devices
|
|
|
|
pub fn new(
|
|
|
|
input: &'static dyn KeyboardDevice,
|
|
|
|
output: &'static FramebufferConsole,
|
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
input,
|
|
|
|
output,
|
|
|
|
input_ring: CharRing::new(),
|
|
|
|
}
|
|
|
|
}
|
2023-08-02 19:53:54 +03:00
|
|
|
}
|
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
impl TtyDevice<16> for CombinedTerminal {
|
|
|
|
fn ring(&self) -> &CharRing<16> {
|
|
|
|
&self.input_ring
|
|
|
|
}
|
2023-08-02 19:53:54 +03:00
|
|
|
}
|
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
impl SerialDevice for CombinedTerminal {
|
|
|
|
fn receive(&self, _blocking: bool) -> Result<u8, Error> {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn send(&self, byte: u8) -> Result<(), Error> {
|
|
|
|
self.output.write_char(byte);
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-08-02 19:53:54 +03:00
|
|
|
}
|
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
impl Device for CombinedTerminal {
|
|
|
|
fn name(&self) -> &'static str {
|
|
|
|
"Combined terminal device"
|
|
|
|
}
|
2023-08-02 19:53:54 +03:00
|
|
|
}
|
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
impl CharDevice for CombinedTerminal {
|
|
|
|
fn read(&'static self, blocking: bool, data: &mut [u8]) -> Result<usize, Error> {
|
|
|
|
assert!(blocking);
|
|
|
|
self.line_read(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write(&self, blocking: bool, data: &[u8]) -> Result<usize, Error> {
|
|
|
|
assert!(blocking);
|
|
|
|
self.line_write(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
|
|
|
match req {
|
|
|
|
&mut DeviceRequest::SetTerminalGroup(id) => {
|
|
|
|
self.set_signal_group(id as _);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
_ => Err(Error::InvalidArgument),
|
2023-08-02 19:53:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-18 18:03:45 +03:00
|
|
|
|
2023-08-08 20:00:24 +03:00
|
|
|
#[cfg(feature = "fb_console")]
|
|
|
|
pub use combined::CombinedTerminal;
|
|
|
|
|
2023-07-18 18:03:45 +03:00
|
|
|
struct CharRingInner<const N: usize> {
|
|
|
|
rd: usize,
|
|
|
|
wr: usize,
|
|
|
|
data: [u8; N],
|
|
|
|
flags: u8,
|
2023-07-27 16:24:52 +03:00
|
|
|
process_group: Option<ProcessId>,
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Ring buffer for a character device. Handles reads, writes and channel notifications for a
|
|
|
|
/// terminal device.
|
|
|
|
pub struct CharRing<const N: usize> {
|
|
|
|
wait_read: Wait,
|
|
|
|
wait_write: Wait,
|
|
|
|
inner: IrqSafeSpinlock<CharRingInner<N>>,
|
2023-07-20 16:19:23 +03:00
|
|
|
config: IrqSafeSpinlock<TerminalOptions>,
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Terminal device interface
|
|
|
|
pub trait TtyDevice<const N: usize>: SerialDevice {
|
|
|
|
/// Returns the ring buffer associated with the device
|
|
|
|
fn ring(&self) -> &CharRing<N>;
|
|
|
|
|
|
|
|
/// Returns `true` if data is ready to be read from or written to the terminal
|
|
|
|
fn is_ready(&self, write: bool) -> Result<bool, Error> {
|
|
|
|
let ring = self.ring();
|
|
|
|
if write {
|
|
|
|
todo!();
|
|
|
|
} else {
|
|
|
|
Ok(ring.is_readable())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-27 16:24:52 +03:00
|
|
|
/// Sets the process group to which signals from this terminal should be delivered
|
|
|
|
fn set_signal_group(&self, id: ProcessId) {
|
|
|
|
self.ring().inner.lock().process_group = Some(id);
|
|
|
|
}
|
|
|
|
|
2023-07-18 18:03:45 +03:00
|
|
|
/// Sends a single byte to the terminal
|
|
|
|
fn line_send(&self, byte: u8) -> Result<(), Error> {
|
2023-07-20 16:19:23 +03:00
|
|
|
let config = self.ring().config.lock();
|
|
|
|
|
|
|
|
if byte == b'\n' && config.output.contains(TerminalOutputOptions::NL_TO_CRNL) {
|
|
|
|
self.send(b'\r').ok();
|
|
|
|
}
|
|
|
|
|
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-07-18 18:03:45 +03:00
|
|
|
let ring = self.ring();
|
2023-07-20 16:19:23 +03:00
|
|
|
let config = ring.config.lock();
|
|
|
|
|
|
|
|
if byte == b'\r' && config.input.contains(TerminalInputOptions::CR_TO_NL) {
|
|
|
|
byte = b'\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
if byte == b'\n' {
|
2023-07-20 18:51:56 +03:00
|
|
|
// TODO implement proper echo here
|
|
|
|
let _echo = config.line.contains(TerminalLineOptions::ECHO)
|
2023-07-20 16:19:23 +03:00
|
|
|
|| config
|
|
|
|
.line
|
|
|
|
.contains(TerminalLineOptions::CANONICAL | TerminalLineOptions::ECHO_NL);
|
2023-07-20 18:51:56 +03:00
|
|
|
|
2023-07-20 16:19:23 +03:00
|
|
|
if config.output.contains(TerminalOutputOptions::NL_TO_CRNL) {
|
|
|
|
self.send(b'\r').ok();
|
|
|
|
}
|
|
|
|
self.send(byte).ok();
|
|
|
|
} else if config.line.contains(TerminalLineOptions::ECHO) {
|
|
|
|
if byte.is_ascii_control() {
|
2023-08-06 17:06:34 +03:00
|
|
|
if byte != config.chars.erase && byte != config.chars.werase {
|
|
|
|
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-08-04 11:00:31 +03:00
|
|
|
if byte == config.chars.interrupt && config.line.contains(TerminalLineOptions::SIGNAL) {
|
2023-07-27 16:24:52 +03:00
|
|
|
drop(config);
|
|
|
|
let pgrp = ring.inner.lock().process_group;
|
|
|
|
if let Some(pgrp) = pgrp {
|
|
|
|
Process::signal_group(pgrp, Signal::Interrupted);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
debugln!("Terminal has no process group attached");
|
|
|
|
}
|
|
|
|
}
|
2023-07-20 16:19:23 +03:00
|
|
|
|
2023-07-18 18:03:45 +03:00
|
|
|
ring.putc(byte, false).ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads and processes data from the terminal
|
|
|
|
fn line_read(&'static self, data: &mut [u8]) -> Result<usize, Error> {
|
|
|
|
let ring = self.ring();
|
2023-07-20 16:19:23 +03:00
|
|
|
let mut config = ring.config.lock();
|
2023-07-18 18:03:45 +03:00
|
|
|
|
|
|
|
if data.is_empty() {
|
|
|
|
return Ok(0);
|
|
|
|
}
|
|
|
|
|
2023-07-20 16:19:23 +03:00
|
|
|
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();
|
|
|
|
|
2023-08-06 17:06:34 +03:00
|
|
|
if config.is_canonical() {
|
|
|
|
if byte == config.chars.eof {
|
|
|
|
break;
|
|
|
|
} else if byte == config.chars.erase {
|
|
|
|
// 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;
|
|
|
|
} else if byte == config.chars.werase {
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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]
|
|
|
|
unsafe fn read_unchecked(&mut self) -> u8 {
|
|
|
|
let res = self.data[self.rd];
|
|
|
|
self.rd = (self.rd + 1) % N;
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
unsafe fn write_unchecked(&mut self, ch: u8) {
|
|
|
|
self.data[self.wr] = ch;
|
|
|
|
self.wr = (self.wr + 1) % N;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<const N: usize> CharRing<N> {
|
|
|
|
/// Constructs an empty ring buffer
|
|
|
|
pub const fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
inner: IrqSafeSpinlock::new(CharRingInner {
|
|
|
|
rd: 0,
|
|
|
|
wr: 0,
|
|
|
|
data: [0; N],
|
|
|
|
flags: 0,
|
2023-07-27 16:24:52 +03:00
|
|
|
process_group: None,
|
2023-07-18 18:03:45 +03:00
|
|
|
}),
|
|
|
|
wait_read: Wait::new("char_ring_read"),
|
|
|
|
wait_write: Wait::new("char_ring_write"),
|
2023-07-20 16:19:23 +03:00
|
|
|
config: IrqSafeSpinlock::new(TerminalOptions::const_default()),
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if the buffer has data to read
|
|
|
|
pub fn is_readable(&self) -> bool {
|
|
|
|
let inner = self.inner.lock();
|
2023-07-20 16:19:23 +03:00
|
|
|
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
|
|
|
|
}
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads a single character from the buffer, blocking until available
|
|
|
|
pub fn getc(&'static self) -> Result<u8, Error> {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let byte = unsafe { lock.read_unchecked() };
|
|
|
|
drop(lock);
|
|
|
|
self.wait_write.wakeup_one();
|
|
|
|
// TODO WAIT_SELECT
|
|
|
|
Ok(byte)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sends a single character to the buffer
|
|
|
|
pub fn putc(&self, ch: u8, blocking: bool) -> Result<(), Error> {
|
|
|
|
let mut lock = self.inner.lock();
|
|
|
|
if blocking {
|
|
|
|
todo!();
|
|
|
|
}
|
|
|
|
unsafe {
|
|
|
|
lock.write_unchecked(ch);
|
|
|
|
}
|
|
|
|
drop(lock);
|
|
|
|
self.wait_read.wakeup_one();
|
|
|
|
// TODO WAIT_SELECT
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|