Make the semantics of tty-related devices correct
This commit is contained in:
parent
1b11a8453e
commit
e048a76afc
8
Makefile
8
Makefile
|
|
@ -22,6 +22,13 @@ FEATURES ?=
|
|||
NO_DEFAULT_FEATURES ?= 0
|
||||
COVERAGE ?= 0
|
||||
ENABLE_BASIC_TEST ?= false
|
||||
# Specify the primary system console (supported: hvc0, tty0).
|
||||
# - hvc0: The virtio-console terminal.
|
||||
# - tty0: The active virtual terminal (VT).
|
||||
# Asterinas will automatically fall back to tty0 if hvc0 is not available.
|
||||
# Note that currently the virtual terminal (tty0) can only work with
|
||||
# linux-efi-handover64 and linux-efi-pe64 boot protocol.
|
||||
CONSOLE ?= hvc0
|
||||
# End of global build options.
|
||||
|
||||
# GDB debugging and profiling options.
|
||||
|
|
@ -57,6 +64,7 @@ CARGO_OSDK := ~/.cargo/bin/cargo-osdk
|
|||
CARGO_OSDK_COMMON_ARGS := --target-arch=$(OSDK_TARGET_ARCH)
|
||||
# The build arguments also apply to the `cargo osdk run` command.
|
||||
CARGO_OSDK_BUILD_ARGS := --kcmd-args="ostd.log_level=$(LOG_LEVEL)"
|
||||
CARGO_OSDK_BUILD_ARGS += --kcmd-args="console=$(CONSOLE)"
|
||||
CARGO_OSDK_TEST_ARGS :=
|
||||
|
||||
ifeq ($(AUTO_TEST), syscall)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_console::{
|
||||
font::BitmapFont,
|
||||
mode::{ConsoleMode, KeyboardMode},
|
||||
AnyConsoleDevice, ConsoleCallback, ConsoleSetFontError,
|
||||
};
|
||||
|
||||
/// A dummy console device.
|
||||
///
|
||||
/// This is used when no framebuffer is available. All operations are no-ops.
|
||||
#[derive(Debug)]
|
||||
pub struct DummyFramebufferConsole;
|
||||
|
||||
impl AnyConsoleDevice for DummyFramebufferConsole {
|
||||
fn send(&self, _buf: &[u8]) {}
|
||||
|
||||
fn register_callback(&self, _callback: &'static ConsoleCallback) {}
|
||||
|
||||
fn set_font(&self, _font: BitmapFont) -> Result<(), ConsoleSetFontError> {
|
||||
Err(ConsoleSetFontError::InappropriateDevice)
|
||||
}
|
||||
|
||||
fn set_mode(&self, _mode: ConsoleMode) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn mode(&self) -> Option<ConsoleMode> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_keyboard_mode(&self, _mode: KeyboardMode) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn keyboard_mode(&self) -> Option<KeyboardMode> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,13 @@ extern crate alloc;
|
|||
mod ansi_escape;
|
||||
mod console;
|
||||
mod console_input;
|
||||
mod dummy_console;
|
||||
mod framebuffer;
|
||||
mod pixel;
|
||||
|
||||
use component::{init_component, ComponentInitError};
|
||||
pub use console::{FramebufferConsole, CONSOLE_NAME, FRAMEBUFFER_CONSOLE};
|
||||
pub use dummy_console::DummyFramebufferConsole;
|
||||
pub use framebuffer::{ColorMapEntry, FrameBuffer, FRAMEBUFFER, MAX_CMAP_SIZE};
|
||||
pub use pixel::{Pixel, PixelFormat, RenderedPixel};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ mod pty;
|
|||
mod shm;
|
||||
pub mod tty;
|
||||
|
||||
use alloc::format;
|
||||
|
||||
use device_id::DeviceId;
|
||||
pub use mem::{getrandom, geturandom};
|
||||
pub use pty::{new_pty_pair, PtyMaster, PtySlave};
|
||||
|
|
@ -43,16 +41,22 @@ pub fn init_in_first_process(ctx: &Context) -> Result<()> {
|
|||
let dev_path = fs_resolver.lookup(&FsPath::try_from("/dev")?)?;
|
||||
dev_path.mount(RamFs::new(), PerMountFlags::default(), ctx)?;
|
||||
|
||||
tty::init();
|
||||
tty::init_in_first_process();
|
||||
|
||||
let tty0 = Arc::new(tty::Tty0Device);
|
||||
add_node(tty0, "tty0", &fs_resolver)?;
|
||||
|
||||
let tty1 = tty::tty1_device().clone();
|
||||
add_node(tty1, "tty1", &fs_resolver)?;
|
||||
|
||||
let tty = Arc::new(tty::TtyDevice);
|
||||
add_node(tty, "tty", &fs_resolver)?;
|
||||
|
||||
let console = tty::system_console().clone();
|
||||
let console = tty::SystemConsole::singleton().clone();
|
||||
add_node(console, "console", &fs_resolver)?;
|
||||
|
||||
for (index, tty) in tty::iter_n_tty().enumerate() {
|
||||
add_node(tty.clone(), &format!("tty{}", index), &fs_resolver)?;
|
||||
if let Some(hvc0) = tty::hvc0_device() {
|
||||
add_node(hvc0.clone(), "hvc0", &fs_resolver)?;
|
||||
}
|
||||
|
||||
pty::init_in_first_process(&fs_resolver, ctx)?;
|
||||
|
|
@ -75,18 +79,18 @@ pub fn get_device(devid: DeviceId) -> Result<Arc<dyn Device>> {
|
|||
let minor = devid.minor().get();
|
||||
|
||||
match (major, minor) {
|
||||
(4, minor) => {
|
||||
let Some(tty) = tty::iter_n_tty().nth(minor as usize) else {
|
||||
return_errno_with_message!(Errno::EINVAL, "the TTY minor ID is invalid");
|
||||
};
|
||||
Ok(tty.clone())
|
||||
}
|
||||
(4, 0) => Ok(Arc::new(tty::Tty0Device)),
|
||||
(4, 1) => Ok(tty::tty1_device().clone()),
|
||||
(5, 0) => Ok(Arc::new(tty::TtyDevice)),
|
||||
(5, 1) => Ok(tty::SystemConsole::singleton().clone()),
|
||||
(229, 0) => tty::hvc0_device()
|
||||
.cloned()
|
||||
.map(|device| device as _)
|
||||
.ok_or_else(|| Error::with_message(Errno::ENODEV, "the hvc0 device is not available")),
|
||||
_ => char::lookup(devid)
|
||||
.map(|device| Arc::new(char::CharFile::new(device)) as Arc<dyn Device>)
|
||||
.ok_or(Error::with_message(
|
||||
Errno::EINVAL,
|
||||
"the device ID is invalid or unsupported",
|
||||
)),
|
||||
.ok_or_else(|| {
|
||||
Error::with_message(Errno::EINVAL, "the device ID is invalid or unsupported")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,69 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! TTY devices.
|
||||
//!
|
||||
//! This module implements TTY devices such as `/dev/tty0`, `/dev/tty`, and `/dev/console`.
|
||||
//!
|
||||
//! Reference: <https://www.kernel.org/doc/html/latest/admin-guide/devices.html>
|
||||
|
||||
use device_id::{DeviceId, MajorId, MinorId};
|
||||
use spin::Once;
|
||||
|
||||
use crate::{
|
||||
device::tty::{hvc0_device, n_tty::VtDriver, tty1_device, Tty},
|
||||
fs::{
|
||||
device::{Device, DeviceType},
|
||||
inode_handle::FileIo,
|
||||
},
|
||||
kcmdline::KCmdlineArg,
|
||||
prelude::*,
|
||||
process::{JobControl, Terminal},
|
||||
};
|
||||
|
||||
/// Corresponds to `/dev/tty0` in the file system. This device represents the active virtual
|
||||
/// terminal.
|
||||
pub struct Tty0Device;
|
||||
|
||||
impl Tty0Device {
|
||||
fn active_vt(&self) -> &Arc<Tty<VtDriver>> {
|
||||
// Currently there is only one virtual terminal `tty1`.
|
||||
tty1_device()
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for Tty0Device {
|
||||
fn type_(&self) -> DeviceType {
|
||||
DeviceType::Char
|
||||
}
|
||||
|
||||
fn id(&self) -> DeviceId {
|
||||
DeviceId::new(MajorId::new(4), MinorId::new(0))
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<Box<dyn FileIo>> {
|
||||
self.active_vt().open()
|
||||
}
|
||||
}
|
||||
|
||||
impl Terminal for Tty0Device {
|
||||
fn job_control(&self) -> &JobControl {
|
||||
self.active_vt().job_control()
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `/dev/tty` in the file system. This device represents the controlling terminal
|
||||
/// of the session of current process.
|
||||
/// of the session of the current process.
|
||||
pub struct TtyDevice;
|
||||
|
||||
impl Device for TtyDevice {
|
||||
fn type_(&self) -> DeviceType {
|
||||
DeviceType::Char
|
||||
}
|
||||
|
||||
fn id(&self) -> DeviceId {
|
||||
DeviceId::new(MajorId::new(5), MinorId::new(0))
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<Box<dyn FileIo>> {
|
||||
let Some(terminal) = current!().terminal() else {
|
||||
return_errno_with_message!(
|
||||
|
|
@ -25,12 +74,60 @@ impl Device for TtyDevice {
|
|||
|
||||
terminal.open()
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `/dev/console` in the file system. This device represents a console to which
|
||||
/// system messages will be sent.
|
||||
pub struct SystemConsole {
|
||||
inner: Arc<dyn Terminal>,
|
||||
}
|
||||
|
||||
impl SystemConsole {
|
||||
/// Returns the singleton instance of the console device.
|
||||
pub fn singleton() -> &'static Arc<SystemConsole> {
|
||||
static INSTANCE: Once<Arc<SystemConsole>> = Once::new();
|
||||
|
||||
INSTANCE.call_once(|| {
|
||||
// TODO: Support specifying multiple TTY devices, e.g., "console=hvc0 console=tty0".
|
||||
let console_name = KCmdlineArg::singleton()
|
||||
.get_console_names()
|
||||
.first()
|
||||
.map(String::as_str)
|
||||
.unwrap_or("tty0");
|
||||
|
||||
let device = match console_name {
|
||||
"tty0" => Some(Arc::new(Tty0Device) as _),
|
||||
"hvc0" => hvc0_device().cloned().map(|device| device as _),
|
||||
_ => None,
|
||||
};
|
||||
let inner = device.unwrap_or_else(|| {
|
||||
warn!(
|
||||
"'{}' console not found, falling back to 'tty0'",
|
||||
console_name
|
||||
);
|
||||
Arc::new(Tty0Device) as _
|
||||
});
|
||||
|
||||
Arc::new(Self { inner })
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the terminal associated with the console device.
|
||||
pub fn terminal(&self) -> &Arc<dyn Terminal> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for SystemConsole {
|
||||
fn type_(&self) -> DeviceType {
|
||||
DeviceType::Char
|
||||
}
|
||||
|
||||
fn id(&self) -> DeviceId {
|
||||
DeviceId::new(MajorId::new(5), MinorId::new(0))
|
||||
DeviceId::new(MajorId::new(5), MinorId::new(1))
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<Box<dyn FileIo>> {
|
||||
self.inner.open()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ mod line_discipline;
|
|||
mod n_tty;
|
||||
mod termio;
|
||||
|
||||
pub use device::TtyDevice;
|
||||
pub use device::{SystemConsole, Tty0Device, TtyDevice};
|
||||
pub use driver::TtyDriver;
|
||||
pub(super) use flags::TtyFlags;
|
||||
pub(super) use n_tty::init;
|
||||
pub use n_tty::{iter_n_tty, system_console};
|
||||
pub(super) use n_tty::init_in_first_process;
|
||||
pub use n_tty::{hvc0_device, tty1_device};
|
||||
|
||||
const IO_CAPACITY: usize = 4096;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, 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;
|
||||
|
|
@ -16,16 +19,20 @@ use crate::{
|
|||
process::signal::{PollHandle, Pollable},
|
||||
};
|
||||
|
||||
pub struct ConsoleDriver {
|
||||
/// 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 ConsoleDriver {
|
||||
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 open(tty: Arc<Tty<Self>>) -> Result<Box<dyn FileIo>> {
|
||||
Ok(Box::new(ConsoleFile(tty)))
|
||||
Ok(Box::new(TtyFile(tty)))
|
||||
}
|
||||
|
||||
fn push_output(&self, chs: &[u8]) -> Result<usize> {
|
||||
|
|
@ -50,14 +57,50 @@ impl TtyDriver for ConsoleDriver {
|
|||
}
|
||||
}
|
||||
|
||||
struct ConsoleFile(Arc<Tty<ConsoleDriver>>);
|
||||
/// 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 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 drain_output(&self) {}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
struct TtyFile<D>(Arc<Tty<D>>);
|
||||
|
||||
#[inherit_methods(from = "self.0")]
|
||||
impl Pollable for ConsoleFile {
|
||||
impl<D: TtyDriver> Pollable for TtyFile<D> {
|
||||
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents;
|
||||
}
|
||||
|
||||
impl InodeIo for ConsoleFile {
|
||||
impl<D: TtyDriver> InodeIo for TtyFile<D> {
|
||||
fn read_at(
|
||||
&self,
|
||||
_offset: usize,
|
||||
|
|
@ -78,7 +121,7 @@ impl InodeIo for ConsoleFile {
|
|||
}
|
||||
|
||||
#[inherit_methods(from = "self.0")]
|
||||
impl FileIo for ConsoleFile {
|
||||
impl<D: TtyDriver> FileIo for TtyFile<D> {
|
||||
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32>;
|
||||
|
||||
fn check_seekable(&self) -> Result<()> {
|
||||
|
|
@ -90,54 +133,71 @@ impl FileIo for ConsoleFile {
|
|||
}
|
||||
}
|
||||
|
||||
static N_TTY: Once<Box<[Arc<Tty<ConsoleDriver>>]>> = Once::new();
|
||||
static TTY1: Once<Arc<Tty<VtDriver>>> = 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
|
||||
};
|
||||
static HVC0: Once<Arc<Tty<HvcDriver>>> = Once::new();
|
||||
|
||||
let ttys = devices
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, device))| create_n_tty(index as _, device))
|
||||
.collect();
|
||||
N_TTY.call_once(|| ttys);
|
||||
/// 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()
|
||||
}
|
||||
|
||||
fn create_n_tty(index: u32, device: Arc<dyn AnyConsoleDevice>) -> Arc<Tty<ConsoleDriver>> {
|
||||
let driver = ConsoleDriver {
|
||||
console: device.clone(),
|
||||
/// 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(in crate::device) fn init_in_first_process() {
|
||||
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());
|
||||
|
||||
let tty = Tty::new(index, driver);
|
||||
let tty_cloned = tty.clone();
|
||||
|
||||
device.register_callback(Box::leak(Box::new(
|
||||
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 _ = tty.push_input(chs.as_slice());
|
||||
let _ = tty1.push_input(chs.as_slice());
|
||||
},
|
||||
)));
|
||||
|
||||
tty_cloned
|
||||
}
|
||||
// Initialize the `hvc0` device if the virtio console is available.
|
||||
|
||||
/// Returns the system console, i.e., `/dev/console`.
|
||||
pub fn system_console() -> &'static Arc<Tty<ConsoleDriver>> {
|
||||
&N_TTY.get().unwrap()[0]
|
||||
}
|
||||
let virtio_console = devices
|
||||
.iter()
|
||||
.find(|(name, _)| name.as_str() == aster_virtio::device::console::DEVICE_NAME)
|
||||
.map(|(_, device)| device.clone());
|
||||
|
||||
/// 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()
|
||||
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());
|
||||
|
||||
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());
|
||||
},
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ pub(super) fn init_on_each_cpu() {
|
|||
|
||||
pub(super) fn init_in_first_process(ctx: &Context) {
|
||||
// FIXME: This should be done by the userspace init process.
|
||||
(crate::device::tty::system_console().clone() as Arc<dyn Terminal>)
|
||||
(crate::device::tty::SystemConsole::singleton()
|
||||
.terminal()
|
||||
.clone() as Arc<dyn Terminal>)
|
||||
.set_control(ctx.process.as_ref())
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue