Refactor TTY device management and update the relevant ioctl processing logic

This commit is contained in:
wyt8 2026-02-10 16:57:55 +00:00
parent 6f6812ff61
commit 77b6d82809
7 changed files with 293 additions and 355 deletions

View File

@ -10,14 +10,11 @@ use aster_cmdline::KCMDLINE;
use device_id::{DeviceId, MajorId, MinorId};
use spin::Once;
use super::n_tty::tty1_device;
use super::vt::active_vt;
use crate::{
device::{
registry::char,
tty::{
Tty,
n_tty::{VtDriver, hvc0_device, serial0_device},
},
tty::{Tty, TtyFile, hvc::hvc0_device, serial::serial0_device, vt::VtDriver},
},
fs::{
device::{Device, DeviceType},
@ -32,9 +29,8 @@ use crate::{
pub struct Tty0Device;
impl Tty0Device {
fn active_vt(&self) -> &Arc<Tty<VtDriver>> {
// Currently there is only one virtual terminal `tty1`.
tty1_device()
fn active_vt(&self) -> Arc<Tty<VtDriver>> {
active_vt()
}
}
@ -52,12 +48,12 @@ impl Device for Tty0Device {
}
fn open(&self) -> Result<Box<dyn FileIo>> {
self.active_vt().open()
Ok(Box::new(TtyFile(self.active_vt())))
}
}
impl Terminal for Tty0Device {
fn job_control(&self) -> &JobControl {
fn job_control(&self) -> Arc<JobControl> {
self.active_vt().job_control()
}
}

View File

@ -6,6 +6,7 @@ use crate::{
device::tty::{Tty, termio::CTermios},
fs::inode_handle::FileIo,
prelude::*,
util::ioctl::RawIoctl,
};
/// A TTY driver.
@ -64,4 +65,24 @@ pub trait TtyDriver: Send + Sync + 'static {
///
/// 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);
/// Driver-specific ioctl handler.
///
/// This method allows a TTY driver to handle driver-specific
/// ioctl commands that are not processed by the generic TTY layer.
///
/// Semantics:
/// - If the driver recognizes and handles the ioctl, it should return
/// `Ok(Some(retval))`, where `retval` is the value returned to userspace.
/// - If the driver does not recognize the ioctl, it should return
/// `Ok(None)` to indicate that the request should be handled by higher
/// layers or reported as unsupported.
/// - If an error occurs while processing the ioctl, it should return
/// `Err(...)`.
fn ioctl(&self, _tty: &Tty<Self>, _raw: RawIoctl) -> Result<Option<i32>>
where
Self: Sized,
{
Ok(None)
}
}

View File

@ -0,0 +1,97 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::{boxed::Box, format, sync::Arc};
use aster_console::AnyConsoleDevice;
use ostd::mm::{Infallible, VmReader, VmWriter};
use spin::Once;
use super::{Tty, TtyDriver};
use crate::{
device::{
registry::char,
tty::{TtyFile, termio::CTermios},
},
fs::inode_handle::FileIo,
prelude::*,
};
/// The driver for hypervisor console devices.
#[derive(Clone)]
pub struct HvcDriver {
console: Arc<dyn AnyConsoleDevice>,
}
impl TtyDriver for HvcDriver {
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/Documentation/admin-guide/devices.txt#L2936>.
const DEVICE_MAJOR_ID: u32 = 229;
fn devtmpfs_path(&self, index: u32) -> Option<String> {
Some(format!("hvc{}", index))
}
fn open(tty: Arc<Tty<Self>>) -> Result<Box<dyn FileIo>> {
Ok(Box::new(TtyFile(tty)))
}
fn push_output(&self, chs: &[u8]) -> Result<usize> {
self.console.send(chs);
Ok(chs.len())
}
fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ {
|chs| self.console.send(chs)
}
fn can_push(&self) -> bool {
true
}
fn notify_input(&self) {}
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
Some(&*self.console)
}
fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {}
}
static HVC0: Once<Arc<Tty<HvcDriver>>> = Once::new();
/// Returns the `hvc0` device.
///
/// Returns `None` if the device is not found nor initialized.
pub fn hvc0_device() -> Option<&'static Arc<Tty<HvcDriver>>> {
HVC0.get()
}
pub(super) fn init_in_first_process() -> Result<()> {
let devices = aster_console::all_devices();
// Initialize the `hvc0` device if the virtio console is available.
let virtio_console = devices
.iter()
.find(|(name, _)| name.as_str() == aster_virtio::device::console::DEVICE_NAME)
.map(|(_, device)| device.clone());
if let Some(virtio_console) = virtio_console {
let driver = HvcDriver {
console: virtio_console.clone(),
};
let hvc0 = Tty::new(0, driver);
HVC0.call_once(|| hvc0.clone());
char::register(hvc0.clone())?;
virtio_console.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()));
let _ = hvc0.push_input(chs.as_slice());
},
)));
}
Ok(())
}

View File

@ -4,7 +4,7 @@ use super::{
CFontOp,
termio::{CTermios, CWinSize},
};
use crate::util::ioctl::{InData, OutData, PassByVal, ioc};
use crate::util::ioctl::{InData, OutData, ioc};
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/asm-generic/ioctls.h>
@ -19,10 +19,4 @@ pub type SetWinSize = ioc!(TIOCSWINSZ, 0x5414, InData<CWinSize>);
// TODO: Consider moving this to the `pty` module.
pub type GetPtyNumber = ioc!(TIOCGPTN, b'T', 0x30, OutData<u32>);
pub type SetGraphicsMode = ioc!(KDSETMODE, 0x4B3A, InData<i32, PassByVal>);
pub type GetGraphicsMode = ioc!(KDGETMODE, 0x4B3B, OutData<i32>);
pub type GetKeyboardMode = ioc!(KDGKBMODE, 0x4B44, OutData<i32>);
pub type SetKeyboardMode = ioc!(KDSKBMODE, 0x4B45, InData<i32, PassByVal>);
pub type SetOrGetFont = ioc!(KDFONTOP, 0x4B72, InData<CFontOp>);

View File

@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
use aster_console::{
AnyConsoleDevice,
font::BitmapFont,
mode::{ConsoleMode, KeyboardMode},
};
use aster_console::{AnyConsoleDevice, font::BitmapFont};
use device_id::{DeviceId, MajorId, MinorId};
use inherit_methods_macro::inherit_methods;
use ostd::{mm::VmIo, sync::LocalIrqDisabled};
use self::{line_discipline::LineDiscipline, termio::CFontOp};
@ -15,7 +12,7 @@ use crate::{
fs::{
device::{Device, DeviceType},
inode_handle::FileIo,
utils::StatusFlags,
utils::{InodeIo, StatusFlags},
},
prelude::*,
process::{
@ -28,18 +25,22 @@ use crate::{
mod device;
mod driver;
mod flags;
mod hvc;
pub(super) mod ioctl_defs;
mod line_discipline;
mod n_tty;
mod serial;
pub(super) mod termio;
mod vt;
pub use device::SystemConsole;
pub use driver::TtyDriver;
pub(super) use flags::TtyFlags;
pub(super) fn init_in_first_process() -> Result<()> {
n_tty::init_in_first_process()?;
hvc::init_in_first_process()?;
serial::init_in_first_process()?;
device::init_in_first_process()?;
vt::init_in_first_process()?;
Ok(())
}
@ -70,7 +71,7 @@ pub struct Tty<D> {
index: u32,
driver: D,
ldisc: SpinLock<LineDiscipline, LocalIrqDisabled>,
job_control: JobControl,
job_control: Arc<JobControl>,
pollee: Pollee,
tty_flags: TtyFlags,
weak_self: Weak<Self>,
@ -82,7 +83,7 @@ impl<D> Tty<D> {
index,
driver,
ldisc: SpinLock::new(LineDiscipline::new()),
job_control: JobControl::new(),
job_control: Arc::new(JobControl::new()),
pollee: Pollee::new(),
tty_flags: TtyFlags::new(),
weak_self: weak_ref.clone(),
@ -376,37 +377,22 @@ impl<D: TtyDriver> Tty<D> {
self.handle_set_font(&font_op)?;
}
cmd @ SetGraphicsMode => {
let console = self.console()?;
let mode = ConsoleMode::try_from(cmd.get())?;
if !console.set_mode(mode) {
return_errno_with_message!(Errno::EINVAL, "the console mode is not supported");
_ => {
if (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)
.job_ioctl(raw_ioctl, false)?
.is_none()
{
match self.driver.ioctl(self, raw_ioctl)? {
Some(ret) => return Ok(ret),
None => {
return Err(crate::prelude::Error::with_message(
Errno::ENOTTY,
"unhandled ioctl command",
));
}
}
}
}
cmd @ GetGraphicsMode => {
let console = self.console()?;
let mode = console.mode().unwrap_or(ConsoleMode::Text);
cmd.write(&(mode as i32))?;
}
cmd @ SetKeyboardMode => {
let console = self.console()?;
let mode = KeyboardMode::try_from(cmd.get())?;
if !console.set_keyboard_mode(mode) {
return_errno_with_message!(Errno::EINVAL, "the keyboard mode is not supported");
}
}
cmd @ GetKeyboardMode => {
let console = self.console()?;
let mode = console.keyboard_mode().unwrap_or(KeyboardMode::Xlate);
cmd.write(&(mode as i32))?;
}
_ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)
.job_ioctl(raw_ioctl, false)?,
});
Ok(0)
@ -414,8 +400,8 @@ impl<D: TtyDriver> Tty<D> {
}
impl<D: TtyDriver> Terminal for Tty<D> {
fn job_control(&self) -> &JobControl {
&self.job_control
fn job_control(&self) -> Arc<JobControl> {
self.job_control.clone()
}
}
@ -439,3 +425,43 @@ impl<D: TtyDriver> Device for Tty<D> {
D::open(self.weak_self.upgrade().unwrap())
}
}
struct TtyFile<D>(Arc<Tty<D>>);
#[inherit_methods(from = "self.0")]
impl<D: TtyDriver> Pollable for TtyFile<D> {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents;
}
impl<D: TtyDriver> InodeIo for TtyFile<D> {
fn read_at(
&self,
_offset: usize,
writer: &mut VmWriter,
status_flags: StatusFlags,
) -> Result<usize> {
self.0.read(writer, status_flags)
}
fn write_at(
&self,
_offset: usize,
reader: &mut VmReader,
status_flags: StatusFlags,
) -> Result<usize> {
self.0.write(reader, status_flags)
}
}
#[inherit_methods(from = "self.0")]
impl<D: TtyDriver> FileIo for TtyFile<D> {
fn ioctl(&self, raw_ioctl: RawIoctl) -> Result<i32>;
fn check_seekable(&self) -> Result<()> {
return_errno_with_message!(Errno::ESPIPE, "the inode is a TTY");
}
fn is_offset_aware(&self) -> bool {
false
}
}

View File

@ -1,297 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::{boxed::Box, format, sync::Arc};
use aster_console::AnyConsoleDevice;
use aster_framebuffer::DummyFramebufferConsole;
use inherit_methods_macro::inherit_methods;
use ostd::mm::{Infallible, VmReader, VmWriter};
use spin::Once;
use super::{Tty, TtyDriver};
use crate::{
device::{registry::char, tty::termio::CTermios},
events::IoEvents,
fs::{
inode_handle::FileIo,
utils::{InodeIo, StatusFlags},
},
prelude::*,
process::signal::{PollHandle, Pollable},
util::ioctl::RawIoctl,
};
/// The driver for VT (virtual terminal) devices.
//
// TODO: This driver needs to support more features for future VT management.
#[derive(Clone)]
pub struct VtDriver {
console: Arc<dyn AnyConsoleDevice>,
}
impl TtyDriver for VtDriver {
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/include/uapi/linux/major.h#L18>.
const DEVICE_MAJOR_ID: u32 = 4;
fn devtmpfs_path(&self, index: u32) -> Option<String> {
Some(format!("tty{}", index))
}
fn open(tty: Arc<Tty<Self>>) -> Result<Box<dyn FileIo>> {
Ok(Box::new(TtyFile(tty)))
}
fn push_output(&self, chs: &[u8]) -> Result<usize> {
self.console.send(chs);
Ok(chs.len())
}
fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ {
|chs| self.console.send(chs)
}
fn can_push(&self) -> bool {
true
}
fn notify_input(&self) {}
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
Some(&*self.console)
}
fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {}
}
/// The driver for serial devices.
#[derive(Clone)]
pub struct SerialDriver {
console: Arc<dyn AnyConsoleDevice>,
}
impl SerialDriver {
const MINOR_ID_BASE: u32 = 64;
}
impl TtyDriver for SerialDriver {
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/include/uapi/linux/major.h#L18>.
const DEVICE_MAJOR_ID: u32 = 4;
fn devtmpfs_path(&self, index: u32) -> Option<String> {
Some(format!("ttyS{}", index - Self::MINOR_ID_BASE))
}
fn open(tty: Arc<Tty<Self>>) -> Result<Box<dyn FileIo>> {
Ok(Box::new(TtyFile(tty)))
}
fn push_output(&self, chs: &[u8]) -> Result<usize> {
self.console.send(chs);
Ok(chs.len())
}
fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ {
|chs| self.console.send(chs)
}
fn can_push(&self) -> bool {
true
}
fn notify_input(&self) {}
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.
#[derive(Clone)]
pub struct HvcDriver {
console: Arc<dyn AnyConsoleDevice>,
}
impl TtyDriver for HvcDriver {
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/Documentation/admin-guide/devices.txt#L2936>.
const DEVICE_MAJOR_ID: u32 = 229;
fn devtmpfs_path(&self, index: u32) -> Option<String> {
Some(format!("hvc{}", index))
}
fn open(tty: Arc<Tty<Self>>) -> Result<Box<dyn FileIo>> {
Ok(Box::new(TtyFile(tty)))
}
fn push_output(&self, chs: &[u8]) -> Result<usize> {
self.console.send(chs);
Ok(chs.len())
}
fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ {
|chs| self.console.send(chs)
}
fn can_push(&self) -> bool {
true
}
fn notify_input(&self) {}
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
Some(&*self.console)
}
fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {}
}
struct TtyFile<D>(Arc<Tty<D>>);
#[inherit_methods(from = "self.0")]
impl<D: TtyDriver> Pollable for TtyFile<D> {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents;
}
impl<D: TtyDriver> InodeIo for TtyFile<D> {
fn read_at(
&self,
_offset: usize,
writer: &mut VmWriter,
status_flags: StatusFlags,
) -> Result<usize> {
self.0.read(writer, status_flags)
}
fn write_at(
&self,
_offset: usize,
reader: &mut VmReader,
status_flags: StatusFlags,
) -> Result<usize> {
self.0.write(reader, status_flags)
}
}
#[inherit_methods(from = "self.0")]
impl<D: TtyDriver> FileIo for TtyFile<D> {
fn ioctl(&self, raw_ioctl: RawIoctl) -> Result<i32>;
fn check_seekable(&self) -> Result<()> {
return_errno_with_message!(Errno::ESPIPE, "the inode is a TTY");
}
fn is_offset_aware(&self) -> bool {
false
}
}
static TTY1: Once<Arc<Tty<VtDriver>>> = Once::new();
static SERIAL0: Once<Arc<Tty<SerialDriver>>> = Once::new();
static HVC0: Once<Arc<Tty<HvcDriver>>> = Once::new();
/// Returns the `tty1` device.
///
/// # Panics
///
/// This function will panic if the `tty1` device has not been initialized.
pub fn tty1_device() -> &'static Arc<Tty<VtDriver>> {
TTY1.get().unwrap()
}
/// Returns the `ttyS0` device.
///
/// Returns `None` if the device is not found nor initialized.
pub fn serial0_device() -> Option<&'static Arc<Tty<SerialDriver>>> {
SERIAL0.get()
}
/// Returns the `hvc0` device.
///
/// Returns `None` if the device is not found nor initialized.
pub fn hvc0_device() -> Option<&'static Arc<Tty<HvcDriver>>> {
HVC0.get()
}
pub(super) fn init_in_first_process() -> Result<()> {
let devices = aster_console::all_devices();
// Initialize the `tty1` device.
let fb_console = devices
.iter()
.find(|(name, _)| name.as_str() == aster_framebuffer::CONSOLE_NAME)
.map(|(_, device)| device.clone())
.unwrap_or_else(|| Arc::new(DummyFramebufferConsole));
let driver = VtDriver {
console: fb_console.clone(),
};
let tty1 = Tty::new(1, driver);
TTY1.call_once(|| tty1.clone());
char::register(tty1.clone())?;
fb_console.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()));
let _ = tty1.push_input(chs.as_slice());
},
)));
// Initialize the `ttyS0` device if the serial console is available.
let serial_console = devices
.iter()
.find(|(name, _)| name.as_str() == aster_uart::CONSOLE_NAME)
.map(|(_, device)| device.clone());
if let Some(serial_console) = serial_console {
let driver = SerialDriver {
console: serial_console.clone(),
};
let serial0 = Tty::new(SerialDriver::MINOR_ID_BASE, driver);
SERIAL0.call_once(|| serial0.clone());
char::register(serial0.clone())?;
serial_console.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()));
let _ = serial0.push_input(chs.as_slice());
},
)));
}
// Initialize the `hvc0` device if the virtio console is available.
let virtio_console = devices
.iter()
.find(|(name, _)| name.as_str() == aster_virtio::device::console::DEVICE_NAME)
.map(|(_, device)| device.clone());
if let Some(virtio_console) = virtio_console {
let driver = HvcDriver {
console: virtio_console.clone(),
};
let hvc0 = Tty::new(0, driver);
HVC0.call_once(|| hvc0.clone());
char::register(hvc0.clone())?;
virtio_console.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()));
let _ = hvc0.push_input(chs.as_slice());
},
)));
}
Ok(())
}

View File

@ -0,0 +1,101 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::{boxed::Box, format, sync::Arc};
use aster_console::AnyConsoleDevice;
use ostd::mm::{Infallible, VmReader, VmWriter};
use spin::Once;
use super::{Tty, TtyDriver};
use crate::{
device::{
registry::char,
tty::{TtyFile, termio::CTermios},
},
fs::inode_handle::FileIo,
prelude::*,
};
/// The driver for serial devices.
#[derive(Clone)]
pub struct SerialDriver {
console: Arc<dyn AnyConsoleDevice>,
}
impl SerialDriver {
const MINOR_ID_BASE: u32 = 64;
}
impl TtyDriver for SerialDriver {
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/include/uapi/linux/major.h#L18>.
const DEVICE_MAJOR_ID: u32 = 4;
fn devtmpfs_path(&self, index: u32) -> Option<String> {
Some(format!("ttyS{}", index - Self::MINOR_ID_BASE))
}
fn open(tty: Arc<Tty<Self>>) -> Result<Box<dyn FileIo>> {
Ok(Box::new(TtyFile(tty)))
}
fn push_output(&self, chs: &[u8]) -> Result<usize> {
self.console.send(chs);
Ok(chs.len())
}
fn echo_callback(&self) -> impl FnMut(&[u8]) + '_ {
|chs| self.console.send(chs)
}
fn can_push(&self) -> bool {
true
}
fn notify_input(&self) {}
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
Some(&*self.console)
}
fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {}
}
static SERIAL0: Once<Arc<Tty<SerialDriver>>> = Once::new();
/// Returns the `ttyS0` device.
///
/// Returns `None` if the device is not found nor initialized.
pub fn serial0_device() -> Option<&'static Arc<Tty<SerialDriver>>> {
SERIAL0.get()
}
pub(super) fn init_in_first_process() -> Result<()> {
let devices = aster_console::all_devices();
// Initialize the `ttyS0` device if the serial console is available.
let serial_console = devices
.iter()
.find(|(name, _)| name.as_str() == aster_uart::CONSOLE_NAME)
.map(|(_, device)| device.clone());
if let Some(serial_console) = serial_console {
let driver = SerialDriver {
console: serial_console.clone(),
};
let serial0 = Tty::new(SerialDriver::MINOR_ID_BASE, driver);
SERIAL0.call_once(|| serial0.clone());
char::register(serial0.clone())?;
serial_console.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()));
let _ = serial0.push_input(chs.as_slice());
},
)));
}
Ok(())
}