dev: rework tty/pty

This commit is contained in:
Mark Poliakov 2024-07-26 21:52:28 +03:00
parent 1bd9d65a5e
commit a3d7ecd867
7 changed files with 446 additions and 732 deletions

View File

@ -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());

View File

@ -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};

View File

@ -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))
}

View 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
}

View File

@ -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());

View File

@ -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)
}))
}

View File

@ -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
}
}