dev: rework tty/pty
This commit is contained in:
parent
1bd9d65a5e
commit
a3d7ecd867
@ -25,7 +25,7 @@ use crate::vfs::{
|
||||
node::NodeRef,
|
||||
socket::{ConnectionSocketWrapper, ListenerSocketWrapper, PacketSocketWrapper},
|
||||
traits::{Read, Seek, Write},
|
||||
ConnectionSocket, FdPoll, FileReadiness, ListenerSocket, Node, PacketSocket, PseudoTerminal,
|
||||
ConnectionSocket, FdPoll, FileReadiness, ListenerSocket, Node, PacketSocket,
|
||||
PseudoTerminalMaster, PseudoTerminalSlave, SharedMemory, Socket, TimerFile,
|
||||
};
|
||||
|
||||
@ -36,6 +36,8 @@ use self::{
|
||||
regular::RegularFile,
|
||||
};
|
||||
|
||||
use super::pty;
|
||||
|
||||
mod device;
|
||||
mod directory;
|
||||
mod pipe;
|
||||
@ -114,7 +116,7 @@ impl File {
|
||||
config: TerminalOptions,
|
||||
size: TerminalSize,
|
||||
) -> Result<(Arc<Self>, Arc<Self>), Error> {
|
||||
let (master, slave) = PseudoTerminal::create(config, size)?;
|
||||
let (master, slave) = pty::create(config, size)?;
|
||||
let master = Arc::new(master);
|
||||
let slave = Arc::new(slave);
|
||||
let (master_node, slave_node) = Node::pseudo_terminal_nodes(master.clone(), slave.clone());
|
||||
|
@ -3,6 +3,7 @@
|
||||
extern crate alloc;
|
||||
|
||||
pub mod block;
|
||||
pub mod pty;
|
||||
|
||||
pub(crate) mod channel;
|
||||
pub(crate) mod device;
|
||||
@ -11,9 +12,9 @@ pub(crate) mod ioctx;
|
||||
pub(crate) mod node;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod poll;
|
||||
pub(crate) mod pty;
|
||||
pub(crate) mod shared_memory;
|
||||
pub(crate) mod socket;
|
||||
pub(crate) mod terminal;
|
||||
pub(crate) mod timer;
|
||||
pub(crate) mod traits;
|
||||
|
||||
@ -26,8 +27,9 @@ pub use node::{
|
||||
RegularImpl, SymlinkImpl,
|
||||
};
|
||||
pub use poll::FdPoll;
|
||||
pub use pty::{PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave};
|
||||
pub use pty::{PseudoTerminalMaster, PseudoTerminalSlave};
|
||||
pub use shared_memory::SharedMemory;
|
||||
pub use socket::{ConnectionSocket, ListenerSocket, PacketSocket, Socket};
|
||||
pub use terminal::{Terminal, TerminalInput, TerminalOutput};
|
||||
pub use timer::TimerFile;
|
||||
pub use traits::{FileReadiness, Read, Seek, Write};
|
||||
|
@ -2,398 +2,164 @@
|
||||
|
||||
// TODO handle werase key
|
||||
use core::{
|
||||
mem::MaybeUninit,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use libk_util::{
|
||||
ring::{LossyRingQueue, RingBuffer},
|
||||
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
use libk_util::{ring::LossyRingQueue, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{
|
||||
DeviceRequest, TerminalInputOptions, TerminalLineOptions, TerminalOptions,
|
||||
TerminalOutputOptions, TerminalSize,
|
||||
},
|
||||
process::{ProcessGroupId, Signal},
|
||||
io::{DeviceRequest, TerminalOptions, TerminalSize},
|
||||
};
|
||||
|
||||
use crate::task::process::Process;
|
||||
use super::terminal::{self, Terminal, TerminalInput, TerminalOutput};
|
||||
|
||||
const CAPACITY: usize = 8192;
|
||||
|
||||
struct PtySlaveToMasterHalf {
|
||||
struct PtyOutput {
|
||||
ring: LossyRingQueue<u8>,
|
||||
shutdown: AtomicBool,
|
||||
}
|
||||
|
||||
struct MasterToSlaveBuffer {
|
||||
pending: Box<[MaybeUninit<u8>]>,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
struct PtyMasterToSlaveHalf {
|
||||
// Actual data to be read by the slave
|
||||
buffer: IrqSafeSpinlock<MasterToSlaveBuffer>,
|
||||
ready_ring: LossyRingQueue<u8>,
|
||||
signal_pgroup: IrqSafeRwLock<Option<ProcessGroupId>>,
|
||||
}
|
||||
|
||||
/// Pseudo-terminal shared device
|
||||
pub struct PseudoTerminal {
|
||||
config: IrqSafeRwLock<TerminalOptions>,
|
||||
slave_to_master: PtySlaveToMasterHalf,
|
||||
master_to_slave: PtyMasterToSlaveHalf,
|
||||
size: IrqSafeRwLock<TerminalSize>,
|
||||
}
|
||||
/// Slave part of a PTY device
|
||||
#[derive(Clone)]
|
||||
pub struct PseudoTerminalSlave {
|
||||
pty: Arc<PseudoTerminal>,
|
||||
}
|
||||
/// Master part of a PTY device
|
||||
#[derive(Clone)]
|
||||
pub struct PseudoTerminalMaster {
|
||||
pty: Arc<PseudoTerminal>,
|
||||
}
|
||||
|
||||
fn read_all(source: &mut RingBuffer<u8>, target: &mut [u8], eof: Option<u8>) -> usize {
|
||||
let mut pos = 0;
|
||||
while pos < target.len()
|
||||
&& let Some(ch) = source.try_read()
|
||||
{
|
||||
if eof.map(|eof| eof == ch).unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
target[pos] = ch;
|
||||
pos += 1;
|
||||
}
|
||||
pos
|
||||
}
|
||||
|
||||
impl MasterToSlaveBuffer {
|
||||
pub fn write_pending(&mut self, byte: u8) {
|
||||
if self.position == self.pending.len() {
|
||||
// TODO flush the buffer
|
||||
todo!();
|
||||
}
|
||||
|
||||
self.pending[self.position].write(byte);
|
||||
self.position += 1;
|
||||
impl TerminalOutput for PtyOutput {
|
||||
fn write(&self, byte: u8) -> Result<(), Error> {
|
||||
self.ring.write(byte);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn erase_pending(&mut self) -> bool {
|
||||
if self.position != 0 {
|
||||
self.position -= 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
|
||||
self.ring.write_multiple(bytes);
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> &[u8] {
|
||||
let data = unsafe { MaybeUninit::slice_assume_init_ref(&self.pending[..self.position]) };
|
||||
self.position = 0;
|
||||
data
|
||||
fn notify_readers(&self) {
|
||||
self.ring.notify_all();
|
||||
}
|
||||
|
||||
fn size(&self) -> TerminalSize {
|
||||
*self.size.read()
|
||||
}
|
||||
|
||||
fn set_size(&self, size: TerminalSize) -> Result<(), Error> {
|
||||
// TODO checks
|
||||
*self.size.write() = size;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PtyMasterToSlaveHalf {
|
||||
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
buffer: IrqSafeSpinlock::new(MasterToSlaveBuffer {
|
||||
pending: Box::new_uninit_slice(256),
|
||||
position: 0,
|
||||
}),
|
||||
ready_ring: LossyRingQueue::try_with_capacity(capacity)?,
|
||||
signal_pgroup: IrqSafeRwLock::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read(&self, buf: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
|
||||
if let Some(mut lock) = self.ready_ring.try_read_lock() {
|
||||
Ok(read_all(&mut lock, buf, eof))
|
||||
} else {
|
||||
block!(self.read_async(buf, eof).await)?
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_readable(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.ready_ring.poll_readable(cx).map(Ok)
|
||||
}
|
||||
|
||||
pub fn flush(&self) {
|
||||
let mut lock = self.buffer.lock();
|
||||
let data = lock.flush();
|
||||
self.ready_ring.write_multiple(data);
|
||||
}
|
||||
|
||||
async fn read_async(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
|
||||
let mut lock = self.ready_ring.read_lock().await;
|
||||
Ok(read_all(&mut lock, buffer, eof))
|
||||
}
|
||||
}
|
||||
|
||||
impl PtySlaveToMasterHalf {
|
||||
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||
impl PtyOutput {
|
||||
pub fn with_capacity(size: TerminalSize, capacity: usize) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
ring: LossyRingQueue::try_with_capacity(capacity)?,
|
||||
shutdown: AtomicBool::new(false),
|
||||
size: IrqSafeRwLock::new(size),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, byte: u8, _config: &TerminalOutputOptions) {
|
||||
// TODO handle output flags
|
||||
self.ring.write(byte);
|
||||
pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
if self.shutdown.load(Ordering::Acquire) || self.ring.poll_readable(cx).is_ready() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
pub fn read_blocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
if self.shutdown.load(Ordering::Acquire) {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if let Some(mut lock) = self.ring.try_read_lock() {
|
||||
let count = read_all(&mut lock, buf, None);
|
||||
let count = terminal::read_all(&mut lock, buffer, None);
|
||||
Ok(count)
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_readable(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
if self.shutdown.load(Ordering::Acquire) || self.ring.poll_readable(cx).is_ready() {
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PseudoTerminal {
|
||||
/// Creates a pair of PTY slave/master devices
|
||||
pub fn create(
|
||||
config: TerminalOptions,
|
||||
size: TerminalSize,
|
||||
) -> Result<(PseudoTerminalMaster, PseudoTerminalSlave), Error> {
|
||||
let master_to_slave = PtyMasterToSlaveHalf::with_capacity(CAPACITY)?;
|
||||
let slave_to_master = PtySlaveToMasterHalf::with_capacity(CAPACITY)?;
|
||||
/// Slave part of a PTY device
|
||||
#[derive(Clone)]
|
||||
pub struct PseudoTerminalSlave(Arc<Terminal<PtyOutput>>);
|
||||
|
||||
let pty = Arc::new(Self {
|
||||
config: IrqSafeRwLock::new(config),
|
||||
master_to_slave,
|
||||
slave_to_master,
|
||||
size: IrqSafeRwLock::new(size),
|
||||
});
|
||||
|
||||
let master = PseudoTerminalMaster { pty: pty.clone() };
|
||||
let slave = PseudoTerminalSlave { pty };
|
||||
|
||||
Ok((master, slave))
|
||||
}
|
||||
|
||||
fn putc_from_slave(&self, byte: u8) {
|
||||
let config = self.config.read();
|
||||
self.slave_to_master.handle_input(byte, &config.output)
|
||||
}
|
||||
|
||||
fn putc_from_master(&self, mut byte: u8) {
|
||||
let config = self.config.read();
|
||||
|
||||
let mut buffer = self.master_to_slave.buffer.lock();
|
||||
|
||||
if byte == b'\r' && config.input.contains(TerminalInputOptions::CR_TO_NL) {
|
||||
byte = b'\n';
|
||||
}
|
||||
|
||||
if config.is_canonical() {
|
||||
// Canonical line processing
|
||||
|
||||
// Echo back
|
||||
if byte == config.chars.erase {
|
||||
let echo =
|
||||
buffer.erase_pending() && config.line.contains(TerminalLineOptions::ECHO_ERASE);
|
||||
|
||||
if echo {
|
||||
for &ch in b"\x1B[D \x1B[D" {
|
||||
self.slave_to_master.handle_input(ch, &config.output);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if byte == b'\n' {
|
||||
// TODO NL_TO_CRNL
|
||||
if config.is_echo_newline() {
|
||||
self.slave_to_master.handle_input(byte, &config.output);
|
||||
}
|
||||
} else if byte.is_ascii_control() {
|
||||
if config.line.contains(TerminalLineOptions::ECHO) {
|
||||
self.slave_to_master.handle_input(b'^', &config.output);
|
||||
self.slave_to_master
|
||||
.handle_input(byte + 0x40, &config.output);
|
||||
}
|
||||
} else if config.line.contains(TerminalLineOptions::ECHO) {
|
||||
self.slave_to_master.handle_input(byte, &config.output);
|
||||
}
|
||||
|
||||
if byte == config.chars.interrupt {
|
||||
if config.line.contains(TerminalLineOptions::SIGNAL) {
|
||||
self.slave_to_master.ring.notify_all();
|
||||
|
||||
if let Some(group_id) = *self.master_to_slave.signal_pgroup.read() {
|
||||
Process::signal_group(group_id, Signal::Interrupted);
|
||||
self.master_to_slave.ready_ring.notify_all();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
buffer.write_pending(byte);
|
||||
}
|
||||
} else {
|
||||
buffer.write_pending(byte);
|
||||
}
|
||||
|
||||
if byte == b'\n' || byte == config.chars.eof {
|
||||
let data = buffer.flush();
|
||||
self.master_to_slave.ready_ring.write_multiple(data);
|
||||
}
|
||||
} else {
|
||||
// Raw line processing
|
||||
self.master_to_slave.ready_ring.write(byte);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_from_slave(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
for &ch in buf {
|
||||
self.putc_from_slave(ch);
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn write_from_master(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
for &ch in buf {
|
||||
self.putc_from_master(ch);
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn read_from_slave(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.slave_to_master.read(buf)
|
||||
}
|
||||
|
||||
fn read_from_master(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
let eof = {
|
||||
let config = self.config.read();
|
||||
config.is_canonical().then_some(config.chars.eof)
|
||||
};
|
||||
self.master_to_slave.read(buf, eof)
|
||||
}
|
||||
|
||||
fn poll_from_slave(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.slave_to_master.poll_readable(cx)
|
||||
}
|
||||
|
||||
fn poll_from_master(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.master_to_slave.poll_readable(cx)
|
||||
}
|
||||
|
||||
fn close_master(&self) {
|
||||
let config = self.config.read();
|
||||
self.master_to_slave.ready_ring.write(config.chars.eof);
|
||||
}
|
||||
|
||||
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
||||
match req {
|
||||
DeviceRequest::SetTerminalGroup(group_id) => {
|
||||
self.master_to_slave
|
||||
.signal_pgroup
|
||||
.write()
|
||||
.replace(*group_id);
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::GetTerminalOptions(options) => {
|
||||
options.write(*self.config.read());
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::SetTerminalOptions(options) => {
|
||||
self.master_to_slave.flush();
|
||||
*self.config.write() = *options;
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::GetTerminalSize(size) => {
|
||||
size.write(*self.size.read());
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::SetTerminalSize(size) => {
|
||||
// TODO SIGWINCH?
|
||||
// TODO validate
|
||||
*self.size.write() = *size;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Master part of a PTY device
|
||||
#[derive(Clone)]
|
||||
pub struct PseudoTerminalMaster(Arc<Terminal<PtyOutput>>);
|
||||
|
||||
impl PseudoTerminalSlave {
|
||||
/// Reads from the master-to-slave half of the PTY
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.pty.read_from_master(buf)
|
||||
self.0.read_from_input(buf)
|
||||
}
|
||||
|
||||
/// Writes to the slave-to-master half of the PTY
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
self.pty.write_from_slave(buf)
|
||||
self.0.write_to_output(buf)
|
||||
}
|
||||
|
||||
/// Polls PTY read readiness
|
||||
pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.pty.poll_from_master(cx)
|
||||
self.0.input().poll_read(cx).map(Ok)
|
||||
}
|
||||
|
||||
/// Performs a device-specific request to the PTY
|
||||
pub fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
||||
self.pty.device_request(req)
|
||||
self.0.handle_device_request(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl PseudoTerminalMaster {
|
||||
/// Reads from the slave-to-master half of the PTY
|
||||
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.pty.read_from_slave(buf)
|
||||
self.0.output().read_blocking(buf)
|
||||
}
|
||||
|
||||
/// Writes to the master-to-slave half of the PTY
|
||||
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
|
||||
self.pty.write_from_master(buf)
|
||||
for &byte in buf {
|
||||
self.0.write_to_input(byte);
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
/// Polls PTY read readiness
|
||||
pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.pty.poll_from_slave(cx)
|
||||
self.0.output().poll_read(cx).map(Ok)
|
||||
}
|
||||
|
||||
/// Performs a device-specific request to the PTY
|
||||
pub fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
||||
self.pty.device_request(req)
|
||||
self.0.handle_device_request(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PseudoTerminalMaster {
|
||||
fn drop(&mut self) {
|
||||
self.pty.close_master();
|
||||
let config = self.0.config();
|
||||
self.0.write_to_input(config.chars.eof);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PseudoTerminalSlave {
|
||||
fn drop(&mut self) {
|
||||
self.pty
|
||||
.slave_to_master
|
||||
.shutdown
|
||||
.store(true, Ordering::Release);
|
||||
self.pty.slave_to_master.ring.notify_all();
|
||||
let output = self.0.output();
|
||||
output.shutdown.store(true, Ordering::Release);
|
||||
output.ring.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
config: TerminalOptions,
|
||||
size: TerminalSize,
|
||||
) -> Result<(PseudoTerminalMaster, PseudoTerminalSlave), Error> {
|
||||
let input = TerminalInput::with_capacity(CAPACITY)?;
|
||||
let output = PtyOutput::with_capacity(size, CAPACITY)?;
|
||||
let pty = Arc::new(Terminal::from_parts(config, input, output));
|
||||
|
||||
let master = PseudoTerminalMaster(pty.clone());
|
||||
let slave = PseudoTerminalSlave(pty);
|
||||
|
||||
Ok((master, slave))
|
||||
}
|
||||
|
310
kernel/libk/src/vfs/terminal.rs
Normal file
310
kernel/libk/src/vfs/terminal.rs
Normal file
@ -0,0 +1,310 @@
|
||||
use core::{
|
||||
mem::MaybeUninit,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use libk_util::{
|
||||
ring::{LossyRingQueue, RingBuffer},
|
||||
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
|
||||
};
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{DeviceRequest, TerminalInputOptions, TerminalLineOptions, TerminalOptions, TerminalSize},
|
||||
process::{ProcessGroupId, Signal},
|
||||
};
|
||||
|
||||
use crate::task::process::Process;
|
||||
|
||||
use super::{CharDevice, FileReadiness};
|
||||
|
||||
pub struct Terminal<O: TerminalOutput> {
|
||||
input: TerminalInput,
|
||||
output: O,
|
||||
config: IrqSafeRwLock<TerminalOptions>,
|
||||
}
|
||||
|
||||
// Keyboard, PTY slave, terminal, etc. -> program
|
||||
pub struct TerminalInput {
|
||||
buffer: IrqSafeSpinlock<InputBuffer>,
|
||||
ready_ring: LossyRingQueue<u8>,
|
||||
signal_pgroup: IrqSafeRwLock<Option<ProcessGroupId>>,
|
||||
}
|
||||
|
||||
// Program -> screen, PTY master, terminal, etc.
|
||||
pub trait TerminalOutput: Sync {
|
||||
fn write(&self, byte: u8) -> Result<(), Error>;
|
||||
|
||||
fn notify_readers(&self) {}
|
||||
|
||||
fn size(&self) -> TerminalSize {
|
||||
TerminalSize {
|
||||
rows: 80,
|
||||
columns: 24,
|
||||
}
|
||||
}
|
||||
fn set_size(&self, size: TerminalSize) -> Result<(), Error> {
|
||||
let _ = size;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
|
||||
fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
|
||||
let mut written = 0;
|
||||
for &byte in bytes {
|
||||
if let Err(error) = self.write(byte) {
|
||||
return Err(error);
|
||||
}
|
||||
written += 1;
|
||||
}
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
|
||||
struct InputBuffer {
|
||||
pending: Box<[MaybeUninit<u8>]>,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl<O: TerminalOutput> Terminal<O> {
|
||||
pub fn from_parts(config: TerminalOptions, input: TerminalInput, output: O) -> Self {
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
config: IrqSafeRwLock::new(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input(&self) -> &TerminalInput {
|
||||
&self.input
|
||||
}
|
||||
|
||||
pub fn output(&self) -> &O {
|
||||
&self.output
|
||||
}
|
||||
|
||||
pub fn config(&self) -> TerminalOptions {
|
||||
*self.config.read()
|
||||
}
|
||||
|
||||
pub fn read_from_input(&self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
let eof = {
|
||||
let config = self.config.read();
|
||||
config.is_canonical().then_some(config.chars.eof)
|
||||
};
|
||||
self.input.read_blocking(buffer, eof)
|
||||
}
|
||||
|
||||
pub fn putc_to_output(&self, byte: u8) -> Result<(), Error> {
|
||||
self.output.write(byte)
|
||||
}
|
||||
|
||||
pub fn write_to_output(&self, buffer: &[u8]) -> Result<usize, Error> {
|
||||
// TODO handle options
|
||||
self.output.write_multiple(buffer)
|
||||
}
|
||||
|
||||
pub fn write_to_input(&self, mut byte: u8) {
|
||||
let config = self.config.read();
|
||||
let mut buffer = self.input.buffer.lock();
|
||||
|
||||
if byte == b'\r' && config.input.contains(TerminalInputOptions::CR_TO_NL) {
|
||||
byte = b'\n';
|
||||
}
|
||||
|
||||
if config.is_canonical() {
|
||||
// Canonical line processing
|
||||
|
||||
// Echo back
|
||||
if byte == config.chars.erase {
|
||||
let echo =
|
||||
buffer.erase_pending() && config.line.contains(TerminalLineOptions::ECHO_ERASE);
|
||||
|
||||
if echo {
|
||||
self.output.write_multiple(b"\x1B[D \x1B[D").ok();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if byte == b'\n' {
|
||||
// TODO NL_TO_CRNL
|
||||
if config.is_echo_newline() {
|
||||
self.output.write(byte).ok();
|
||||
}
|
||||
} else if byte.is_ascii_control() {
|
||||
if config.line.contains(TerminalLineOptions::ECHO) {
|
||||
self.output.write_multiple(&[b'^', byte + 0x40]).ok();
|
||||
}
|
||||
} else if config.line.contains(TerminalLineOptions::ECHO) {
|
||||
self.output.write(byte).ok();
|
||||
}
|
||||
|
||||
if byte == config.chars.interrupt {
|
||||
if config.line.contains(TerminalLineOptions::SIGNAL) {
|
||||
self.output.notify_readers();
|
||||
|
||||
if let Some(group_id) = *self.input.signal_pgroup.read() {
|
||||
Process::signal_group(group_id, Signal::Interrupted);
|
||||
self.input.ready_ring.notify_all();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
buffer.write_pending(byte);
|
||||
}
|
||||
} else {
|
||||
buffer.write_pending(byte);
|
||||
}
|
||||
|
||||
if byte == b'\n' || byte == config.chars.eof {
|
||||
let data = buffer.flush();
|
||||
self.input.ready_ring.write_multiple(data);
|
||||
}
|
||||
} else {
|
||||
// Raw line processing
|
||||
self.input.ready_ring.write(byte);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
||||
match req {
|
||||
DeviceRequest::SetTerminalGroup(group) => {
|
||||
self.input.set_signal_pgroup(*group);
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::GetTerminalOptions(options) => {
|
||||
options.write(*self.config.read());
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::SetTerminalOptions(options) => {
|
||||
self.input.flush();
|
||||
*self.config.write() = *options;
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::SetTerminalSize(size) => {
|
||||
// TODO SIGWINCH?
|
||||
self.output.set_size(*size)
|
||||
}
|
||||
DeviceRequest::GetTerminalSize(size) => {
|
||||
size.write(self.output.size());
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalInput {
|
||||
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
buffer: IrqSafeSpinlock::new(InputBuffer::with_capacity(capacity)),
|
||||
ready_ring: LossyRingQueue::try_with_capacity(capacity)?,
|
||||
signal_pgroup: IrqSafeRwLock::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn read(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
|
||||
let mut lock = self.ready_ring.read_lock().await;
|
||||
Ok(read_all(&mut lock, buffer, eof))
|
||||
}
|
||||
|
||||
pub fn read_blocking(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
|
||||
if let Some(mut lock) = self.ready_ring.try_read_lock() {
|
||||
Ok(read_all(&mut lock, buffer, eof))
|
||||
} else {
|
||||
block!(self.read(buffer, eof).await)?
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_read<'a>(&'a self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
self.ready_ring.poll_readable(cx)
|
||||
}
|
||||
|
||||
pub fn flush(&self) {
|
||||
let mut lock = self.buffer.lock();
|
||||
let data = lock.flush();
|
||||
self.ready_ring.write_multiple(data);
|
||||
}
|
||||
|
||||
pub fn set_signal_pgroup(&self, value: ProcessGroupId) {
|
||||
self.signal_pgroup.write().replace(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl InputBuffer {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
pending: Box::new_uninit_slice(capacity),
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_pending(&mut self, byte: u8) {
|
||||
if self.position == self.pending.len() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
self.pending[self.position].write(byte);
|
||||
self.position += 1;
|
||||
}
|
||||
|
||||
pub fn erase_pending(&mut self) -> bool {
|
||||
if self.position != 0 {
|
||||
self.position -= 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> &[u8] {
|
||||
let data = unsafe { MaybeUninit::slice_assume_init_ref(&self.pending[..self.position]) };
|
||||
self.position = 0;
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: TerminalOutput> CharDevice for Terminal<O> {
|
||||
fn write(&'static self, buf: &[u8]) -> Result<usize, Error> {
|
||||
self.write_to_output(buf)
|
||||
}
|
||||
|
||||
fn read(&'static self, buf: &mut [u8]) -> Result<usize, Error> {
|
||||
self.read_from_input(buf)
|
||||
}
|
||||
|
||||
fn is_readable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_writable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_terminal(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
||||
self.handle_device_request(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: TerminalOutput> FileReadiness for Terminal<O> {
|
||||
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.input().poll_read(cx).map(Ok)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_all(source: &mut RingBuffer<u8>, target: &mut [u8], eof: Option<u8>) -> usize {
|
||||
let mut pos = 0;
|
||||
while pos < target.len()
|
||||
&& let Some(ch) = source.try_read()
|
||||
{
|
||||
if eof.map(|eof| eof == ch).unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
target[pos] = ch;
|
||||
pos += 1;
|
||||
}
|
||||
pos
|
||||
}
|
@ -9,7 +9,6 @@ pub mod display;
|
||||
pub mod power;
|
||||
pub mod serial;
|
||||
pub mod timer;
|
||||
pub mod tty;
|
||||
|
||||
static DEVICE_MANAGER: IrqSafeSpinlock<DeviceManager> = IrqSafeSpinlock::new(DeviceManager::new());
|
||||
|
||||
|
@ -1,18 +1,15 @@
|
||||
//! ARM PL011 driver
|
||||
use abi::{error::Error, io::DeviceRequest};
|
||||
use abi::{error::Error, io::TerminalOptions};
|
||||
use alloc::boxed::Box;
|
||||
use device_api::{
|
||||
interrupt::{InterruptHandler, Irq},
|
||||
serial::SerialDevice,
|
||||
Device,
|
||||
};
|
||||
use device_tree::{device_tree_driver, dt::DevTreeIndexPropExt};
|
||||
use futures_util::task::{Context, Poll};
|
||||
use kernel_fs::devfs::{self, CharDeviceType};
|
||||
use libk::{
|
||||
block,
|
||||
device::external_interrupt_controller,
|
||||
vfs::{CharDevice, FileReadiness},
|
||||
vfs::{Terminal, TerminalInput, TerminalOutput},
|
||||
};
|
||||
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
|
||||
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
|
||||
@ -22,10 +19,7 @@ use tock_registers::{
|
||||
registers::{ReadOnly, ReadWrite, WriteOnly},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
debug::{self, DebugSink, LogLevel},
|
||||
device::tty::{TtyContext, TtyDevice},
|
||||
};
|
||||
use crate::debug::{self, DebugSink, LogLevel};
|
||||
|
||||
register_bitfields! {
|
||||
u32,
|
||||
@ -65,25 +59,27 @@ register_structs! {
|
||||
}
|
||||
}
|
||||
|
||||
struct Pl011Inner {
|
||||
struct Io {
|
||||
regs: DeviceMemoryIo<'static, Regs>,
|
||||
}
|
||||
|
||||
struct Pl011Inner {
|
||||
io: IrqSafeSpinlock<Io>,
|
||||
}
|
||||
|
||||
/// PL011 device instance
|
||||
pub struct Pl011 {
|
||||
inner: OneTimeInit<IrqSafeSpinlock<Pl011Inner>>,
|
||||
inner: OneTimeInit<Terminal<Pl011Inner>>,
|
||||
base: PhysicalAddress,
|
||||
irq: Irq,
|
||||
context: TtyContext,
|
||||
}
|
||||
|
||||
impl Pl011Inner {
|
||||
fn send_byte(&mut self, b: u8) -> Result<(), Error> {
|
||||
impl Io {
|
||||
fn send(&mut self, byte: u8) {
|
||||
while self.regs.FR.matches_all(FR::TXFF::SET) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
self.regs.DR.set(b as u32);
|
||||
Ok(())
|
||||
self.regs.DR.set(byte as u32);
|
||||
}
|
||||
|
||||
unsafe fn init(&mut self) {
|
||||
@ -95,9 +91,33 @@ impl Pl011Inner {
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalOutput for Pl011Inner {
|
||||
fn write(&self, byte: u8) -> Result<(), Error> {
|
||||
self.io.lock().send(byte);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
|
||||
let mut lock = self.io.lock();
|
||||
for &byte in bytes {
|
||||
lock.send(byte);
|
||||
}
|
||||
Ok(bytes.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pl011Inner {
|
||||
#[inline]
|
||||
fn handle_irq(&self) -> u32 {
|
||||
let lock = self.io.lock();
|
||||
lock.regs.ICR.write(ICR::ALL::CLEAR);
|
||||
lock.regs.DR.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugSink for Pl011 {
|
||||
fn putc(&self, byte: u8) -> Result<(), Error> {
|
||||
self.send(byte)
|
||||
self.inner.get().putc_to_output(byte)
|
||||
}
|
||||
|
||||
fn supports_control_sequences(&self) -> bool {
|
||||
@ -105,99 +125,12 @@ impl DebugSink for Pl011 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TtyDevice for Pl011 {
|
||||
fn context(&self) -> &TtyContext {
|
||||
&self.context
|
||||
}
|
||||
}
|
||||
|
||||
impl FileReadiness for Pl011 {
|
||||
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.context.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// impl CharDevice for Pl011 {
|
||||
// fn write(&self, blocking: bool, data: &[u8]) -> Result<usize, Error> {
|
||||
// assert!(blocking);
|
||||
// self.line_write(data)
|
||||
// }
|
||||
//
|
||||
// fn read(&'static self, blocking: bool, data: &mut [u8]) -> Result<usize, Error> {
|
||||
// assert!(blocking);
|
||||
// match block! {
|
||||
// self.line_read(data).await
|
||||
// } {
|
||||
// Ok(res) => res,
|
||||
// Err(err) => Err(err),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
impl CharDevice for Pl011 {
|
||||
fn write(&self, data: &[u8]) -> Result<usize, Error> {
|
||||
self.line_write(data)
|
||||
}
|
||||
|
||||
fn read(&self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
match block! {
|
||||
self.line_read(data).await
|
||||
} {
|
||||
Ok(res) => res,
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
|
||||
match req {
|
||||
&mut DeviceRequest::SetTerminalGroup(id) => {
|
||||
self.set_signal_group(id);
|
||||
Ok(())
|
||||
}
|
||||
DeviceRequest::SetTerminalOptions(config) => self.context.set_config(config),
|
||||
DeviceRequest::GetTerminalOptions(config) => {
|
||||
config.write(self.context.config());
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::InvalidArgument),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerialDevice for Pl011 {
|
||||
fn send(&self, byte: u8) -> Result<(), Error> {
|
||||
self.inner.get().lock().send_byte(byte)
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptHandler for Pl011 {
|
||||
fn handle_irq(&self, _vector: Option<usize>) -> bool {
|
||||
let inner = self.inner.get().lock();
|
||||
inner.regs.ICR.write(ICR::ALL::CLEAR);
|
||||
let inner = self.inner.get();
|
||||
let byte = inner.output().handle_irq();
|
||||
|
||||
let byte = inner.regs.DR.get();
|
||||
drop(inner);
|
||||
|
||||
// if byte == b'\x1b' as u32 {
|
||||
// use crate::task::sched::CpuQueue;
|
||||
|
||||
// for (i, queue) in CpuQueue::all().enumerate() {
|
||||
// log_print_raw!(LogLevel::Fatal, "queue{}:\n", i);
|
||||
// let lock = unsafe { queue.grab() };
|
||||
// for item in lock.iter() {
|
||||
// log_print_raw!(
|
||||
// LogLevel::Fatal,
|
||||
// "* {} {:?} {:?}\n",
|
||||
// item.id(),
|
||||
// item.name(),
|
||||
// item.state()
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
self.recv_byte(byte as u8);
|
||||
// }
|
||||
inner.write_to_input(byte as u8);
|
||||
|
||||
true
|
||||
}
|
||||
@ -209,15 +142,24 @@ impl Device for Pl011 {
|
||||
}
|
||||
|
||||
unsafe fn init(&'static self) -> Result<(), Error> {
|
||||
let mut inner = Pl011Inner {
|
||||
let mut io = Io {
|
||||
regs: DeviceMemoryIo::map(self.base, Default::default())?,
|
||||
};
|
||||
inner.init();
|
||||
io.init();
|
||||
|
||||
self.inner.init(IrqSafeSpinlock::new(inner));
|
||||
let input = TerminalInput::with_capacity(64)?;
|
||||
let output = Pl011Inner {
|
||||
io: IrqSafeSpinlock::new(io),
|
||||
};
|
||||
|
||||
self.inner.init(Terminal::from_parts(
|
||||
TerminalOptions::const_default(),
|
||||
input,
|
||||
output,
|
||||
));
|
||||
|
||||
debug::add_sink(self, LogLevel::Debug);
|
||||
devfs::add_char_device(self, CharDeviceType::TtySerial)?;
|
||||
devfs::add_char_device(self.inner.get(), CharDeviceType::TtySerial)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -226,7 +168,10 @@ impl Device for Pl011 {
|
||||
let intc = external_interrupt_controller();
|
||||
|
||||
intc.register_irq(self.irq, Default::default(), self)?;
|
||||
self.inner.get().lock().regs.IMSC.modify(IMSC::RXIM::SET);
|
||||
{
|
||||
let io = self.inner.get().output().io.lock();
|
||||
io.regs.IMSC.modify(IMSC::RXIM::SET);
|
||||
}
|
||||
intc.enable_irq(self.irq)?;
|
||||
|
||||
Ok(())
|
||||
@ -243,7 +188,6 @@ device_tree_driver! {
|
||||
inner: OneTimeInit::new(),
|
||||
// TODO obtain IRQ from dt
|
||||
irq: Irq::External(1),
|
||||
context: TtyContext::new(),
|
||||
base: PhysicalAddress::from_u64(base)
|
||||
}))
|
||||
}
|
||||
|
@ -1,309 +0,0 @@
|
||||
//! 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::Process;
|
||||
use libk_util::{ring::RingBuffer, sync::IrqSafeSpinlock, waker::QueueWaker};
|
||||
|
||||
struct TerminalRing {
|
||||
buffer: IrqSafeSpinlock<RingBuffer<u8>>,
|
||||
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 poll(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.notify.register(cx.waker());
|
||||
if self.buffer.lock().is_readable() {
|
||||
self.notify.remove(cx.waker());
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
fn read_blocking(&self) -> impl Future<Output = Option<u8>> + '_ {
|
||||
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());
|
||||
Poll::Ready(Some(unsafe { lock.read_single_unchecked() }))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
F { ring: self }
|
||||
}
|
||||
}
|
||||
|
||||
struct TtyContextInner {
|
||||
config: TerminalOptions,
|
||||
process_group: Option<ProcessGroupId>,
|
||||
}
|
||||
|
||||
/// Represents the context of a terminal device
|
||||
pub struct TtyContext {
|
||||
ring: TerminalRing,
|
||||
inner: IrqSafeSpinlock<TtyContextInner>,
|
||||
}
|
||||
|
||||
// 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);
|
||||
Process::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<usize, Error> {
|
||||
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<usize, Error> {
|
||||
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<usize, Error> {
|
||||
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<u8> {
|
||||
self.ring.read_blocking().await
|
||||
}
|
||||
|
||||
/// Polls TTY for input readiness
|
||||
pub fn poll(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.ring.poll(cx)
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user