mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-23 01:13:23 +00:00
Redefine the TTY driver interface
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
5a9a63e1a7
commit
67065835ef
@ -24,7 +24,7 @@ pub struct FramebufferConsole {
|
|||||||
state: SpinLock<ConsoleState, LocalIrqDisabled>,
|
state: SpinLock<ConsoleState, LocalIrqDisabled>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static CONSOLE_NAME: &str = "Framebuffer-Console";
|
pub const CONSOLE_NAME: &str = "Framebuffer-Console";
|
||||||
|
|
||||||
pub static FRAMEBUFFER_CONSOLE: Once<Arc<FramebufferConsole>> = Once::new();
|
pub static FRAMEBUFFER_CONSOLE: Once<Arc<FramebufferConsole>> = Once::new();
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ use ostd::Pod;
|
|||||||
|
|
||||||
use crate::transport::{ConfigManager, VirtioTransport};
|
use crate::transport::{ConfigManager, VirtioTransport};
|
||||||
|
|
||||||
pub static DEVICE_NAME: &str = "Virtio-Block";
|
pub const DEVICE_NAME: &str = "Virtio-Block";
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// features for virtio block device
|
/// features for virtio block device
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
|
|
||||||
pub static DEVICE_NAME: &str = "Virtio-Console";
|
pub const DEVICE_NAME: &str = "Virtio-Console";
|
||||||
|
@ -32,7 +32,7 @@ use ostd::{io::IoMem, Pod};
|
|||||||
|
|
||||||
use crate::transport::VirtioTransport;
|
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()`].
|
/// Select value used for [`device::InputDevice::query_config_select()`].
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -4,4 +4,4 @@ pub mod config;
|
|||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod header;
|
pub mod header;
|
||||||
|
|
||||||
pub static DEVICE_NAME: &str = "Virtio-Net";
|
pub const DEVICE_NAME: &str = "Virtio-Net";
|
||||||
|
@ -14,7 +14,8 @@ pub mod device;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod header;
|
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 trait VsockDeviceIrqHandler = Fn() + Send + Sync + 'static;
|
||||||
|
|
||||||
pub fn register_device(name: String, device: Arc<SpinLock<SocketDevice>>) {
|
pub fn register_device(name: String, device: Arc<SpinLock<SocketDevice>>) {
|
||||||
|
@ -11,11 +11,12 @@ mod zero;
|
|||||||
#[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
|
#[cfg(all(target_arch = "x86_64", feature = "cvm_guest"))]
|
||||||
mod tdxguest;
|
mod tdxguest;
|
||||||
|
|
||||||
|
use alloc::format;
|
||||||
|
|
||||||
pub use pty::{new_pty_pair, PtyMaster, PtySlave};
|
pub use pty::{new_pty_pair, PtyMaster, PtySlave};
|
||||||
pub use random::Random;
|
pub use random::Random;
|
||||||
pub use urandom::Urandom;
|
pub use urandom::Urandom;
|
||||||
|
|
||||||
use self::tty::get_n_tty;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fs::device::{add_node, Device, DeviceId, DeviceType},
|
fs::device::{add_node, Device, DeviceId, DeviceType},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -25,23 +26,37 @@ use crate::{
|
|||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
let null = Arc::new(null::Null);
|
let null = Arc::new(null::Null);
|
||||||
add_node(null, "null")?;
|
add_node(null, "null")?;
|
||||||
|
|
||||||
let zero = Arc::new(zero::Zero);
|
let zero = Arc::new(zero::Zero);
|
||||||
add_node(zero, "zero")?;
|
add_node(zero, "zero")?;
|
||||||
|
|
||||||
tty::init();
|
tty::init();
|
||||||
let console = get_n_tty().clone();
|
|
||||||
add_node(console, "console")?;
|
|
||||||
let tty = Arc::new(tty::TtyDevice);
|
let tty = Arc::new(tty::TtyDevice);
|
||||||
add_node(tty, "tty")?;
|
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")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
ostd::if_tdx_enabled!({
|
ostd::if_tdx_enabled!({
|
||||||
add_node(Arc::new(tdxguest::TdxGuest), "tdx_guest")?;
|
add_node(Arc::new(tdxguest::TdxGuest), "tdx_guest")?;
|
||||||
});
|
});
|
||||||
|
|
||||||
let random = Arc::new(random::Random);
|
let random = Arc::new(random::Random);
|
||||||
add_node(random, "random")?;
|
add_node(random, "random")?;
|
||||||
|
|
||||||
let urandom = Arc::new(urandom::Urandom);
|
let urandom = Arc::new(urandom::Urandom);
|
||||||
add_node(urandom, "urandom")?;
|
add_node(urandom, "urandom")?;
|
||||||
|
|
||||||
pty::init()?;
|
pty::init()?;
|
||||||
|
|
||||||
shm::init()?;
|
shm::init()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
98
kernel/src/device/pty/driver.rs
Normal file
98
kernel/src/device/pty/driver.rs
Normal file
@ -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<RingBuffer<u8>>,
|
||||||
|
pollee: Pollee,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pseudoterminal slave.
|
||||||
|
pub type PtySlave = Tty<PtyDriver>;
|
||||||
|
|
||||||
|
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<usize> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
155
kernel/src/device/pty/master.rs
Normal file
155
kernel/src/device/pty/master.rs
Normal file
@ -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<dyn Inode>,
|
||||||
|
slave: Arc<PtySlave>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PtyMaster {
|
||||||
|
pub(super) fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
|
||||||
|
let slave = PtySlave::new(index, PtyDriver::new());
|
||||||
|
|
||||||
|
Arc::new(Self { ptmx, slave })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn slave(&self) -> &Arc<PtySlave> {
|
||||||
|
&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<usize> {
|
||||||
|
// 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<usize> {
|
||||||
|
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<i32> {
|
||||||
|
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<dyn Terminal>).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::<DevPts>().unwrap();
|
||||||
|
|
||||||
|
let index = self.slave.index();
|
||||||
|
devpts.remove_slave(index);
|
||||||
|
}
|
||||||
|
}
|
@ -10,10 +10,11 @@ use crate::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[expect(clippy::module_inception)]
|
mod driver;
|
||||||
mod pty;
|
mod master;
|
||||||
|
|
||||||
pub use pty::{PtyMaster, PtySlave};
|
pub use driver::PtySlave;
|
||||||
|
pub use master::PtyMaster;
|
||||||
use spin::Once;
|
use spin::Once;
|
||||||
|
|
||||||
static DEV_PTS: Once<Dentry> = Once::new();
|
static DEV_PTS: Once<Dentry> = Once::new();
|
||||||
|
@ -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<dyn Inode>,
|
|
||||||
index: u32,
|
|
||||||
slave: Arc<PtySlave>,
|
|
||||||
input: SpinLock<RingBuffer<u8>>,
|
|
||||||
pollee: Pollee,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PtyMaster {
|
|
||||||
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
|
|
||||||
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<dyn Inode> {
|
|
||||||
&self.ptmx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slave(&self) -> &Arc<PtySlave> {
|
|
||||||
&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<usize> {
|
|
||||||
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<usize> {
|
|
||||||
// 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<usize> {
|
|
||||||
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<i32> {
|
|
||||||
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<dyn Terminal>).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::<DevPts>().unwrap();
|
|
||||||
|
|
||||||
let index = self.index;
|
|
||||||
devpts.remove_slave(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PtySlave {
|
|
||||||
ldisc: SpinLock<LineDiscipline>,
|
|
||||||
job_control: JobControl,
|
|
||||||
pollee: Pollee,
|
|
||||||
master: Weak<PtyMaster>,
|
|
||||||
weak_self: Weak<Self>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PtySlave {
|
|
||||||
pub fn index(&self) -> u32 {
|
|
||||||
self.master().index()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn master(&self) -> Arc<PtyMaster> {
|
|
||||||
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<usize> {
|
|
||||||
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<usize> {
|
|
||||||
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<i32> {
|
|
||||||
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<dyn Terminal>)
|
|
||||||
.job_ioctl(cmd, arg, false)?,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +1,23 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use ostd::mm::{Infallible, VmReader};
|
/// A TTY driver.
|
||||||
use spin::Once;
|
///
|
||||||
|
/// 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::{
|
/// Drains the output buffer.
|
||||||
device::tty::{get_n_tty, Tty},
|
fn drain_output(&self);
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub static TTY_DRIVER: Once<Arc<TtyDriver>> = Once::new();
|
/// Returns a callback function that echoes input characters to the output buffer.
|
||||||
|
///
|
||||||
pub(super) fn init() {
|
/// Note that the implementation may choose to hold a lock during the life of the callback.
|
||||||
for (_, device) in aster_console::all_devices() {
|
/// During this time, calls to other methods such as [`Self::push_output`] may cause deadlocks.
|
||||||
device.register_callback(&console_input_callback)
|
fn echo_callback(&self) -> impl FnMut(&[u8]) + '_;
|
||||||
}
|
|
||||||
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<Vec<Arc<Tty>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Arc<Tty>> {
|
|
||||||
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<Self>, tty: Arc<Tty>) {
|
|
||||||
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<Infallible>) {
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use alloc::format;
|
|
||||||
|
|
||||||
use super::termio::{KernelTermios, WinSize, CC_C_CHAR};
|
use super::termio::{KernelTermios, WinSize, CC_C_CHAR};
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -61,10 +59,6 @@ impl CurrentLine {
|
|||||||
pub fn is_full(&self) -> bool {
|
pub fn is_full(&self) -> bool {
|
||||||
self.buffer.is_full()
|
self.buffer.is_full()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.buffer.is_empty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LineDiscipline {
|
impl LineDiscipline {
|
||||||
@ -79,7 +73,7 @@ impl LineDiscipline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a character 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(&[u8])>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ch: u8,
|
ch: u8,
|
||||||
mut signal_callback: F1,
|
mut signal_callback: F1,
|
||||||
@ -135,19 +129,17 @@ impl LineDiscipline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: respect output flags
|
// TODO: respect output flags
|
||||||
fn output_char<F: FnMut(&str)>(&self, ch: u8, mut echo_callback: F) {
|
fn output_char<F: FnMut(&[u8])>(&self, ch: u8, mut echo_callback: F) {
|
||||||
match ch {
|
match ch {
|
||||||
b'\n' => echo_callback("\n"),
|
b'\n' => echo_callback(b"\n"),
|
||||||
b'\r' => echo_callback("\r\n"),
|
b'\r' => echo_callback(b"\r\n"),
|
||||||
ch if ch == *self.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 the current character
|
// Write a space to overwrite the current character
|
||||||
let backspace: &str = core::str::from_utf8(b"\x08 \x08").unwrap();
|
echo_callback(b"\x08 \x08");
|
||||||
echo_callback(backspace);
|
|
||||||
}
|
}
|
||||||
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() => {
|
ch if is_ctrl_char(ch) && self.termios.contains_echo_ctl() => {
|
||||||
let ctrl_char = format!("^{}", ctrl_char_to_printable(ch));
|
echo_callback(&[b'^', ctrl_char_to_printable(ch)]);
|
||||||
echo_callback(&ctrl_char);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -267,7 +259,7 @@ fn char_to_signal(ch: u8, termios: &KernelTermios) -> Option<SigNum> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ctrl_char_to_printable(ch: u8) -> char {
|
fn ctrl_char_to_printable(ch: u8) -> u8 {
|
||||||
debug_assert!(is_ctrl_char(ch));
|
debug_assert!(is_ctrl_char(ch));
|
||||||
char::from_u32((ch + b'A' - 1) as u32).unwrap()
|
ch + b'A' - 1
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use ostd::{early_print, sync::LocalIrqDisabled};
|
use ostd::sync::LocalIrqDisabled;
|
||||||
use spin::Once;
|
|
||||||
|
|
||||||
use self::{driver::TtyDriver, line_discipline::LineDiscipline};
|
use self::line_discipline::LineDiscipline;
|
||||||
use crate::{
|
use crate::{
|
||||||
current_userspace,
|
current_userspace,
|
||||||
events::IoEvents,
|
events::IoEvents,
|
||||||
@ -21,65 +20,65 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod device;
|
mod device;
|
||||||
pub mod driver;
|
mod driver;
|
||||||
pub mod line_discipline;
|
mod line_discipline;
|
||||||
pub mod termio;
|
mod n_tty;
|
||||||
|
mod termio;
|
||||||
|
|
||||||
pub use device::TtyDevice;
|
pub use device::TtyDevice;
|
||||||
|
pub use driver::TtyDriver;
|
||||||
static N_TTY: Once<Arc<Tty>> = Once::new();
|
pub(super) use n_tty::init;
|
||||||
|
pub use n_tty::{iter_n_tty, system_console};
|
||||||
pub(super) fn init() {
|
|
||||||
let name = CString::new("console").unwrap();
|
|
||||||
let tty = Tty::new(name);
|
|
||||||
N_TTY.call_once(|| tty);
|
|
||||||
driver::init();
|
|
||||||
}
|
|
||||||
|
|
||||||
const IO_CAPACITY: usize = 4096;
|
const IO_CAPACITY: usize = 4096;
|
||||||
|
|
||||||
pub struct Tty {
|
/// A teletyper (TTY).
|
||||||
/// tty_name
|
///
|
||||||
#[expect(unused)]
|
/// This abstracts the general functionality of a TTY in a way that
|
||||||
name: CString,
|
/// - Any input device driver can use [`Tty::push_input`] to push input characters, and users can
|
||||||
/// line discipline
|
/// [`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<D> {
|
||||||
|
index: u32,
|
||||||
|
driver: D,
|
||||||
ldisc: SpinLock<LineDiscipline, LocalIrqDisabled>,
|
ldisc: SpinLock<LineDiscipline, LocalIrqDisabled>,
|
||||||
job_control: JobControl,
|
job_control: JobControl,
|
||||||
pollee: Pollee,
|
pollee: Pollee,
|
||||||
/// driver
|
|
||||||
driver: SpinLock<Weak<TtyDriver>>,
|
|
||||||
weak_self: Weak<Self>,
|
weak_self: Weak<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tty {
|
impl<D> Tty<D> {
|
||||||
pub fn new(name: CString) -> Arc<Self> {
|
pub fn new(index: u32, driver: D) -> Arc<Self> {
|
||||||
Arc::new_cyclic(move |weak_ref| Tty {
|
Arc::new_cyclic(move |weak_ref| Tty {
|
||||||
name,
|
index,
|
||||||
|
driver,
|
||||||
ldisc: SpinLock::new(LineDiscipline::new()),
|
ldisc: SpinLock::new(LineDiscipline::new()),
|
||||||
job_control: JobControl::new(),
|
job_control: JobControl::new(),
|
||||||
pollee: Pollee::new(),
|
pollee: Pollee::new(),
|
||||||
driver: SpinLock::new(Weak::new()),
|
|
||||||
weak_self: weak_ref.clone(),
|
weak_self: weak_ref.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_driver(&self, driver: Weak<TtyDriver>) {
|
pub fn index(&self) -> u32 {
|
||||||
*self.driver.disable_irq().lock() = driver;
|
self.index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_char(&self, ch: u8) {
|
pub fn driver(&self) -> &D {
|
||||||
// FIXME: Use `early_print` to avoid calling virtio-console.
|
&self.driver
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_io_events(&self) -> IoEvents {
|
fn check_io_events(&self) -> IoEvents {
|
||||||
@ -91,14 +90,34 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pollable for Tty {
|
impl<D: TtyDriver> Tty<D> {
|
||||||
|
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<D> Pollable for Tty<D> {
|
||||||
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
|
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
|
||||||
self.pollee
|
self.pollee
|
||||||
.poll_with(mask, poller, || self.check_io_events())
|
.poll_with(mask, poller, || self.check_io_events())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileIo for Tty {
|
impl<D: TtyDriver> FileIo for Tty<D> {
|
||||||
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
|
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
|
||||||
self.job_control.wait_until_in_foreground()?;
|
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 mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
|
||||||
let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
|
let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
|
||||||
|
|
||||||
if let Ok(content) = alloc::str::from_utf8(&buf[..write_len]) {
|
self.driver.push_output(&buf[..write_len]);
|
||||||
print!("{content}");
|
|
||||||
} else {
|
|
||||||
println!("Not utf-8 content: {:?}", buf);
|
|
||||||
}
|
|
||||||
Ok(write_len)
|
Ok(write_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +155,9 @@ impl FileIo for Tty {
|
|||||||
IoctlCmd::TCSETSW => {
|
IoctlCmd::TCSETSW => {
|
||||||
let termios = current_userspace!().read_val(arg)?;
|
let termios = current_userspace!().read_val(arg)?;
|
||||||
|
|
||||||
self.ldisc.lock().set_termios(termios);
|
let mut ldisc = self.ldisc.lock();
|
||||||
// TODO: Drain the output buffer
|
ldisc.set_termios(termios);
|
||||||
|
self.driver.drain_output();
|
||||||
}
|
}
|
||||||
IoctlCmd::TCSETSF => {
|
IoctlCmd::TCSETSF => {
|
||||||
let termios = current_userspace!().read_val(arg)?;
|
let termios = current_userspace!().read_val(arg)?;
|
||||||
@ -149,7 +165,7 @@ impl FileIo for Tty {
|
|||||||
let mut ldisc = self.ldisc.lock();
|
let mut ldisc = self.ldisc.lock();
|
||||||
ldisc.set_termios(termios);
|
ldisc.set_termios(termios);
|
||||||
ldisc.drain_input();
|
ldisc.drain_input();
|
||||||
// TODO: Drain the output buffer
|
self.driver.drain_output();
|
||||||
|
|
||||||
self.pollee.invalidate();
|
self.pollee.invalidate();
|
||||||
}
|
}
|
||||||
@ -163,6 +179,16 @@ impl FileIo for Tty {
|
|||||||
|
|
||||||
self.ldisc.lock().set_window_size(winsize);
|
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<dyn Terminal>)
|
_ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)
|
||||||
.job_ioctl(cmd, arg, false)?,
|
.job_ioctl(cmd, arg, false)?,
|
||||||
}
|
}
|
||||||
@ -171,23 +197,18 @@ impl FileIo for Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Terminal for Tty {
|
impl<D: TtyDriver> Terminal for Tty<D> {
|
||||||
fn job_control(&self) -> &JobControl {
|
fn job_control(&self) -> &JobControl {
|
||||||
&self.job_control
|
&self.job_control
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for Tty {
|
impl<D: TtyDriver> Device for Tty<D> {
|
||||||
fn type_(&self) -> DeviceType {
|
fn type_(&self) -> DeviceType {
|
||||||
DeviceType::CharDevice
|
DeviceType::CharDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> DeviceId {
|
fn id(&self) -> DeviceId {
|
||||||
// The same value as /dev/console in linux.
|
DeviceId::new(88, self.index)
|
||||||
DeviceId::new(88, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_n_tty() -> &'static Arc<Tty> {
|
|
||||||
N_TTY.get().unwrap()
|
|
||||||
}
|
|
||||||
|
77
kernel/src/device/tty/n_tty.rs
Normal file
77
kernel/src/device/tty/n_tty.rs
Normal file
@ -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<dyn AnyConsoleDevice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Box<[Arc<Tty<ConsoleDriver>>]>> = 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<dyn AnyConsoleDevice>) -> Arc<Tty<ConsoleDriver>> {
|
||||||
|
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<Infallible>| {
|
||||||
|
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<Tty<ConsoleDriver>> {
|
||||||
|
&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<Item = &'static Arc<Tty<ConsoleDriver>>> {
|
||||||
|
N_TTY.get().unwrap().iter()
|
||||||
|
}
|
@ -37,7 +37,7 @@ pub fn spawn_init_process(
|
|||||||
set_session_and_group(&process);
|
set_session_and_group(&process);
|
||||||
|
|
||||||
// FIXME: This should be done by the userspace init process.
|
// FIXME: This should be done by the userspace init process.
|
||||||
(crate::device::tty::get_n_tty().clone() as Arc<dyn Terminal>).set_control(&process)?;
|
(crate::device::tty::system_console().clone() as Arc<dyn Terminal>).set_control(&process)?;
|
||||||
|
|
||||||
process.run();
|
process.run();
|
||||||
|
|
||||||
|
@ -731,15 +731,14 @@ pub fn enqueue_signal_async(process: Weak<Process>, signum: SigNum) {
|
|||||||
/// This is the asynchronous version of [`ProcessGroup::broadcast_signal`]. By asynchronous, this
|
/// 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
|
/// method submits a work item and returns, so this method doesn't sleep and can be used in atomic
|
||||||
/// mode.
|
/// mode.
|
||||||
pub fn broadcast_signal_async(process_group: Weak<ProcessGroup>, signal: SigNum) {
|
pub fn broadcast_signal_async(process_group: Weak<ProcessGroup>, signum: SigNum) {
|
||||||
use super::signal::signals::kernel::KernelSignal;
|
use super::signal::signals::kernel::KernelSignal;
|
||||||
use crate::thread::work_queue;
|
use crate::thread::work_queue;
|
||||||
|
|
||||||
let signal = KernelSignal::new(signal);
|
|
||||||
work_queue::submit_work_func(
|
work_queue::submit_work_func(
|
||||||
move || {
|
move || {
|
||||||
if let Some(process_group) = process_group.upgrade() {
|
if let Some(process_group) = process_group.upgrade() {
|
||||||
process_group.broadcast_signal(signal);
|
process_group.broadcast_signal(KernelSignal::new(signum));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
work_queue::WorkPriority::High,
|
work_queue::WorkPriority::High,
|
||||||
|
Reference in New Issue
Block a user