Use one lock for LineDiscipline

This commit is contained in:
Ruihan Li
2025-05-03 22:58:25 +08:00
committed by Tate, Hongliang Tian
parent 45258d0475
commit 5a9a63e1a7
3 changed files with 234 additions and 300 deletions

View File

@ -27,6 +27,7 @@ use crate::{
}; };
const BUFFER_CAPACITY: usize = 4096; const BUFFER_CAPACITY: usize = 4096;
const IO_CAPACITY: usize = 4096;
/// Pseudo terminal master. /// Pseudo terminal master.
/// Internally, it has two buffers. /// Internally, it has two buffers.
@ -37,7 +38,6 @@ pub struct PtyMaster {
index: u32, index: u32,
slave: Arc<PtySlave>, slave: Arc<PtySlave>,
input: SpinLock<RingBuffer<u8>>, input: SpinLock<RingBuffer<u8>>,
/// The state of input buffer
pollee: Pollee, pollee: Pollee,
} }
@ -45,8 +45,9 @@ impl PtyMaster {
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> { pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
Arc::new_cyclic(move |master| { Arc::new_cyclic(move |master| {
let slave = Arc::new_cyclic(move |weak_self| PtySlave { let slave = Arc::new_cyclic(move |weak_self| PtySlave {
ldisc: LineDiscipline::new(), ldisc: SpinLock::new(LineDiscipline::new()),
job_control: JobControl::new(), job_control: JobControl::new(),
pollee: Pollee::new(),
master: master.clone(), master: master.clone(),
weak_self: weak_self.clone(), weak_self: weak_self.clone(),
}); });
@ -73,51 +74,57 @@ impl PtyMaster {
&self.slave &self.slave
} }
pub(super) fn slave_push_char(&self, ch: u8) { fn slave_push(&self, chs: &[u8]) {
let mut input = self.input.disable_irq().lock(); let mut input = self.input.lock();
input.push_overwrite(ch);
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); self.pollee.notify(IoEvents::IN);
} }
pub(super) fn slave_poll( fn slave_echo(&self) -> impl FnMut(&str) + '_ {
&self, let mut input = self.input.lock();
mask: IoEvents, let mut has_notified = false;
mut poller: Option<&mut PollHandle>,
) -> IoEvents {
let mut poll_status = IoEvents::empty();
let poll_in_mask = mask & IoEvents::IN; move |content| {
if !poll_in_mask.is_empty() { for byte in content.as_bytes() {
let poll_in_status = self.slave.ldisc.poll(poll_in_mask, poller.as_deref_mut()); input.push_overwrite(*byte);
poll_status |= poll_in_status; }
if !has_notified {
self.pollee.notify(IoEvents::IN);
has_notified = true;
}
} }
let poll_out_mask = mask & IoEvents::OUT;
if !poll_out_mask.is_empty() {
let poll_out_status = self
.pollee
.poll_with(poll_out_mask, poller, || self.check_io_events());
poll_status |= poll_out_status;
}
poll_status
} }
fn try_read(&self, writer: &mut VmWriter) -> Result<usize> { fn try_read(&self, buf: &mut [u8]) -> Result<usize> {
let mut input = self.input.disable_irq().lock(); if buf.is_empty() {
return Ok(0);
}
let mut input = self.input.lock();
if input.is_empty() { if input.is_empty() {
return_errno_with_message!(Errno::EAGAIN, "the buffer is empty"); return_errno_with_message!(Errno::EAGAIN, "the buffer is empty");
} }
let read_len = input.read_fallible(writer)?; let read_len = input.len().min(buf.len());
input.pop_slice(&mut buf[..read_len]).unwrap();
self.pollee.invalidate(); self.pollee.invalidate();
Ok(read_len) Ok(read_len)
} }
fn check_io_events(&self) -> IoEvents { fn check_io_events(&self) -> IoEvents {
let input = self.input.disable_irq().lock(); let input = self.input.lock();
if !input.is_empty() { if !input.is_empty() {
IoEvents::IN | IoEvents::OUT IoEvents::IN | IoEvents::OUT
@ -128,60 +135,27 @@ impl PtyMaster {
} }
impl Pollable for PtyMaster { impl Pollable for PtyMaster {
fn poll(&self, mask: IoEvents, mut poller: Option<&mut PollHandle>) -> IoEvents { fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
let mut poll_status = IoEvents::empty(); self.pollee
.poll_with(mask, poller, || self.check_io_events())
let poll_in_mask = mask & IoEvents::IN;
if !poll_in_mask.is_empty() {
let poll_in_status = self
.pollee
.poll_with(poll_in_mask, poller.as_deref_mut(), || {
self.check_io_events()
});
poll_status |= poll_in_status;
}
let poll_out_mask = mask & IoEvents::OUT;
if !poll_out_mask.is_empty() {
let poll_out_status = self.slave.ldisc.poll(poll_out_mask, poller);
poll_status |= poll_out_status;
}
poll_status
} }
} }
impl FileIo for PtyMaster { impl FileIo for PtyMaster {
fn read(&self, writer: &mut VmWriter) -> Result<usize> { fn read(&self, writer: &mut VmWriter) -> Result<usize> {
if !writer.has_avail() { // TODO: Add support for non-blocking mode and timeout
return Ok(0); let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)];
} let read_len = self.wait_events(IoEvents::IN, None, || self.try_read(&mut buf))?;
// TODO: deal with nonblocking and timeout writer.write_fallible(&mut buf[..read_len].into())?;
self.wait_events(IoEvents::IN, None, || self.try_read(writer)) Ok(read_len)
} }
fn write(&self, reader: &mut VmReader) -> Result<usize> { fn write(&self, reader: &mut VmReader) -> Result<usize> {
let buf = reader.collect()?; let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
let write_len = buf.len(); let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
let mut input = self.input.lock();
for character in buf {
self.slave.ldisc.push_char(
character,
|signum| {
if let Some(foreground) = self.slave.job_control.foreground() {
broadcast_signal_async(Arc::downgrade(&foreground), signum);
}
},
|content| {
for byte in content.as_bytes() {
input.push_overwrite(*byte);
}
},
);
}
self.pollee.notify(IoEvents::IN); self.slave.master_push(&buf[..write_len]);
Ok(write_len) Ok(write_len)
} }
@ -248,8 +222,9 @@ impl Drop for PtyMaster {
} }
pub struct PtySlave { pub struct PtySlave {
ldisc: LineDiscipline, ldisc: SpinLock<LineDiscipline>,
job_control: JobControl, job_control: JobControl,
pollee: Pollee,
master: Weak<PtyMaster>, master: Weak<PtyMaster>,
weak_self: Weak<Self>, weak_self: Weak<Self>,
} }
@ -262,6 +237,34 @@ impl PtySlave {
fn master(&self) -> Arc<PtyMaster> { fn master(&self) -> Arc<PtyMaster> {
self.master.upgrade().unwrap() 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 { impl Device for PtySlave {
@ -282,59 +285,57 @@ impl Terminal for PtySlave {
impl Pollable for PtySlave { impl Pollable for PtySlave {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.master().slave_poll(mask, poller) self.pollee
.poll_with(mask, poller, || self.check_io_events())
} }
} }
impl FileIo for PtySlave { impl FileIo for PtySlave {
fn read(&self, writer: &mut VmWriter) -> Result<usize> { fn read(&self, writer: &mut VmWriter) -> Result<usize> {
let mut buf = vec![0u8; writer.avail()];
self.job_control.wait_until_in_foreground()?; self.job_control.wait_until_in_foreground()?;
let read_len = self.ldisc.read(&mut buf)?;
writer.write_fallible(&mut buf.as_slice().into())?; // 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) Ok(read_len)
} }
fn write(&self, reader: &mut VmReader) -> Result<usize> { fn write(&self, reader: &mut VmReader) -> Result<usize> {
let buf = reader.collect()?; let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
let write_len = buf.len(); let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
let master = self.master();
for ch in buf { self.master().slave_push(&buf[..write_len]);
// do we need to add '\r' here?
if ch == b'\n' {
master.slave_push_char(b'\r');
master.slave_push_char(b'\n');
} else {
master.slave_push_char(ch);
}
}
Ok(write_len) Ok(write_len)
} }
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> { fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
match cmd { match cmd {
IoctlCmd::TCGETS => { IoctlCmd::TCGETS => {
let termios = self.ldisc.termios(); let termios = *self.ldisc.lock().termios();
current_userspace!().write_val(arg, &termios)?; current_userspace!().write_val(arg, &termios)?;
} }
IoctlCmd::TCSETS => { IoctlCmd::TCSETS => {
let termios = current_userspace!().read_val(arg)?; let termios = current_userspace!().read_val(arg)?;
self.ldisc.set_termios(termios); self.ldisc.lock().set_termios(termios);
} }
IoctlCmd::TIOCGPTN => { IoctlCmd::TIOCGPTN => {
let idx = self.index(); let idx = self.index();
current_userspace!().write_val(arg, &idx)?; current_userspace!().write_val(arg, &idx)?;
} }
IoctlCmd::TIOCGWINSZ => { IoctlCmd::TIOCGWINSZ => {
let winsize = self.ldisc.window_size(); let winsize = self.ldisc.lock().window_size();
current_userspace!().write_val(arg, &winsize)?; current_userspace!().write_val(arg, &winsize)?;
} }
IoctlCmd::TIOCSWINSZ => { IoctlCmd::TIOCSWINSZ => {
let winsize = current_userspace!().read_val(arg)?; let winsize = current_userspace!().read_val(arg)?;
self.ldisc.set_window_size(winsize); self.ldisc.lock().set_window_size(winsize);
} }
IoctlCmd::FIONREAD => { IoctlCmd::FIONREAD => {
let buffer_len = self.ldisc.buffer_len() as i32; let buffer_len = self.ldisc.lock().buffer_len() as i32;
current_userspace!().write_val(arg, &buffer_len)?; current_userspace!().write_val(arg, &buffer_len)?;
} }
_ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>) _ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)

View File

@ -2,16 +2,12 @@
use alloc::format; use alloc::format;
use ostd::{sync::LocalIrqDisabled, trap::disable_local};
use super::termio::{KernelTermios, WinSize, CC_C_CHAR}; use super::termio::{KernelTermios, WinSize, CC_C_CHAR};
use crate::{ use crate::{
events::IoEvents,
prelude::*, prelude::*,
process::signal::{ process::signal::{
constants::{SIGINT, SIGQUIT}, constants::{SIGINT, SIGQUIT},
sig_num::SigNum, sig_num::SigNum,
PollHandle, Pollable, Pollee,
}, },
util::ring_buffer::RingBuffer, util::ring_buffer::RingBuffer,
}; };
@ -21,22 +17,15 @@ use crate::{
const BUFFER_CAPACITY: usize = 4096; const BUFFER_CAPACITY: usize = 4096;
// Lock ordering to prevent deadlock (circular dependencies):
// 1. `termios`
// 2. `current_line`
// 3. `read_buffer`
// 4. `work_item_para`
pub struct LineDiscipline { pub struct LineDiscipline {
/// Current line /// Current line
current_line: SpinLock<CurrentLine, LocalIrqDisabled>, current_line: CurrentLine,
/// The read buffer /// The read buffer
read_buffer: SpinLock<RingBuffer<u8>, LocalIrqDisabled>, read_buffer: RingBuffer<u8>,
/// Termios /// Termios
termios: SpinLock<KernelTermios, LocalIrqDisabled>, termios: KernelTermios,
/// Windows size /// Windows size
winsize: SpinLock<WinSize, LocalIrqDisabled>, winsize: WinSize,
/// Pollee
pollee: Pollee,
} }
pub struct CurrentLine { pub struct CurrentLine {
@ -78,112 +67,85 @@ impl CurrentLine {
} }
} }
impl Pollable for LineDiscipline {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.pollee
.poll_with(mask, poller, || self.check_io_events())
}
}
impl LineDiscipline { impl LineDiscipline {
/// Creates a new line discipline /// Creates a new line discipline.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
current_line: SpinLock::new(CurrentLine::default()), current_line: CurrentLine::default(),
read_buffer: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)), read_buffer: RingBuffer::new(BUFFER_CAPACITY),
termios: SpinLock::new(KernelTermios::default()), termios: KernelTermios::default(),
winsize: SpinLock::new(WinSize::default()), winsize: WinSize::default(),
pollee: Pollee::new(),
} }
} }
/// Pushes a char to the line discipline /// Pushes a character to the line discipline.
pub fn push_char<F1: FnMut(SigNum), F2: FnMut(&str)>( pub fn push_char<F1: FnMut(SigNum), F2: FnMut(&str)>(
&self, &mut self,
ch: u8, ch: u8,
mut signal_callback: F1, mut signal_callback: F1,
echo_callback: F2, echo_callback: F2,
) { ) {
let termios = self.termios.lock(); let ch = if self.termios.contains_icrnl() && ch == b'\r' {
let ch = if termios.contains_icrnl() && ch == b'\r' {
b'\n' b'\n'
} else { } else {
ch ch
}; };
if let Some(signum) = char_to_signal(ch, &termios) { if let Some(signum) = char_to_signal(ch, &self.termios) {
signal_callback(signum); signal_callback(signum);
// CBREAK mode may require the character to be outputted, so just go ahead. // CBREAK mode may require the character to be echoed, so just go ahead.
} }
// Typically, a tty in raw mode does not echo. But the tty can also be in a CBREAK mode, // 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. // with ICANON closed and ECHO opened.
if termios.contain_echo() { if self.termios.contain_echo() {
self.output_char(ch, &termios, echo_callback); self.output_char(ch, echo_callback);
} }
// Raw mode // Raw mode
if !termios.is_canonical_mode() { if !self.termios.is_canonical_mode() {
self.read_buffer.lock().push_overwrite(ch); self.read_buffer.push_overwrite(ch);
self.pollee.notify(IoEvents::IN);
return; return;
} }
// Canonical mode // Canonical mode
if ch == *termios.get_special_char(CC_C_CHAR::VKILL) { if ch == *self.termios.get_special_char(CC_C_CHAR::VKILL) {
// Erase current line // Erase current line
self.current_line.lock().drain(); self.current_line.drain();
} }
if ch == *termios.get_special_char(CC_C_CHAR::VERASE) { if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) {
// Type backspace // Type backspace
let mut current_line = self.current_line.lock(); self.current_line.backspace();
if !current_line.is_empty() {
current_line.backspace();
}
} }
if is_line_terminator(ch, &termios) { if is_line_terminator(ch, &self.termios) {
// If a new line is met, all bytes in current_line will be moved to read_buffer // A new line is met. Move all bytes in `current_line` to `read_buffer`.
let mut current_line = self.current_line.lock(); self.current_line.push_char(ch);
current_line.push_char(ch); for line_ch in self.current_line.drain() {
let current_line_chars = current_line.drain(); self.read_buffer.push_overwrite(line_ch);
for char in current_line_chars {
self.read_buffer.lock().push_overwrite(char);
self.pollee.notify(IoEvents::IN);
} }
} }
if is_printable_char(ch) { if is_printable_char(ch) {
// Printable character // Printable character
self.current_line.lock().push_char(ch); self.current_line.push_char(ch);
}
}
fn check_io_events(&self) -> IoEvents {
let buffer = self.read_buffer.lock();
if !buffer.is_empty() {
IoEvents::IN
} else {
IoEvents::empty()
} }
} }
// TODO: respect output flags // TODO: respect output flags
fn output_char<F: FnMut(&str)>(&self, ch: u8, termios: &KernelTermios, mut echo_callback: F) { fn output_char<F: FnMut(&str)>(&self, ch: u8, mut echo_callback: F) {
match ch { match ch {
b'\n' => echo_callback("\n"), b'\n' => echo_callback("\n"),
b'\r' => echo_callback("\r\n"), b'\r' => echo_callback("\r\n"),
ch if ch == *termios.get_special_char(CC_C_CHAR::VERASE) => { ch if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) => {
// write a space to overwrite current character // Write a space to overwrite the current character
let backspace: &str = core::str::from_utf8(b"\x08 \x08").unwrap(); let backspace: &str = core::str::from_utf8(b"\x08 \x08").unwrap();
echo_callback(backspace); echo_callback(backspace);
} }
ch if is_printable_char(ch) => print!("{}", char::from(ch)), ch if is_printable_char(ch) => print!("{}", char::from(ch)),
ch if is_ctrl_char(ch) && termios.contains_echo_ctl() => { ch if is_ctrl_char(ch) && self.termios.contains_echo_ctl() => {
let ctrl_char = format!("^{}", ctrl_char_to_printable(ch)); let ctrl_char = format!("^{}", ctrl_char_to_printable(ch));
echo_callback(&ctrl_char); echo_callback(&ctrl_char);
} }
@ -191,140 +153,86 @@ impl LineDiscipline {
} }
} }
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
self.wait_events(IoEvents::IN, None, || self.try_read(buf))
}
/// Reads all bytes buffered to `dst`.
///
/// This method returns the actual read length.
fn try_read(&self, dst: &mut [u8]) -> Result<usize> {
let (vmin, vtime) = {
let termios = self.termios.lock();
let vmin = *termios.get_special_char(CC_C_CHAR::VMIN);
let vtime = *termios.get_special_char(CC_C_CHAR::VTIME);
(vmin, vtime)
};
let read_len = {
if vmin == 0 && vtime == 0 {
// poll read
self.poll_read(dst)
} else if vmin > 0 && vtime == 0 {
// block read
self.block_read(dst, vmin)?
} else if vmin == 0 && vtime > 0 {
todo!()
} else if vmin > 0 && vtime > 0 {
todo!()
} else {
unreachable!()
}
};
self.pollee.invalidate();
Ok(read_len)
}
/// Reads bytes from `self` to `dst`, returning the actual bytes read. /// Reads bytes from `self` to `dst`, returning the actual bytes read.
/// ///
/// If no bytes are available, this method returns 0 immediately.
fn poll_read(&self, dst: &mut [u8]) -> usize {
let termios = self.termios.lock();
let mut buffer = self.read_buffer.lock();
let len = buffer.len();
let max_read_len = len.min(dst.len());
if max_read_len == 0 {
return 0;
}
let mut read_len = 0;
for dst_i in dst.iter_mut().take(max_read_len) {
if let Some(next_char) = buffer.pop() {
if termios.is_canonical_mode() {
// canonical mode, read until meet new line
if is_line_terminator(next_char, &termios) {
// The eof should not be read
if !is_eof(next_char, &termios) {
*dst_i = next_char;
read_len += 1;
}
break;
} else {
*dst_i = next_char;
read_len += 1;
}
} else {
// raw mode
// FIXME: avoid additional bound check
*dst_i = next_char;
read_len += 1;
}
} else {
break;
}
}
read_len
}
/// Reads bytes from `self` into `dst`,
/// returning the actual number of bytes read.
///
/// # Errors /// # Errors
/// ///
/// If the available bytes are fewer than `min(dst.len(), vmin)`, /// If no bytes are available or the available bytes are fewer than `min(dst.len(), vmin)`,
/// this method returns [`Errno::EAGAIN`]. /// this method returns [`Errno::EAGAIN`].
pub fn block_read(&self, dst: &mut [u8], vmin: u8) -> Result<usize> { pub fn try_read(&mut self, dst: &mut [u8]) -> Result<usize> {
let _guard = disable_local(); let vmin = *self.termios.get_special_char(CC_C_CHAR::VMIN);
let buffer_len = self.read_buffer.lock().len(); let vtime = *self.termios.get_special_char(CC_C_CHAR::VTIME);
if buffer_len >= dst.len() {
return Ok(self.poll_read(dst)); if vtime != 0 {
warn!("non-zero VTIME is not supported");
} }
if buffer_len < vmin as usize {
return_errno!(Errno::EAGAIN); // If `vmin` is zero or `dst` is empty, the following condition will always be false. This
// is correct, as the expected behavior is to never block or return `EAGAIN`.
if self.buffer_len() < dst.len().min(vmin as _) {
return_errno_with_message!(
Errno::EAGAIN,
"the characters in the buffer are not enough"
);
} }
Ok(self.poll_read(&mut dst[..buffer_len]))
for (i, dst_i) in dst.iter_mut().enumerate() {
let Some(ch) = self.read_buffer.pop() else {
// No more characters
return Ok(i);
};
if self.termios.is_canonical_mode() && is_eof(ch, &self.termios) {
// This allows the userspace program to see `Ok(0)`
return Ok(i);
}
*dst_i = ch;
if self.termios.is_canonical_mode() && is_line_terminator(ch, &self.termios) {
// Read until line terminators in canonical mode
return Ok(i + 1);
}
}
Ok(dst.len())
} }
/// Returns whether there is buffered data pub fn termios(&self) -> &KernelTermios {
pub fn is_empty(&self) -> bool { &self.termios
self.read_buffer.lock().len() == 0
} }
pub fn termios(&self) -> KernelTermios { pub fn set_termios(&mut self, termios: KernelTermios) {
*self.termios.lock() self.termios = termios;
} }
pub fn set_termios(&self, termios: KernelTermios) { pub fn drain_input(&mut self) {
*self.termios.lock() = termios; self.current_line.drain();
} self.read_buffer.clear();
pub fn drain_input(&self) {
self.current_line.lock().drain();
self.read_buffer.lock().clear();
self.pollee.invalidate();
} }
pub fn buffer_len(&self) -> usize { pub fn buffer_len(&self) -> usize {
self.read_buffer.lock().len() self.read_buffer.len()
} }
pub fn window_size(&self) -> WinSize { pub fn window_size(&self) -> WinSize {
*self.winsize.lock() self.winsize
} }
pub fn set_window_size(&self, winsize: WinSize) { pub fn set_window_size(&mut self, winsize: WinSize) {
*self.winsize.lock() = winsize; self.winsize = winsize;
} }
} }
fn is_line_terminator(item: u8, termios: &KernelTermios) -> bool { fn is_line_terminator(ch: u8, termios: &KernelTermios) -> bool {
if item == b'\n' if ch == b'\n'
|| item == *termios.get_special_char(CC_C_CHAR::VEOF) || ch == *termios.get_special_char(CC_C_CHAR::VEOF)
|| item == *termios.get_special_char(CC_C_CHAR::VEOL) || ch == *termios.get_special_char(CC_C_CHAR::VEOL)
{ {
return true; return true;
} }
if termios.contains_iexten() && item == *termios.get_special_char(CC_C_CHAR::VEOL2) { if termios.contains_iexten() && ch == *termios.get_special_char(CC_C_CHAR::VEOL2) {
return true; return true;
} }

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use ostd::early_print; use ostd::{early_print, sync::LocalIrqDisabled};
use spin::Once; use spin::Once;
use self::{driver::TtyDriver, line_discipline::LineDiscipline}; use self::{driver::TtyDriver, line_discipline::LineDiscipline};
@ -15,7 +15,7 @@ use crate::{
prelude::*, prelude::*,
process::{ process::{
broadcast_signal_async, broadcast_signal_async,
signal::{PollHandle, Pollable}, signal::{PollHandle, Pollable, Pollee},
JobControl, Terminal, JobControl, Terminal,
}, },
}; };
@ -36,13 +36,16 @@ pub(super) fn init() {
driver::init(); driver::init();
} }
const IO_CAPACITY: usize = 4096;
pub struct Tty { pub struct Tty {
/// tty_name /// tty_name
#[expect(unused)] #[expect(unused)]
name: CString, name: CString,
/// line discipline /// line discipline
ldisc: LineDiscipline, ldisc: SpinLock<LineDiscipline, LocalIrqDisabled>,
job_control: JobControl, job_control: JobControl,
pollee: Pollee,
/// driver /// driver
driver: SpinLock<Weak<TtyDriver>>, driver: SpinLock<Weak<TtyDriver>>,
weak_self: Weak<Self>, weak_self: Weak<Self>,
@ -52,8 +55,9 @@ impl Tty {
pub fn new(name: CString) -> Arc<Self> { pub fn new(name: CString) -> Arc<Self> {
Arc::new_cyclic(move |weak_ref| Tty { Arc::new_cyclic(move |weak_ref| Tty {
name, name,
ldisc: LineDiscipline::new(), ldisc: SpinLock::new(LineDiscipline::new()),
job_control: JobControl::new(), job_control: JobControl::new(),
pollee: Pollee::new(),
driver: SpinLock::new(Weak::new()), driver: SpinLock::new(Weak::new()),
weak_self: weak_ref.clone(), weak_self: weak_ref.clone(),
}) })
@ -66,7 +70,7 @@ impl Tty {
pub fn push_char(&self, ch: u8) { pub fn push_char(&self, ch: u8) {
// FIXME: Use `early_print` to avoid calling virtio-console. // FIXME: Use `early_print` to avoid calling virtio-console.
// This is only a workaround // This is only a workaround
self.ldisc.push_char( self.ldisc.lock().push_char(
ch, ch,
|signum| { |signum| {
if let Some(foreground) = self.job_control.foreground() { if let Some(foreground) = self.job_control.foreground() {
@ -74,69 +78,90 @@ impl Tty {
} }
}, },
|content| early_print!("{}", content), |content| early_print!("{}", content),
) );
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 Pollable for Tty { impl Pollable for Tty {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.ldisc.poll(mask, poller) 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<usize> { fn read(&self, writer: &mut VmWriter) -> Result<usize> {
let mut buf = vec![0; writer.avail()];
self.job_control.wait_until_in_foreground()?; self.job_control.wait_until_in_foreground()?;
let read_len = self.ldisc.read(buf.as_mut_slice())?;
writer.write_fallible(&mut buf.as_slice().into())?; // 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();
// TODO: Confirm what we should do if `write_fallible` fails in the middle.
writer.write_fallible(&mut buf[..read_len].into())?;
Ok(read_len) Ok(read_len)
} }
fn write(&self, reader: &mut VmReader) -> Result<usize> { fn write(&self, reader: &mut VmReader) -> Result<usize> {
let buf = reader.collect()?; let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
if let Ok(content) = alloc::str::from_utf8(&buf) { 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}"); print!("{content}");
} else { } else {
println!("Not utf-8 content: {:?}", buf); println!("Not utf-8 content: {:?}", buf);
} }
Ok(buf.len()) Ok(write_len)
} }
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> { fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
match cmd { match cmd {
IoctlCmd::TCGETS => { IoctlCmd::TCGETS => {
// Get terminal attributes let termios = *self.ldisc.lock().termios();
let termios = self.ldisc.termios();
trace!("get termios = {:?}", termios);
current_userspace!().write_val(arg, &termios)?; current_userspace!().write_val(arg, &termios)?;
} }
IoctlCmd::TCSETS => { IoctlCmd::TCSETS => {
// Set terminal attributes
let termios = current_userspace!().read_val(arg)?; let termios = current_userspace!().read_val(arg)?;
debug!("set termios = {:?}", termios);
self.ldisc.set_termios(termios); self.ldisc.lock().set_termios(termios);
} }
IoctlCmd::TCSETSW => { IoctlCmd::TCSETSW => {
let termios = current_userspace!().read_val(arg)?; let termios = current_userspace!().read_val(arg)?;
debug!("set termios = {:?}", termios);
self.ldisc.set_termios(termios); self.ldisc.lock().set_termios(termios);
// TODO: drain output buffer // TODO: Drain the output buffer
} }
IoctlCmd::TCSETSF => { IoctlCmd::TCSETSF => {
let termios = current_userspace!().read_val(arg)?; let termios = current_userspace!().read_val(arg)?;
debug!("set termios = {:?}", termios);
self.ldisc.set_termios(termios); let mut ldisc = self.ldisc.lock();
self.ldisc.drain_input(); ldisc.set_termios(termios);
// TODO: drain output buffer ldisc.drain_input();
// TODO: Drain the output buffer
self.pollee.invalidate();
} }
IoctlCmd::TIOCGWINSZ => { IoctlCmd::TIOCGWINSZ => {
let winsize = self.ldisc.window_size(); let winsize = self.ldisc.lock().window_size();
current_userspace!().write_val(arg, &winsize)?; current_userspace!().write_val(arg, &winsize)?;
} }
IoctlCmd::TIOCSWINSZ => { IoctlCmd::TIOCSWINSZ => {
let winsize = current_userspace!().read_val(arg)?; let winsize = current_userspace!().read_val(arg)?;
self.ldisc.set_window_size(winsize);
self.ldisc.lock().set_window_size(winsize);
} }
_ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>) _ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)
.job_ioctl(cmd, arg, false)?, .job_ioctl(cmd, arg, false)?,