diff --git a/kernel/src/device/pty/driver.rs b/kernel/src/device/pty/driver.rs index 39b430ba8..5b5fa6f86 100644 --- a/kernel/src/device/pty/driver.rs +++ b/kernel/src/device/pty/driver.rs @@ -5,7 +5,13 @@ use ostd::sync::SpinLock; use super::file::PtySlaveFile; use crate::{ - device::tty::{Tty, TtyDriver, TtyFlags}, + device::{ + pty::packet::{PacketCtrl, PacketStatus}, + tty::{ + termio::{CCtrlCharId, CInputFlags, CLocalFlags, CTermios}, + Tty, TtyDriver, TtyFlags, + }, + }, events::IoEvents, fs::inode_handle::FileIo, prelude::*, @@ -25,6 +31,7 @@ pub struct PtyDriver { pollee: Pollee, opened_slaves: SpinLock, tty_flags: TtyFlags, + packet_ctrl: PacketCtrl, } /// A pseudoterminal slave. @@ -39,6 +46,7 @@ impl PtyDriver { pollee: Pollee::new(), opened_slaves: SpinLock::new(0), tty_flags, + packet_ctrl: PacketCtrl::new(), } } @@ -47,6 +55,27 @@ impl PtyDriver { return Ok(0); } + // Reference: . + match self.packet_ctrl.take_status() { + None => { + // Packet mode is disabled. + self.read_output(buf) + } + Some(packet_status) if !packet_status.is_empty() => { + // Some packet status is pending. + buf[0] = packet_status.bits(); + Ok(1) + } + Some(_) => { + // There's no pending packet status. + let data_len = self.read_output(&mut buf[1..])?; + buf[0] = 0; + Ok(data_len + 1) + } + } + } + + fn read_output(&self, buf: &mut [u8]) -> Result { let mut output = self.output.lock(); if output.is_empty() { if self.tty_flags.is_other_closed() { @@ -57,7 +86,6 @@ impl PtyDriver { let read_len = output.len().min(buf.len()); output.pop_slice(&mut buf[..read_len]).unwrap(); - Ok(read_len) } @@ -76,6 +104,10 @@ impl PtyDriver { pub(super) fn tty_flags(&self) -> &TtyFlags { &self.tty_flags } + + pub(super) fn packet_ctrl(&self) -> &PacketCtrl { + &self.packet_ctrl + } } impl TtyDriver for PtyDriver { @@ -110,7 +142,7 @@ impl TtyDriver for PtyDriver { len += 1; } - self.pollee.notify(IoEvents::IN); + self.pollee.notify(IoEvents::IN | IoEvents::RDNORM); Ok(len) } @@ -129,7 +161,7 @@ impl TtyDriver for PtyDriver { } if !has_notified { - self.pollee.notify(IoEvents::IN); + self.pollee.notify(IoEvents::IN | IoEvents::RDNORM); has_notified = true; } } @@ -147,4 +179,40 @@ impl TtyDriver for PtyDriver { fn console(&self) -> Option<&dyn AnyConsoleDevice> { None } + + fn on_termios_change(&self, old_termios: &CTermios, new_termios: &CTermios) { + // Reference: . + let extproc = old_termios.local_flags().contains(CLocalFlags::EXTPROC) + || new_termios.local_flags().contains(CLocalFlags::EXTPROC); + let old_flow = old_termios.input_flags().contains(CInputFlags::IXON) + && old_termios.special_char(CCtrlCharId::VSTOP) == 0o23 + && old_termios.special_char(CCtrlCharId::VSTART) == 0o21; + let new_flow = new_termios.input_flags().contains(CInputFlags::IXON) + && new_termios.special_char(CCtrlCharId::VSTOP) == 0o23 + && new_termios.special_char(CCtrlCharId::VSTART) == 0o21; + + if (old_flow == new_flow) && !extproc { + return; + } + + let has_set = self.packet_ctrl.set_status(|packet_status| { + if old_flow != new_flow { + *packet_status &= !(PacketStatus::DOSTOP | PacketStatus::NOSTOP); + if new_flow { + *packet_status |= PacketStatus::DOSTOP; + } else { + *packet_status |= PacketStatus::NOSTOP; + } + } + + if extproc { + *packet_status |= PacketStatus::IOCTL; + } + }); + + if has_set { + self.pollee + .notify(IoEvents::PRI | IoEvents::IN | IoEvents::RDNORM); + } + } } diff --git a/kernel/src/device/pty/master.rs b/kernel/src/device/pty/master.rs index 5ad8f0346..41c7ade40 100644 --- a/kernel/src/device/pty/master.rs +++ b/kernel/src/device/pty/master.rs @@ -63,7 +63,7 @@ impl PtyMaster { let mut events = IoEvents::empty(); if self.slave().driver().buffer_len() > 0 { - events |= IoEvents::IN; + events |= IoEvents::IN | IoEvents::RDNORM; } if self.slave().can_push() { @@ -74,6 +74,12 @@ impl PtyMaster { events |= IoEvents::HUP; } + // Deal with packet mode. + let packet_ctrl = self.slave.driver().packet_ctrl(); + if packet_ctrl.has_status() { + events |= IoEvents::PRI | IoEvents::IN | IoEvents::RDNORM; + } + events } } @@ -144,6 +150,9 @@ mod ioctl_defs { pub(super) type GetPtyLock = ioc!(TIOCGPTLCK, b'T', 0x39, OutData); pub(super) type OpenPtySlave = ioc!(TIOCGPTPEER, b'T', 0x41, NoData); + + pub(super) type SetPktMode = ioc!(TIOCPKT, 0x5420, InData); + pub(super) type GetPktMode = ioc!(TIOCGPKT, b'T', 0x38, OutData); } impl FileIo for PtyMaster { @@ -223,6 +232,20 @@ impl FileIo for PtyMaster { cmd.write(&len)?; } + cmd @ SetPktMode => { + let new_mode = cmd.read()? != 0; + + self.slave.driver().packet_ctrl().set_mode(new_mode); + } + cmd @ GetPktMode => { + let packet_mode = if self.slave.driver().packet_ctrl().mode() { + 1 + } else { + 0 + }; + + cmd.write(&packet_mode)?; + } _ => (self.slave.clone() as Arc).job_ioctl(raw_ioctl, true)?, }); diff --git a/kernel/src/device/pty/mod.rs b/kernel/src/device/pty/mod.rs index 1264ccf34..f1bc07dd1 100644 --- a/kernel/src/device/pty/mod.rs +++ b/kernel/src/device/pty/mod.rs @@ -13,6 +13,7 @@ use crate::{ mod driver; mod file; mod master; +mod packet; pub use driver::PtySlave; pub use master::PtyMaster; diff --git a/kernel/src/device/pty/packet.rs b/kernel/src/device/pty/packet.rs new file mode 100644 index 000000000..88edf8f1c --- /dev/null +++ b/kernel/src/device/pty/packet.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::sync::atomic::{AtomicBool, Ordering}; + +use crate::prelude::*; + +/// Control states related to packet mode. +/// +/// Reference: . +pub(super) struct PacketCtrl { + mode: AtomicBool, + status: SpinLock, +} + +impl PacketCtrl { + // Creates a new `PacketCtrl`. + pub(super) fn new() -> Self { + Self { + mode: AtomicBool::new(false), + status: SpinLock::new(PacketStatus::empty()), + } + } + + /// Returns whether packet mode is enabled. + pub(super) fn mode(&self) -> bool { + self.mode.load(Ordering::Relaxed) + } + + /// Sets whether packet mode is enabled. + pub(super) fn set_mode(&self, mode: bool) { + // Reference: . + let mut status = self.status.lock(); + + let old_mode = self.mode.load(Ordering::Relaxed); + self.mode.store(mode, Ordering::Relaxed); + + if mode && !old_mode { + *status = PacketStatus::empty(); + } + } + + /// Sets packet status if packet mode is enabled. + pub(super) fn set_status(&self, set_packet_status: impl FnOnce(&mut PacketStatus)) -> bool { + // Fast path: Packet mode is disabled. + if !self.mode.load(Ordering::Relaxed) { + return false; + } + + // Packet mode is enabled. + let mut packet_status = self.status.lock(); + set_packet_status(&mut packet_status); + true + } + + /// Checks if packet mode is enabled and there is pending packet status. + pub(super) fn has_status(&self) -> bool { + // Fast path: Packet mode is disabled. + if !self.mode.load(Ordering::Relaxed) { + return false; + } + + // Packet mode is enabled. + !self.status.lock().is_empty() + } + + /// Takes out packet status if packet mode is enabled. + pub(super) fn take_status(&self) -> Option { + // Fast path: Packed mode is disabled. + if !self.mode.load(Ordering::Relaxed) { + return None; + } + + // Packet mode is enabled. + let mut status = self.status.lock(); + let old_status = *status; + *status = PacketStatus::empty(); + Some(old_status) + } +} + +bitflags! { + /// Reference: . + pub(super) struct PacketStatus: u8 { + const DATA = 0; + const FLUSHREAD = 1 << 0; + const FLUSHWRITE = 1 << 1; + const STOP = 1 << 2; + const START = 1 << 3; + const NOSTOP = 1 << 4; + const DOSTOP = 1 << 5; + const IOCTL = 1 << 6; + } +} diff --git a/kernel/src/device/tty/driver.rs b/kernel/src/device/tty/driver.rs index d62c59dfc..6987807d0 100644 --- a/kernel/src/device/tty/driver.rs +++ b/kernel/src/device/tty/driver.rs @@ -2,7 +2,11 @@ use aster_console::AnyConsoleDevice; -use crate::{device::tty::Tty, fs::inode_handle::FileIo, prelude::*}; +use crate::{ + device::tty::{termio::CTermios, Tty}, + fs::inode_handle::FileIo, + prelude::*, +}; /// A TTY driver. /// @@ -58,4 +62,9 @@ pub trait TtyDriver: Send + Sync + 'static { /// /// If the TTY is not associated with any console device, this method will return `None`. fn console(&self) -> Option<&dyn AnyConsoleDevice>; + + /// Notifies that the TTY termios is changed. + /// + /// This method will be called with a spin lock held, so it cannot break atomic mode. + fn on_termios_change(&self, old_termios: &CTermios, new_termios: &CTermios); } diff --git a/kernel/src/device/tty/line_discipline.rs b/kernel/src/device/tty/line_discipline.rs index 4b93e7f02..53daa32d9 100644 --- a/kernel/src/device/tty/line_discipline.rs +++ b/kernel/src/device/tty/line_discipline.rs @@ -4,6 +4,7 @@ use ostd::const_assert; use super::termio::{CCtrlCharId, CTermios, CWinSize}; use crate::{ + device::tty::termio::{CInputFlags, CLocalFlags}, prelude::*, process::signal::{ constants::{SIGINT, SIGQUIT}, @@ -98,7 +99,7 @@ impl LineDiscipline { mut signal_callback: F1, echo_callback: F2, ) -> Result<()> { - let ch = if self.termios.contains_icrnl() && ch == b'\r' { + let ch = if self.termios.input_flags().contains(CInputFlags::ICRNL) && ch == b'\r' { b'\n' } else { ch @@ -111,7 +112,7 @@ impl LineDiscipline { // Typically, a TTY in raw mode does not echo. But the TTY can also be in a CBREAK mode, // with ICANON closed and ECHO opened. - if self.termios.contain_echo() { + if self.termios.local_flags().contains(CLocalFlags::ECHO) { self.output_char(ch, echo_callback); } @@ -166,7 +167,7 @@ impl LineDiscipline { echo_callback(b"\x08"); } ch if is_printable_char(ch) => echo_callback(&[ch]), - ch if is_ctrl_char(ch) && self.termios.contains_echo_ctl() => { + ch if is_ctrl_char(ch) && self.termios.local_flags().contains(CLocalFlags::ECHOCTL) => { echo_callback(&[b'^', ctrl_char_to_printable(ch)]); } _ => {} @@ -269,7 +270,9 @@ fn is_line_terminator(ch: u8, termios: &CTermios) -> bool { return true; } - if termios.contains_iexten() && ch == termios.special_char(CCtrlCharId::VEOL2) { + if termios.local_flags().contains(CLocalFlags::IEXTEN) + && ch == termios.special_char(CCtrlCharId::VEOL2) + { return true; } @@ -293,7 +296,7 @@ fn is_ctrl_char(ch: u8) -> bool { } fn char_to_signal(ch: u8, termios: &CTermios) -> Option { - if !termios.is_canonical_mode() || !termios.contains_isig() { + if !termios.is_canonical_mode() || !termios.local_flags().contains(CLocalFlags::ISIG) { return None; } diff --git a/kernel/src/device/tty/mod.rs b/kernel/src/device/tty/mod.rs index 84c74d645..314e774a4 100644 --- a/kernel/src/device/tty/mod.rs +++ b/kernel/src/device/tty/mod.rs @@ -31,7 +31,7 @@ mod flags; pub(super) mod ioctl_defs; mod line_discipline; mod n_tty; -mod termio; +pub(super) mod termio; pub use device::SystemConsole; pub use driver::TtyDriver; @@ -152,7 +152,7 @@ impl Tty { } } - self.pollee.notify(IoEvents::IN); + self.pollee.notify(IoEvents::IN | IoEvents::RDNORM); Ok(len) } @@ -160,7 +160,7 @@ impl Tty { let mut events = IoEvents::empty(); if self.ldisc.lock().buffer_len() > 0 { - events |= IoEvents::IN; + events |= IoEvents::IN | IoEvents::RDNORM; } if self.driver.can_push() { @@ -315,12 +315,17 @@ impl Tty { cmd @ SetTermios => { let termios = cmd.read()?; - self.ldisc.lock().set_termios(termios); + let mut ldisc = self.ldisc.lock(); + let old_termios = ldisc.termios(); + self.driver().on_termios_change(old_termios, &termios); + ldisc.set_termios(termios); } cmd @ SetTermiosDrain => { let termios = cmd.read()?; let mut ldisc = self.ldisc.lock(); + let old_termios = ldisc.termios(); + self.driver().on_termios_change(old_termios, &termios); ldisc.set_termios(termios); self.driver.drain_output(); } @@ -328,6 +333,8 @@ impl Tty { let termios = cmd.read()?; let mut ldisc = self.ldisc.lock(); + let old_termios = ldisc.termios(); + self.driver().on_termios_change(old_termios, &termios); ldisc.set_termios(termios); ldisc.drain_input(); self.driver.drain_output(); diff --git a/kernel/src/device/tty/n_tty.rs b/kernel/src/device/tty/n_tty.rs index 374fedcb9..ff2b655bb 100644 --- a/kernel/src/device/tty/n_tty.rs +++ b/kernel/src/device/tty/n_tty.rs @@ -10,7 +10,7 @@ use spin::Once; use super::{Tty, TtyDriver}; use crate::{ - device::registry::char, + device::{registry::char, tty::termio::CTermios}, events::IoEvents, fs::{ inode_handle::FileIo, @@ -61,6 +61,8 @@ impl TtyDriver for VtDriver { fn console(&self) -> Option<&dyn AnyConsoleDevice> { Some(&*self.console) } + + fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {} } /// The driver for hypervisor console devices. @@ -101,6 +103,8 @@ impl TtyDriver for HvcDriver { fn console(&self) -> Option<&dyn AnyConsoleDevice> { Some(&*self.console) } + + fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {} } struct TtyFile(Arc>); diff --git a/kernel/src/device/tty/termio.rs b/kernel/src/device/tty/termio.rs index 630cb2a69..7fc322abd 100644 --- a/kernel/src/device/tty/termio.rs +++ b/kernel/src/device/tty/termio.rs @@ -11,7 +11,7 @@ bitflags! { /// The input flags; `c_iflags` bits in Linux. #[derive(Pod)] #[repr(C)] - pub(super) struct CInputFlags: u32 { + pub struct CInputFlags: u32 { // https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h const IGNBRK = 0x001; /* Ignore break condition */ const BRKINT = 0x002; /* Signal interrupt on break */ @@ -136,7 +136,7 @@ bitflags! { /// The local flags; `c_lflags` bits in Linux. #[repr(C)] #[derive(Pod)] - pub(super) struct CLocalFlags: u32 { + pub struct CLocalFlags: u32 { // https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L127 const ISIG = 0x00001; const ICANON = 0x00002; @@ -174,7 +174,7 @@ impl Default for CLocalFlags { #[repr(u32)] #[derive(Debug, Clone, Copy, TryFromInt)] #[expect(clippy::upper_case_acronyms)] -pub(super) enum CCtrlCharId { +pub enum CCtrlCharId { // https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L42 VINTR = 0, VQUIT = 1, @@ -276,7 +276,7 @@ impl CTermios { /// Reference: . const NUM_CTRL_CHARS: usize = 19; - pub(super) fn special_char(&self, id: CCtrlCharId) -> CCtrlChar { + pub fn special_char(&self, id: CCtrlCharId) -> CCtrlChar { self.c_cc[id as usize] } @@ -292,27 +292,14 @@ impl CTermios { self.c_lflags.contains(CLocalFlags::ICANON) } - /// Returns whether the input flags contain `ICRNL`. - /// - /// The `ICRNL` flag means the `\r` characters in the input should be mapped to `\n`. - pub(super) fn contains_icrnl(&self) -> bool { - self.c_iflags.contains(CInputFlags::ICRNL) + /// Returns the input flags. + pub fn input_flags(&self) -> &CInputFlags { + &self.c_iflags } - pub(super) fn contains_isig(&self) -> bool { - self.c_lflags.contains(CLocalFlags::ISIG) - } - - pub(super) fn contain_echo(&self) -> bool { - self.c_lflags.contains(CLocalFlags::ECHO) - } - - pub(super) fn contains_echo_ctl(&self) -> bool { - self.c_lflags.contains(CLocalFlags::ECHOCTL) - } - - pub(super) fn contains_iexten(&self) -> bool { - self.c_lflags.contains(CLocalFlags::IEXTEN) + /// Returns the local flags. + pub fn local_flags(&self) -> &CLocalFlags { + &self.c_lflags } } diff --git a/kernel/src/events/io_events.rs b/kernel/src/events/io_events.rs index 5e849aca3..0de3bf1a3 100644 --- a/kernel/src/events/io_events.rs +++ b/kernel/src/events/io_events.rs @@ -5,13 +5,14 @@ use crate::prelude::*; bitflags! { pub struct IoEvents: u32 { - const IN = 0x0001; - const PRI = 0x0002; - const OUT = 0x0004; - const ERR = 0x0008; - const HUP = 0x0010; - const NVAL = 0x0020; - const RDHUP = 0x2000; + const IN = 0x0001; + const PRI = 0x0002; + const OUT = 0x0004; + const ERR = 0x0008; + const HUP = 0x0010; + const NVAL = 0x0020; + const RDNORM = 0x0040; + const RDHUP = 0x2000; /// Events that are always polled even without specifying them. const ALWAYS_POLL = Self::ERR.bits | Self::HUP.bits; }