From 67065835efaa52ff25a07897a08df962eff90129 Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Sat, 3 May 2025 23:31:54 +0800 Subject: [PATCH] Redefine the TTY driver interface --- kernel/comps/framebuffer/src/console.rs | 2 +- kernel/comps/virtio/src/device/block/mod.rs | 2 +- kernel/comps/virtio/src/device/console/mod.rs | 2 +- kernel/comps/virtio/src/device/input/mod.rs | 2 +- kernel/comps/virtio/src/device/network/mod.rs | 2 +- kernel/comps/virtio/src/device/socket/mod.rs | 3 +- kernel/src/device/mod.rs | 21 +- kernel/src/device/pty/driver.rs | 98 +++++ kernel/src/device/pty/master.rs | 155 ++++++++ kernel/src/device/pty/mod.rs | 7 +- kernel/src/device/pty/pty.rs | 347 ------------------ kernel/src/device/tty/driver.rs | 102 +---- kernel/src/device/tty/line_discipline.rs | 26 +- kernel/src/device/tty/mod.rs | 139 ++++--- kernel/src/device/tty/n_tty.rs | 77 ++++ kernel/src/process/process/init_proc.rs | 2 +- kernel/src/process/process/mod.rs | 5 +- 17 files changed, 469 insertions(+), 523 deletions(-) create mode 100644 kernel/src/device/pty/driver.rs create mode 100644 kernel/src/device/pty/master.rs delete mode 100644 kernel/src/device/pty/pty.rs create mode 100644 kernel/src/device/tty/n_tty.rs diff --git a/kernel/comps/framebuffer/src/console.rs b/kernel/comps/framebuffer/src/console.rs index 5bc3a58e0..f39fdfd44 100644 --- a/kernel/comps/framebuffer/src/console.rs +++ b/kernel/comps/framebuffer/src/console.rs @@ -24,7 +24,7 @@ pub struct FramebufferConsole { state: SpinLock, } -pub static CONSOLE_NAME: &str = "Framebuffer-Console"; +pub const CONSOLE_NAME: &str = "Framebuffer-Console"; pub static FRAMEBUFFER_CONSOLE: Once> = Once::new(); diff --git a/kernel/comps/virtio/src/device/block/mod.rs b/kernel/comps/virtio/src/device/block/mod.rs index 45a373e5e..bdbe72453 100644 --- a/kernel/comps/virtio/src/device/block/mod.rs +++ b/kernel/comps/virtio/src/device/block/mod.rs @@ -12,7 +12,7 @@ use ostd::Pod; use crate::transport::{ConfigManager, VirtioTransport}; -pub static DEVICE_NAME: &str = "Virtio-Block"; +pub const DEVICE_NAME: &str = "Virtio-Block"; bitflags! { /// features for virtio block device diff --git a/kernel/comps/virtio/src/device/console/mod.rs b/kernel/comps/virtio/src/device/console/mod.rs index b5a8fce11..bc8328d5f 100644 --- a/kernel/comps/virtio/src/device/console/mod.rs +++ b/kernel/comps/virtio/src/device/console/mod.rs @@ -3,4 +3,4 @@ pub mod config; pub mod device; -pub static DEVICE_NAME: &str = "Virtio-Console"; +pub const DEVICE_NAME: &str = "Virtio-Console"; diff --git a/kernel/comps/virtio/src/device/input/mod.rs b/kernel/comps/virtio/src/device/input/mod.rs index 5dc793805..e804fc087 100644 --- a/kernel/comps/virtio/src/device/input/mod.rs +++ b/kernel/comps/virtio/src/device/input/mod.rs @@ -32,7 +32,7 @@ use ostd::{io::IoMem, Pod}; use crate::transport::VirtioTransport; -pub static DEVICE_NAME: &str = "Virtio-Input"; +pub const DEVICE_NAME: &str = "Virtio-Input"; /// Select value used for [`device::InputDevice::query_config_select()`]. #[repr(u8)] diff --git a/kernel/comps/virtio/src/device/network/mod.rs b/kernel/comps/virtio/src/device/network/mod.rs index 7940c4438..32bd6a450 100644 --- a/kernel/comps/virtio/src/device/network/mod.rs +++ b/kernel/comps/virtio/src/device/network/mod.rs @@ -4,4 +4,4 @@ pub mod config; pub mod device; pub mod header; -pub static DEVICE_NAME: &str = "Virtio-Net"; +pub const DEVICE_NAME: &str = "Virtio-Net"; diff --git a/kernel/comps/virtio/src/device/socket/mod.rs b/kernel/comps/virtio/src/device/socket/mod.rs index 652a3cfa2..d2a5ac4fb 100644 --- a/kernel/comps/virtio/src/device/socket/mod.rs +++ b/kernel/comps/virtio/src/device/socket/mod.rs @@ -14,7 +14,8 @@ pub mod device; pub mod error; pub mod header; -pub static DEVICE_NAME: &str = "Virtio-Vsock"; +pub const DEVICE_NAME: &str = "Virtio-Vsock"; + pub trait VsockDeviceIrqHandler = Fn() + Send + Sync + 'static; pub fn register_device(name: String, device: Arc>) { diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index 94c1cded1..1cb82cb14 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -11,11 +11,12 @@ mod zero; #[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))] mod tdxguest; +use alloc::format; + pub use pty::{new_pty_pair, PtyMaster, PtySlave}; pub use random::Random; pub use urandom::Urandom; -use self::tty::get_n_tty; use crate::{ fs::device::{add_node, Device, DeviceId, DeviceType}, prelude::*, @@ -25,23 +26,37 @@ use crate::{ pub fn init() -> Result<()> { let null = Arc::new(null::Null); add_node(null, "null")?; + let zero = Arc::new(zero::Zero); add_node(zero, "zero")?; + tty::init(); - let console = get_n_tty().clone(); - add_node(console, "console")?; + let tty = Arc::new(tty::TtyDevice); add_node(tty, "tty")?; + + let console = tty::system_console().clone(); + add_node(console, "console")?; + + for (index, tty) in tty::iter_n_tty().enumerate() { + add_node(tty.clone(), &format!("tty{}", index))?; + } + #[cfg(target_arch = "x86_64")] ostd::if_tdx_enabled!({ add_node(Arc::new(tdxguest::TdxGuest), "tdx_guest")?; }); + let random = Arc::new(random::Random); add_node(random, "random")?; + let urandom = Arc::new(urandom::Urandom); add_node(urandom, "urandom")?; + pty::init()?; + shm::init()?; + Ok(()) } diff --git a/kernel/src/device/pty/driver.rs b/kernel/src/device/pty/driver.rs new file mode 100644 index 000000000..27d500b8f --- /dev/null +++ b/kernel/src/device/pty/driver.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::sync::SpinLock; + +use crate::{ + device::tty::{Tty, TtyDriver}, + events::IoEvents, + prelude::{return_errno_with_message, Errno, Result}, + process::signal::Pollee, + util::ring_buffer::RingBuffer, +}; + +const BUFFER_CAPACITY: usize = 4096; + +/// A pseudoterminal driver. +/// +/// This is contained in the PTY slave, but it maintains the output buffer and the pollee of the +/// master. The pollee of the slave is part of the [`Tty`] structure (see the definition of +/// [`PtySlave`]). +pub struct PtyDriver { + output: SpinLock>, + pollee: Pollee, +} + +/// A pseudoterminal slave. +pub type PtySlave = Tty; + +impl PtyDriver { + pub(super) fn new() -> Self { + Self { + output: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)), + pollee: Pollee::new(), + } + } + + pub(super) fn try_read(&self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut output = self.output.lock(); + if output.is_empty() { + return_errno_with_message!(Errno::EAGAIN, "the buffer is empty"); + } + + let read_len = output.len().min(buf.len()); + output.pop_slice(&mut buf[..read_len]).unwrap(); + + Ok(read_len) + } + + pub(super) fn pollee(&self) -> &Pollee { + &self.pollee + } + + pub(super) fn buffer_len(&self) -> usize { + self.output.lock().len() + } +} + +impl TtyDriver for PtyDriver { + fn push_output(&self, chs: &[u8]) { + let mut output = self.output.lock(); + + for ch in chs { + // TODO: This is termios-specific behavior and should be part of the TTY implementation + // instead of the TTY driver implementation. See the ONLCR flag for more details. + if *ch == b'\n' { + output.push_overwrite(b'\r'); + output.push_overwrite(b'\n'); + continue; + } + output.push_overwrite(*ch); + } + self.pollee.notify(IoEvents::IN); + } + + fn drain_output(&self) { + self.output.lock().clear(); + self.pollee.invalidate(); + } + + fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ { + let mut output = self.output.lock(); + let mut has_notified = false; + + move |chs| { + for ch in chs { + output.push_overwrite(*ch); + } + + if !has_notified { + self.pollee.notify(IoEvents::IN); + has_notified = true; + } + } + } +} diff --git a/kernel/src/device/pty/master.rs b/kernel/src/device/pty/master.rs new file mode 100644 index 000000000..1fb199d2f --- /dev/null +++ b/kernel/src/device/pty/master.rs @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::format; + +use ostd::task::Task; + +use super::{driver::PtyDriver, PtySlave}; +use crate::{ + current_userspace, + events::IoEvents, + fs::{ + devpts::DevPts, + file_table::FdFlags, + fs_resolver::FsPath, + inode_handle::FileIo, + utils::{AccessMode, Inode, InodeMode, IoctlCmd}, + }, + prelude::*, + process::{ + posix_thread::{AsPosixThread, AsThreadLocal}, + signal::{PollHandle, Pollable}, + Terminal, + }, +}; + +const IO_CAPACITY: usize = 4096; + +/// A pseudoterminal master. +/// +/// A pseudoterminal contains two buffers: +/// * The input buffer is written by the master and read by the slave, which is maintained in the +/// line discipline (part of [`PtySlave`], which is a [`Tty`]). +/// * The output buffer is written by the slave and read by the master, which is maintained in the +/// driver (i.e., [`PtyDriver`]). +/// +/// [`Tty`]: crate::device::tty::Tty +pub struct PtyMaster { + ptmx: Arc, + slave: Arc, +} + +impl PtyMaster { + pub(super) fn new(ptmx: Arc, index: u32) -> Arc { + let slave = PtySlave::new(index, PtyDriver::new()); + + Arc::new(Self { ptmx, slave }) + } + + pub(super) fn slave(&self) -> &Arc { + &self.slave + } + + fn check_io_events(&self) -> IoEvents { + if self.slave().driver().buffer_len() > 0 { + IoEvents::IN | IoEvents::OUT + } else { + IoEvents::OUT + } + } +} + +impl Pollable for PtyMaster { + fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { + self.slave + .driver() + .pollee() + .poll_with(mask, poller, || self.check_io_events()) + } +} + +impl FileIo for PtyMaster { + fn read(&self, writer: &mut VmWriter) -> Result { + // TODO: Add support for non-blocking mode and timeout + let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)]; + let read_len = self.wait_events(IoEvents::IN, None, || { + self.slave.driver().try_read(&mut buf) + })?; + self.slave.driver().pollee().invalidate(); + + // TODO: Confirm what we should do if `write_fallible` fails in the middle. + writer.write_fallible(&mut buf[..read_len].into())?; + Ok(read_len) + } + + fn write(&self, reader: &mut VmReader) -> Result { + let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)]; + let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?; + + self.slave.push_input(&buf[..write_len]); + Ok(write_len) + } + + fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { + match cmd { + IoctlCmd::TCGETS + | IoctlCmd::TCSETS + | IoctlCmd::TCSETSW + | IoctlCmd::TCSETSF + | IoctlCmd::TIOCGWINSZ + | IoctlCmd::TIOCSWINSZ + | IoctlCmd::TIOCGPTN => return self.slave.ioctl(cmd, arg), + IoctlCmd::TIOCSPTLCK => { + // TODO: Lock or unlock the PTY. + } + IoctlCmd::TIOCGPTPEER => { + let current_task = Task::current().unwrap(); + let posix_thread = current_task.as_posix_thread().unwrap(); + let thread_local = current_task.as_thread_local().unwrap(); + + // TODO: Deal with `open()` flags. + let slave = { + let slave_name = { + let devpts_path = super::DEV_PTS.get().unwrap().abs_path(); + format!("{}/{}", devpts_path, self.slave.index()) + }; + + let fs_path = FsPath::try_from(slave_name.as_str())?; + + let inode_handle = { + let fs = posix_thread.fs().resolver().read(); + let flags = AccessMode::O_RDWR as u32; + let mode = (InodeMode::S_IRUSR | InodeMode::S_IWUSR).bits(); + fs.open(&fs_path, flags, mode)? + }; + Arc::new(inode_handle) + }; + + let fd = { + let file_table = thread_local.borrow_file_table(); + let mut file_table_locked = file_table.unwrap().write(); + // TODO: Deal with the `O_CLOEXEC` flag. + file_table_locked.insert(slave, FdFlags::empty()) + }; + return Ok(fd); + } + IoctlCmd::FIONREAD => { + let len = self.slave.driver().buffer_len() as i32; + current_userspace!().write_val(arg, &len)?; + } + _ => (self.slave.clone() as Arc).job_ioctl(cmd, arg, true)?, + } + + Ok(0) + } +} + +impl Drop for PtyMaster { + fn drop(&mut self) { + let fs = self.ptmx.fs(); + let devpts = fs.downcast_ref::().unwrap(); + + let index = self.slave.index(); + devpts.remove_slave(index); + } +} diff --git a/kernel/src/device/pty/mod.rs b/kernel/src/device/pty/mod.rs index feb9404a3..5f310387c 100644 --- a/kernel/src/device/pty/mod.rs +++ b/kernel/src/device/pty/mod.rs @@ -10,10 +10,11 @@ use crate::{ prelude::*, }; -#[expect(clippy::module_inception)] -mod pty; +mod driver; +mod master; -pub use pty::{PtyMaster, PtySlave}; +pub use driver::PtySlave; +pub use master::PtyMaster; use spin::Once; static DEV_PTS: Once = Once::new(); diff --git a/kernel/src/device/pty/pty.rs b/kernel/src/device/pty/pty.rs deleted file mode 100644 index 003c3cb31..000000000 --- a/kernel/src/device/pty/pty.rs +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -use alloc::format; - -use ostd::task::Task; - -use crate::{ - current_userspace, - device::tty::line_discipline::LineDiscipline, - events::IoEvents, - fs::{ - device::{Device, DeviceId, DeviceType}, - devpts::DevPts, - file_table::FdFlags, - fs_resolver::FsPath, - inode_handle::FileIo, - utils::{AccessMode, Inode, InodeMode, IoctlCmd}, - }, - prelude::*, - process::{ - broadcast_signal_async, - posix_thread::{AsPosixThread, AsThreadLocal}, - signal::{PollHandle, Pollable, Pollee}, - JobControl, Terminal, - }, - util::ring_buffer::RingBuffer, -}; - -const BUFFER_CAPACITY: usize = 4096; -const IO_CAPACITY: usize = 4096; - -/// Pseudo terminal master. -/// Internally, it has two buffers. -/// One is inside ldisc, which is written by master and read by slave, -/// the other is a ring buffer, which is written by slave and read by master. -pub struct PtyMaster { - ptmx: Arc, - index: u32, - slave: Arc, - input: SpinLock>, - pollee: Pollee, -} - -impl PtyMaster { - pub fn new(ptmx: Arc, index: u32) -> Arc { - Arc::new_cyclic(move |master| { - let slave = Arc::new_cyclic(move |weak_self| PtySlave { - ldisc: SpinLock::new(LineDiscipline::new()), - job_control: JobControl::new(), - pollee: Pollee::new(), - master: master.clone(), - weak_self: weak_self.clone(), - }); - - PtyMaster { - ptmx, - index, - slave, - input: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)), - pollee: Pollee::new(), - } - }) - } - - pub fn index(&self) -> u32 { - self.index - } - - pub fn ptmx(&self) -> &Arc { - &self.ptmx - } - - pub fn slave(&self) -> &Arc { - &self.slave - } - - fn slave_push(&self, chs: &[u8]) { - let mut input = self.input.lock(); - - for ch in chs { - // TODO: This is termios-specific behavior and should be part of the TTY implementation - // instead of the TTY driver implementation. See the ONLCR flag for more details. - if *ch == b'\n' { - input.push_overwrite(b'\r'); - input.push_overwrite(b'\n'); - continue; - } - input.push_overwrite(*ch); - } - self.pollee.notify(IoEvents::IN); - } - - fn slave_echo(&self) -> impl FnMut(&str) + '_ { - let mut input = self.input.lock(); - let mut has_notified = false; - - move |content| { - for byte in content.as_bytes() { - input.push_overwrite(*byte); - } - - if !has_notified { - self.pollee.notify(IoEvents::IN); - has_notified = true; - } - } - } - - fn try_read(&self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - - let mut input = self.input.lock(); - if input.is_empty() { - return_errno_with_message!(Errno::EAGAIN, "the buffer is empty"); - } - - let read_len = input.len().min(buf.len()); - input.pop_slice(&mut buf[..read_len]).unwrap(); - self.pollee.invalidate(); - - Ok(read_len) - } - - fn check_io_events(&self) -> IoEvents { - let input = self.input.lock(); - - if !input.is_empty() { - IoEvents::IN | IoEvents::OUT - } else { - IoEvents::OUT - } - } -} - -impl Pollable for PtyMaster { - fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { - self.pollee - .poll_with(mask, poller, || self.check_io_events()) - } -} - -impl FileIo for PtyMaster { - fn read(&self, writer: &mut VmWriter) -> Result { - // TODO: Add support for non-blocking mode and timeout - let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)]; - let read_len = self.wait_events(IoEvents::IN, None, || self.try_read(&mut buf))?; - - writer.write_fallible(&mut buf[..read_len].into())?; - Ok(read_len) - } - - fn write(&self, reader: &mut VmReader) -> Result { - let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)]; - let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?; - - self.slave.master_push(&buf[..write_len]); - Ok(write_len) - } - - fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { - match cmd { - IoctlCmd::TCGETS - | IoctlCmd::TCSETS - | IoctlCmd::TIOCGPTN - | IoctlCmd::TIOCGWINSZ - | IoctlCmd::TIOCSWINSZ => return self.slave.ioctl(cmd, arg), - IoctlCmd::TIOCSPTLCK => { - // TODO: lock/unlock pty - } - IoctlCmd::TIOCGPTPEER => { - let current_task = Task::current().unwrap(); - let posix_thread = current_task.as_posix_thread().unwrap(); - let thread_local = current_task.as_thread_local().unwrap(); - - // TODO: deal with open options - let slave = { - let slave_name = { - let devpts_path = super::DEV_PTS.get().unwrap().abs_path(); - format!("{}/{}", devpts_path, self.index()) - }; - - let fs_path = FsPath::try_from(slave_name.as_str())?; - - let inode_handle = { - let fs = posix_thread.fs().resolver().read(); - let flags = AccessMode::O_RDWR as u32; - let mode = (InodeMode::S_IRUSR | InodeMode::S_IWUSR).bits(); - fs.open(&fs_path, flags, mode)? - }; - Arc::new(inode_handle) - }; - - let fd = { - let file_table = thread_local.borrow_file_table(); - let mut file_table_locked = file_table.unwrap().write(); - // TODO: deal with the O_CLOEXEC flag - file_table_locked.insert(slave, FdFlags::empty()) - }; - return Ok(fd); - } - IoctlCmd::FIONREAD => { - let len = self.input.lock().len() as i32; - current_userspace!().write_val(arg, &len)?; - } - _ => (self.slave.clone() as Arc).job_ioctl(cmd, arg, true)?, - } - - Ok(0) - } -} - -impl Drop for PtyMaster { - fn drop(&mut self) { - let fs = self.ptmx.fs(); - let devpts = fs.downcast_ref::().unwrap(); - - let index = self.index; - devpts.remove_slave(index); - } -} - -pub struct PtySlave { - ldisc: SpinLock, - job_control: JobControl, - pollee: Pollee, - master: Weak, - weak_self: Weak, -} - -impl PtySlave { - pub fn index(&self) -> u32 { - self.master().index() - } - - fn master(&self) -> Arc { - self.master.upgrade().unwrap() - } - - fn master_push(&self, chs: &[u8]) { - let mut ldisc = self.ldisc.lock(); - - let master = self.master(); - let mut echo = master.slave_echo(); - - for ch in chs { - ldisc.push_char( - *ch, - |signum| { - if let Some(foreground) = self.job_control.foreground() { - broadcast_signal_async(Arc::downgrade(&foreground), signum); - } - }, - &mut echo, - ); - } - self.pollee.notify(IoEvents::IN); - } - - fn check_io_events(&self) -> IoEvents { - if self.ldisc.lock().buffer_len() != 0 { - IoEvents::IN | IoEvents::OUT - } else { - IoEvents::OUT - } - } -} - -impl Device for PtySlave { - fn type_(&self) -> DeviceType { - DeviceType::CharDevice - } - - fn id(&self) -> crate::fs::device::DeviceId { - DeviceId::new(88, self.index()) - } -} - -impl Terminal for PtySlave { - fn job_control(&self) -> &JobControl { - &self.job_control - } -} - -impl Pollable for PtySlave { - fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { - self.pollee - .poll_with(mask, poller, || self.check_io_events()) - } -} - -impl FileIo for PtySlave { - fn read(&self, writer: &mut VmWriter) -> Result { - self.job_control.wait_until_in_foreground()?; - - // TODO: Add support for non-blocking mode and timeout - let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)]; - let read_len = - self.wait_events(IoEvents::IN, None, || self.ldisc.lock().try_read(&mut buf))?; - self.pollee.invalidate(); - - writer.write_fallible(&mut buf[..read_len].into())?; - Ok(read_len) - } - - fn write(&self, reader: &mut VmReader) -> Result { - let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)]; - let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?; - - self.master().slave_push(&buf[..write_len]); - Ok(write_len) - } - - fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { - match cmd { - IoctlCmd::TCGETS => { - let termios = *self.ldisc.lock().termios(); - current_userspace!().write_val(arg, &termios)?; - } - IoctlCmd::TCSETS => { - let termios = current_userspace!().read_val(arg)?; - self.ldisc.lock().set_termios(termios); - } - IoctlCmd::TIOCGPTN => { - let idx = self.index(); - current_userspace!().write_val(arg, &idx)?; - } - IoctlCmd::TIOCGWINSZ => { - let winsize = self.ldisc.lock().window_size(); - current_userspace!().write_val(arg, &winsize)?; - } - IoctlCmd::TIOCSWINSZ => { - let winsize = current_userspace!().read_val(arg)?; - self.ldisc.lock().set_window_size(winsize); - } - IoctlCmd::FIONREAD => { - let buffer_len = self.ldisc.lock().buffer_len() as i32; - current_userspace!().write_val(arg, &buffer_len)?; - } - _ => (self.weak_self.upgrade().unwrap() as Arc) - .job_ioctl(cmd, arg, false)?, - } - - Ok(0) - } -} diff --git a/kernel/src/device/tty/driver.rs b/kernel/src/device/tty/driver.rs index d72378ae6..7974b853d 100644 --- a/kernel/src/device/tty/driver.rs +++ b/kernel/src/device/tty/driver.rs @@ -1,89 +1,23 @@ // SPDX-License-Identifier: MPL-2.0 -use ostd::mm::{Infallible, VmReader}; -use spin::Once; +/// A TTY driver. +/// +/// A driver exposes some device-specific behavior to [`Tty`]. For example, a device provides +/// methods to write to the output buffer (see [`Self::push_output`]), where the output buffer can +/// be the monitor if the underlying device is framebuffer, or just a ring buffer if the underlying +/// device is pseduoterminal). +/// +/// [`Tty`]: super::Tty +pub trait TtyDriver: Send + Sync + 'static { + /// Pushes characters into the output buffer. + fn push_output(&self, chs: &[u8]); -use crate::{ - device::tty::{get_n_tty, Tty}, - prelude::*, -}; + /// Drains the output buffer. + fn drain_output(&self); -pub static TTY_DRIVER: Once> = Once::new(); - -pub(super) fn init() { - for (_, device) in aster_console::all_devices() { - device.register_callback(&console_input_callback) - } - let tty_driver = Arc::new(TtyDriver::new()); - // FIXME: install n_tty into tty_driver? - let n_tty = get_n_tty(); - tty_driver.install(n_tty.clone()); - TTY_DRIVER.call_once(|| tty_driver); -} - -pub struct TtyDriver { - ttys: SpinLock>>, -} - -impl TtyDriver { - pub const fn new() -> Self { - Self { - ttys: SpinLock::new(Vec::new()), - } - } - - /// Return the tty device in driver's internal table. - pub fn lookup(&self, index: usize) -> Result> { - let ttys = self.ttys.disable_irq().lock(); - // Return the tty device corresponding to idx - if index >= ttys.len() { - return_errno_with_message!(Errno::ENODEV, "lookup failed. No tty device"); - } - let tty = ttys[index].clone(); - drop(ttys); - Ok(tty) - } - - /// Install a new tty into the driver's internal tables. - pub fn install(self: &Arc, tty: Arc) { - tty.set_driver(Arc::downgrade(self)); - self.ttys.disable_irq().lock().push(tty); - } - - /// remove a new tty into the driver's internal tables. - pub fn remove(&self, index: usize) -> Result<()> { - let mut ttys = self.ttys.disable_irq().lock(); - if index >= ttys.len() { - return_errno_with_message!(Errno::ENODEV, "lookup failed. No tty device"); - } - let removed_tty = ttys.remove(index); - removed_tty.set_driver(Weak::new()); - drop(ttys); - Ok(()) - } - - pub fn push_char(&self, ch: u8) { - // FIXME: should the char send to all ttys? - for tty in &*self.ttys.disable_irq().lock() { - tty.push_char(ch); - } - } -} - -impl Default for TtyDriver { - fn default() -> Self { - Self::new() - } -} - -fn console_input_callback(mut reader: VmReader) { - let tty_driver = get_tty_driver(); - while reader.remain() > 0 { - let ch = reader.read_val().unwrap(); - tty_driver.push_char(ch); - } -} - -fn get_tty_driver() -> &'static TtyDriver { - TTY_DRIVER.get().unwrap() + /// Returns a callback function that echoes input characters to the output buffer. + /// + /// Note that the implementation may choose to hold a lock during the life of the callback. + /// During this time, calls to other methods such as [`Self::push_output`] may cause deadlocks. + fn echo_callback(&self) -> impl FnMut(&[u8]) + '_; } diff --git a/kernel/src/device/tty/line_discipline.rs b/kernel/src/device/tty/line_discipline.rs index fe6e5e427..cc6ddff3e 100644 --- a/kernel/src/device/tty/line_discipline.rs +++ b/kernel/src/device/tty/line_discipline.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -use alloc::format; - use super::termio::{KernelTermios, WinSize, CC_C_CHAR}; use crate::{ prelude::*, @@ -61,10 +59,6 @@ impl CurrentLine { pub fn is_full(&self) -> bool { self.buffer.is_full() } - - pub fn is_empty(&self) -> bool { - self.buffer.is_empty() - } } impl LineDiscipline { @@ -79,7 +73,7 @@ impl LineDiscipline { } /// Pushes a character to the line discipline. - pub fn push_char( + pub fn push_char( &mut self, ch: u8, mut signal_callback: F1, @@ -135,19 +129,17 @@ impl LineDiscipline { } // TODO: respect output flags - fn output_char(&self, ch: u8, mut echo_callback: F) { + fn output_char(&self, ch: u8, mut echo_callback: F) { match ch { - b'\n' => echo_callback("\n"), - b'\r' => echo_callback("\r\n"), + b'\n' => echo_callback(b"\n"), + b'\r' => echo_callback(b"\r\n"), ch if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) => { // Write a space to overwrite the current character - let backspace: &str = core::str::from_utf8(b"\x08 \x08").unwrap(); - echo_callback(backspace); + echo_callback(b"\x08 \x08"); } - ch if is_printable_char(ch) => print!("{}", char::from(ch)), + ch if is_printable_char(ch) => echo_callback(&[ch]), ch if is_ctrl_char(ch) && self.termios.contains_echo_ctl() => { - let ctrl_char = format!("^{}", ctrl_char_to_printable(ch)); - echo_callback(&ctrl_char); + echo_callback(&[b'^', ctrl_char_to_printable(ch)]); } _ => {} } @@ -267,7 +259,7 @@ fn char_to_signal(ch: u8, termios: &KernelTermios) -> Option { } } -fn ctrl_char_to_printable(ch: u8) -> char { +fn ctrl_char_to_printable(ch: u8) -> u8 { debug_assert!(is_ctrl_char(ch)); - char::from_u32((ch + b'A' - 1) as u32).unwrap() + ch + b'A' - 1 } diff --git a/kernel/src/device/tty/mod.rs b/kernel/src/device/tty/mod.rs index 2cd6e6b84..c62d7513f 100644 --- a/kernel/src/device/tty/mod.rs +++ b/kernel/src/device/tty/mod.rs @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 -use ostd::{early_print, sync::LocalIrqDisabled}; -use spin::Once; +use ostd::sync::LocalIrqDisabled; -use self::{driver::TtyDriver, line_discipline::LineDiscipline}; +use self::line_discipline::LineDiscipline; use crate::{ current_userspace, events::IoEvents, @@ -21,65 +20,65 @@ use crate::{ }; mod device; -pub mod driver; -pub mod line_discipline; -pub mod termio; +mod driver; +mod line_discipline; +mod n_tty; +mod termio; pub use device::TtyDevice; - -static N_TTY: Once> = Once::new(); - -pub(super) fn init() { - let name = CString::new("console").unwrap(); - let tty = Tty::new(name); - N_TTY.call_once(|| tty); - driver::init(); -} +pub use driver::TtyDriver; +pub(super) use n_tty::init; +pub use n_tty::{iter_n_tty, system_console}; const IO_CAPACITY: usize = 4096; -pub struct Tty { - /// tty_name - #[expect(unused)] - name: CString, - /// line discipline +/// A teletyper (TTY). +/// +/// This abstracts the general functionality of a TTY in a way that +/// - Any input device driver can use [`Tty::push_input`] to push input characters, and users can +/// [`Tty::read`] from the TTY; +/// - Users can also [`Tty::write`] output characters to the TTY and the output device driver will +/// receive the characters from [`TtyDriver::push_output`] where the generic parameter `D` is +/// the [`TtyDriver`]. +/// +/// ```text +/// +------------+ +-------------+ +/// |input device| |output device| +/// | driver | | driver | +/// +-----+------+ +------^------+ +/// | | +/// | +-------+ | +/// +-----> TTY +-----+ +/// +-------+ +/// Tty::push_input D::push_output +/// ``` +pub struct Tty { + index: u32, + driver: D, ldisc: SpinLock, job_control: JobControl, pollee: Pollee, - /// driver - driver: SpinLock>, weak_self: Weak, } -impl Tty { - pub fn new(name: CString) -> Arc { +impl Tty { + pub fn new(index: u32, driver: D) -> Arc { Arc::new_cyclic(move |weak_ref| Tty { - name, + index, + driver, ldisc: SpinLock::new(LineDiscipline::new()), job_control: JobControl::new(), pollee: Pollee::new(), - driver: SpinLock::new(Weak::new()), weak_self: weak_ref.clone(), }) } - pub fn set_driver(&self, driver: Weak) { - *self.driver.disable_irq().lock() = driver; + pub fn index(&self) -> u32 { + self.index } - pub fn push_char(&self, ch: u8) { - // FIXME: Use `early_print` to avoid calling virtio-console. - // This is only a workaround - self.ldisc.lock().push_char( - ch, - |signum| { - if let Some(foreground) = self.job_control.foreground() { - broadcast_signal_async(Arc::downgrade(&foreground), signum); - } - }, - |content| early_print!("{}", content), - ); - self.pollee.notify(IoEvents::IN); + pub fn driver(&self) -> &D { + &self.driver } fn check_io_events(&self) -> IoEvents { @@ -91,14 +90,34 @@ impl Tty { } } -impl Pollable for Tty { +impl Tty { + pub fn push_input(&self, chs: &[u8]) { + let mut ldisc = self.ldisc.lock(); + let mut echo = self.driver.echo_callback(); + + for ch in chs { + ldisc.push_char( + *ch, + |signum| { + if let Some(foreground) = self.job_control.foreground() { + broadcast_signal_async(Arc::downgrade(&foreground), signum); + } + }, + &mut echo, + ); + } + self.pollee.notify(IoEvents::IN); + } +} + +impl Pollable for Tty { fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { self.pollee .poll_with(mask, poller, || self.check_io_events()) } } -impl FileIo for Tty { +impl FileIo for Tty { fn read(&self, writer: &mut VmWriter) -> Result { self.job_control.wait_until_in_foreground()?; @@ -117,11 +136,7 @@ impl FileIo for Tty { let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)]; let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?; - if let Ok(content) = alloc::str::from_utf8(&buf[..write_len]) { - print!("{content}"); - } else { - println!("Not utf-8 content: {:?}", buf); - } + self.driver.push_output(&buf[..write_len]); Ok(write_len) } @@ -140,8 +155,9 @@ impl FileIo for Tty { IoctlCmd::TCSETSW => { let termios = current_userspace!().read_val(arg)?; - self.ldisc.lock().set_termios(termios); - // TODO: Drain the output buffer + let mut ldisc = self.ldisc.lock(); + ldisc.set_termios(termios); + self.driver.drain_output(); } IoctlCmd::TCSETSF => { let termios = current_userspace!().read_val(arg)?; @@ -149,7 +165,7 @@ impl FileIo for Tty { let mut ldisc = self.ldisc.lock(); ldisc.set_termios(termios); ldisc.drain_input(); - // TODO: Drain the output buffer + self.driver.drain_output(); self.pollee.invalidate(); } @@ -163,6 +179,16 @@ impl FileIo for Tty { self.ldisc.lock().set_window_size(winsize); } + IoctlCmd::TIOCGPTN => { + let idx = self.index; + + current_userspace!().write_val(arg, &idx)?; + } + IoctlCmd::FIONREAD => { + let buffer_len = self.ldisc.lock().buffer_len() as u32; + + current_userspace!().write_val(arg, &buffer_len)?; + } _ => (self.weak_self.upgrade().unwrap() as Arc) .job_ioctl(cmd, arg, false)?, } @@ -171,23 +197,18 @@ impl FileIo for Tty { } } -impl Terminal for Tty { +impl Terminal for Tty { fn job_control(&self) -> &JobControl { &self.job_control } } -impl Device for Tty { +impl Device for Tty { fn type_(&self) -> DeviceType { DeviceType::CharDevice } fn id(&self) -> DeviceId { - // The same value as /dev/console in linux. - DeviceId::new(88, 0) + DeviceId::new(88, self.index) } } - -pub fn get_n_tty() -> &'static Arc { - N_TTY.get().unwrap() -} diff --git a/kernel/src/device/tty/n_tty.rs b/kernel/src/device/tty/n_tty.rs new file mode 100644 index 000000000..ced128a3a --- /dev/null +++ b/kernel/src/device/tty/n_tty.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{boxed::Box, sync::Arc, vec}; + +use aster_console::AnyConsoleDevice; +use ostd::mm::{Infallible, VmReader, VmWriter}; +use spin::Once; + +use super::{Tty, TtyDriver}; + +pub struct ConsoleDriver { + console: Arc, +} + +impl TtyDriver for ConsoleDriver { + fn push_output(&self, chs: &[u8]) { + self.console.send(chs); + } + + fn drain_output(&self) {} + + fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ { + |chs| self.console.send(chs) + } +} + +static N_TTY: Once>]>> = Once::new(); + +pub(in crate::device) fn init() { + let devices = { + let mut devices = aster_console::all_devices(); + // Sort by priorities to ensure that the TTY for the virtio-console device comes first. Is + // there a better way than hardcoding this? + devices.sort_by_key(|(name, _)| match name.as_str() { + aster_virtio::device::console::DEVICE_NAME => 0, + aster_framebuffer::CONSOLE_NAME => 1, + _ => 2, + }); + devices + }; + + let ttys = devices + .into_iter() + .enumerate() + .map(|(index, (_, device))| create_n_tty(index as _, device)) + .collect(); + N_TTY.call_once(|| ttys); +} + +fn create_n_tty(index: u32, device: Arc) -> Arc> { + let driver = ConsoleDriver { + console: device.clone(), + }; + + let tty = Tty::new(index, driver); + let tty_cloned = tty.clone(); + + device.register_callback(Box::leak(Box::new( + move |mut reader: VmReader| { + let mut chs = vec![0u8; reader.remain()]; + reader.read(&mut VmWriter::from(chs.as_mut_slice())); + tty.push_input(chs.as_slice()); + }, + ))); + + tty_cloned +} + +/// Returns the system console, i.e., `/dev/console`. +pub fn system_console() -> &'static Arc> { + &N_TTY.get().unwrap()[0] +} + +/// Iterates all TTY devices, i.e., `/dev/tty1`, `/dev/tty2`, e.t.c. +pub fn iter_n_tty() -> impl Iterator>> { + N_TTY.get().unwrap().iter() +} diff --git a/kernel/src/process/process/init_proc.rs b/kernel/src/process/process/init_proc.rs index 91b8f6556..68ff665d3 100644 --- a/kernel/src/process/process/init_proc.rs +++ b/kernel/src/process/process/init_proc.rs @@ -37,7 +37,7 @@ pub fn spawn_init_process( set_session_and_group(&process); // FIXME: This should be done by the userspace init process. - (crate::device::tty::get_n_tty().clone() as Arc).set_control(&process)?; + (crate::device::tty::system_console().clone() as Arc).set_control(&process)?; process.run(); diff --git a/kernel/src/process/process/mod.rs b/kernel/src/process/process/mod.rs index 06d4c3098..bfd24f3a6 100644 --- a/kernel/src/process/process/mod.rs +++ b/kernel/src/process/process/mod.rs @@ -731,15 +731,14 @@ pub fn enqueue_signal_async(process: Weak, signum: SigNum) { /// This is the asynchronous version of [`ProcessGroup::broadcast_signal`]. By asynchronous, this /// method submits a work item and returns, so this method doesn't sleep and can be used in atomic /// mode. -pub fn broadcast_signal_async(process_group: Weak, signal: SigNum) { +pub fn broadcast_signal_async(process_group: Weak, signum: SigNum) { use super::signal::signals::kernel::KernelSignal; use crate::thread::work_queue; - let signal = KernelSignal::new(signal); work_queue::submit_work_func( move || { if let Some(process_group) = process_group.upgrade() { - process_group.broadcast_signal(signal); + process_group.broadcast_signal(KernelSignal::new(signum)); } }, work_queue::WorkPriority::High,