Add virtual terminal driver and related functions, including console, file representation, and IOCTL definition
This commit is contained in:
parent
77b6d82809
commit
14dccef3dc
|
|
@ -0,0 +1,298 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
|
||||
use aster_console::{
|
||||
AnyConsoleDevice, ConsoleCallback, ConsoleSetFontError,
|
||||
font::BitmapFont,
|
||||
mode::{ConsoleMode, KeyboardMode},
|
||||
};
|
||||
use aster_framebuffer::{
|
||||
ConsoleState, DummyFramebufferConsole, EscapeFsm, FRAMEBUFFER, FrameBuffer,
|
||||
};
|
||||
use int_to_c_enum::TryFromInt;
|
||||
use ostd::sync::{LocalIrqDisabled, SpinLock};
|
||||
|
||||
use crate::{
|
||||
device::tty::vt::keyboard::VtKeyboard,
|
||||
process::{
|
||||
Pid,
|
||||
signal::{sig_num::SigNum, signals::kernel::KernelSignal},
|
||||
},
|
||||
};
|
||||
|
||||
/// The VT mode type.
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromInt)]
|
||||
pub(in crate::device::tty::vt) enum VtModeType {
|
||||
/// Kernel-controlled switching.
|
||||
///
|
||||
/// In this mode, the VT automatically switches without notifying any process.
|
||||
Auto = 0,
|
||||
/// Process-controlled switching.
|
||||
///
|
||||
/// In this mode, a process is notified when the VT is switched away from or to it. The process
|
||||
/// can then release or acquire the VT as needed.
|
||||
Process = 1,
|
||||
}
|
||||
|
||||
/// The VT mode configuration.
|
||||
///
|
||||
/// The meaning mirrors Linux `struct vt_mode`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(in crate::device::tty::vt) struct VtMode {
|
||||
pub mode_type: VtModeType,
|
||||
pub wait_on_inactive: bool,
|
||||
pub release_signal: Option<SigNum>,
|
||||
pub acquire_signal: Option<SigNum>,
|
||||
}
|
||||
|
||||
impl Default for VtMode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode_type: VtModeType::Auto,
|
||||
wait_on_inactive: false,
|
||||
release_signal: None,
|
||||
acquire_signal: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The virtual terminal console device.
|
||||
pub(in crate::device::tty::vt) struct VtConsole {
|
||||
keyboard: VtKeyboard,
|
||||
mode: SpinLock<VtMode>,
|
||||
pid: SpinLock<Option<Pid>>,
|
||||
inner: SpinLock<(ConsoleState, EscapeFsm), LocalIrqDisabled>,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for VtConsole {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("VtConsole").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl VtConsole {
|
||||
/// Create a new VT console backed by a real framebuffer.
|
||||
fn new(framebuffer: Arc<FrameBuffer>) -> Self {
|
||||
let state = ConsoleState::new(framebuffer);
|
||||
let esc_fsm = EscapeFsm::new();
|
||||
Self {
|
||||
keyboard: VtKeyboard::default(),
|
||||
mode: SpinLock::new(VtMode::default()),
|
||||
pid: SpinLock::new(None),
|
||||
inner: SpinLock::new((state, esc_fsm)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark this VT as active.
|
||||
///
|
||||
/// For [VtModeType::Auto] mode, we enable rendering and redraw the full shadow buffer
|
||||
/// so the framebuffer immediately shows the latest text contents for this VT.
|
||||
pub(in crate::device::tty::vt) fn activate(&self) {
|
||||
if self.vt_mode().mode_type == VtModeType::Auto {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.0.enable_rendering();
|
||||
inner.0.flush_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark this VT as inactive.
|
||||
///
|
||||
/// For [VtModeType::Auto] mode, we disable rendering so background VTs keep
|
||||
/// their shadow buffers updated but do not draw to the physical framebuffer.
|
||||
pub(in crate::device::tty::vt) fn deactivate(&self) {
|
||||
if self.vt_mode().mode_type == VtModeType::Auto {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.0.disable_rendering();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the VT keyboard device.
|
||||
pub(in crate::device::tty::vt) fn vt_keyboard(&self) -> &VtKeyboard {
|
||||
&self.keyboard
|
||||
}
|
||||
|
||||
/// Get the VT mode configuration.
|
||||
pub(in crate::device::tty::vt) fn vt_mode(&self) -> VtMode {
|
||||
let guard = self.mode.lock();
|
||||
*guard
|
||||
}
|
||||
|
||||
/// Set the VT mode configuration.
|
||||
pub(in crate::device::tty::vt) fn set_vt_mode(&self, mode: VtMode) {
|
||||
let mut guard = self.mode.lock();
|
||||
*guard = mode;
|
||||
}
|
||||
|
||||
/// Get the controlling process ID.
|
||||
fn pid(&self) -> Option<Pid> {
|
||||
let guard = self.pid.lock();
|
||||
*guard
|
||||
}
|
||||
|
||||
/// Set the controlling process ID.
|
||||
pub(in crate::device::tty::vt) fn set_pid(&self, pid: Option<Pid>) {
|
||||
let mut guard = self.pid.lock();
|
||||
*guard = pid;
|
||||
}
|
||||
|
||||
/// Deliver the configured "release" signal to the controlling process, if any.
|
||||
///
|
||||
/// This is used when switching away from this VT in [VtModeType::Process] mode.
|
||||
pub(in crate::device::tty::vt) fn send_release_signal(&self) {
|
||||
let mode = self.vt_mode();
|
||||
if let (Some(sig), Some(pid)) = (mode.release_signal, self.pid())
|
||||
&& let Some(process) = crate::process::process_table::get_process(pid)
|
||||
{
|
||||
process.enqueue_signal(Box::new(KernelSignal::new(sig)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Deliver the configured "acquire" signal to the controlling process, if any.
|
||||
///
|
||||
/// This is used when switching to this VT in [VtModeType::Process] mode.
|
||||
pub(in crate::device::tty::vt) fn send_acquire_signal(&self) {
|
||||
let mode = self.vt_mode();
|
||||
if let (Some(sig), Some(pid)) = (mode.acquire_signal, self.pid())
|
||||
&& let Some(process) = crate::process::process_table::get_process(pid)
|
||||
{
|
||||
process.enqueue_signal(Box::new(KernelSignal::new(sig)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyConsoleDevice for VtConsole {
|
||||
fn send(&self, buf: &[u8]) {
|
||||
let mut inner = self.inner.lock();
|
||||
let (state, esc_fsm) = &mut *inner;
|
||||
|
||||
for byte in buf {
|
||||
if esc_fsm.eat(*byte, state) {
|
||||
// The character is part of an ANSI escape sequence.
|
||||
continue;
|
||||
}
|
||||
|
||||
if *byte == 0 {
|
||||
// The character is a NUL character.
|
||||
continue;
|
||||
}
|
||||
|
||||
state.send_char(*byte);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_callback(&self, _callback: &'static ConsoleCallback) {}
|
||||
|
||||
fn set_font(&self, font: BitmapFont) -> Result<(), ConsoleSetFontError> {
|
||||
self.inner.lock().0.set_font(font)
|
||||
}
|
||||
|
||||
fn set_mode(&self, mode: ConsoleMode) -> bool {
|
||||
self.inner.lock().0.set_mode(mode);
|
||||
true
|
||||
}
|
||||
|
||||
fn mode(&self) -> Option<ConsoleMode> {
|
||||
Some(self.inner.lock().0.mode())
|
||||
}
|
||||
|
||||
fn set_keyboard_mode(&self, mode: KeyboardMode) -> bool {
|
||||
self.keyboard.set_mode(mode);
|
||||
true
|
||||
}
|
||||
|
||||
fn keyboard_mode(&self) -> Option<KeyboardMode> {
|
||||
Some(self.keyboard.mode())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::device::tty::vt) enum VtConsoleBackend {
|
||||
/// Framebuffer exists and is usable.
|
||||
Real(Arc<VtConsole>),
|
||||
/// No framebuffer device available; or not yet initialized.
|
||||
Dummy(Arc<DummyFramebufferConsole>),
|
||||
}
|
||||
|
||||
impl VtConsoleBackend {
|
||||
fn console(&self) -> &dyn AnyConsoleDevice {
|
||||
match self {
|
||||
VtConsoleBackend::Real(c) => c.as_ref(),
|
||||
VtConsoleBackend::Dummy(c) => c.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The proxy console device for virtual terminal.
|
||||
#[derive(Debug)]
|
||||
pub(in crate::device::tty::vt) struct VtConsoleProxy {
|
||||
backend: SpinLock<VtConsoleBackend, LocalIrqDisabled>,
|
||||
}
|
||||
|
||||
impl VtConsoleProxy {
|
||||
pub(in crate::device::tty::vt) fn new_dummy() -> Self {
|
||||
Self {
|
||||
backend: SpinLock::new(VtConsoleBackend::Dummy(Arc::new(DummyFramebufferConsole))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to upgrade the backend to a real VT console if the framebuffer is available.
|
||||
pub(in crate::device::tty::vt) fn try_upgrade_to_real(&self) {
|
||||
let mut guard = self.backend.lock();
|
||||
if matches!(&*guard, VtConsoleBackend::Real(_)) {
|
||||
return;
|
||||
}
|
||||
if let Some(fb) = FRAMEBUFFER.get() {
|
||||
*guard = VtConsoleBackend::Real(Arc::new(VtConsole::new(fb.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to downgrade the backend to dummy if it's currently real.
|
||||
pub(in crate::device::tty::vt) fn try_downgrade_to_dummy(&self) {
|
||||
let mut guard = self.backend.lock();
|
||||
if matches!(&*guard, VtConsoleBackend::Dummy(_)) {
|
||||
return;
|
||||
}
|
||||
*guard = VtConsoleBackend::Dummy(Arc::new(DummyFramebufferConsole));
|
||||
}
|
||||
|
||||
/// Get a reference to the real VT console if currently in Real backend.
|
||||
pub(in crate::device::tty::vt) fn vt_console(&self) -> Option<Arc<VtConsole>> {
|
||||
let guard = self.backend.lock();
|
||||
match &*guard {
|
||||
VtConsoleBackend::Real(c) => Some(c.clone()),
|
||||
VtConsoleBackend::Dummy(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyConsoleDevice for VtConsoleProxy {
|
||||
fn send(&self, buf: &[u8]) {
|
||||
self.backend.lock().console().send(buf);
|
||||
}
|
||||
|
||||
fn register_callback(&self, callback: &'static ConsoleCallback) {
|
||||
self.backend.lock().console().register_callback(callback);
|
||||
}
|
||||
|
||||
fn set_font(&self, font: BitmapFont) -> Result<(), ConsoleSetFontError> {
|
||||
self.backend.lock().console().set_font(font)
|
||||
}
|
||||
|
||||
fn set_mode(&self, mode: ConsoleMode) -> bool {
|
||||
self.backend.lock().console().set_mode(mode)
|
||||
}
|
||||
|
||||
fn mode(&self) -> Option<ConsoleMode> {
|
||||
self.backend.lock().console().mode()
|
||||
}
|
||||
|
||||
fn set_keyboard_mode(&self, mode: KeyboardMode) -> bool {
|
||||
self.backend.lock().console().set_keyboard_mode(mode)
|
||||
}
|
||||
|
||||
fn keyboard_mode(&self) -> Option<KeyboardMode> {
|
||||
self.backend.lock().console().keyboard_mode()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, format, sync::Arc};
|
||||
|
||||
use aster_console::{
|
||||
AnyConsoleDevice,
|
||||
mode::{ConsoleMode, KeyboardMode},
|
||||
};
|
||||
use ostd::sync::SpinLock;
|
||||
|
||||
use crate::{
|
||||
device::tty::{
|
||||
Tty, TtyDriver,
|
||||
termio::CTermios,
|
||||
vt::{
|
||||
VtIndex,
|
||||
console::{VtConsole, VtConsoleProxy, VtMode},
|
||||
file::VtFile,
|
||||
},
|
||||
},
|
||||
fs::inode_handle::FileIo,
|
||||
prelude::*,
|
||||
util::ioctl::{RawIoctl, dispatch_ioctl},
|
||||
};
|
||||
|
||||
/// The driver for VT (virtual terminal) devices.
|
||||
pub struct VtDriver {
|
||||
/// A stable console proxy.
|
||||
///
|
||||
/// The actual backend can be upgraded from Dummy to Real when the vt is opened and
|
||||
/// the framebuffer is available; and can be downgraded back to Dummy when the vt is closed.
|
||||
console: VtConsoleProxy,
|
||||
/// The number of open file handles to this VT.
|
||||
open_count: SpinLock<usize>,
|
||||
}
|
||||
|
||||
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>> {
|
||||
log::debug!("VtDriver: opening VT{}", tty.index());
|
||||
Ok(Box::new(VtFile::new(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) {}
|
||||
|
||||
fn ioctl(&self, _tty: &Tty<Self>, raw_ioctl: RawIoctl) -> Result<Option<i32>> {
|
||||
use super::{ioctl_defs::*, manager::VIRTUAL_TERMINAL_MANAGER};
|
||||
|
||||
let with_vt_console = |f: &mut dyn FnMut(&VtConsole) -> Result<Option<i32>>| {
|
||||
if let Some(vt_console) = self.vt_console() {
|
||||
f(&vt_console)
|
||||
} else {
|
||||
log::warn!("VtDriver: ioctl called on VT without real console");
|
||||
Ok(None)
|
||||
}
|
||||
};
|
||||
|
||||
let vtm = VIRTUAL_TERMINAL_MANAGER
|
||||
.get()
|
||||
.expect("VirtualTerminalManager is not initialized");
|
||||
|
||||
dispatch_ioctl! {match raw_ioctl {
|
||||
cmd @ GetAvailableVt => {
|
||||
let res = vtm
|
||||
.get_available_vt_index()
|
||||
.map(|index| index.get() as i32)
|
||||
.unwrap_or(-1);
|
||||
|
||||
cmd.write(&res)?
|
||||
}
|
||||
cmd @ GetVtState => {
|
||||
let state = vtm.get_global_vt_state();
|
||||
|
||||
cmd.write(&state)?
|
||||
}
|
||||
cmd @ GetVtMode => {
|
||||
return with_vt_console(&mut |vt_console| {
|
||||
let vt_mode = vt_console.vt_mode();
|
||||
let c_vt_mode: CVtMode = vt_mode.into();
|
||||
cmd.write(&c_vt_mode)?;
|
||||
Ok(Some(0))
|
||||
});
|
||||
}
|
||||
cmd @ SetVtMode => {
|
||||
return with_vt_console(&mut |vt_console| {
|
||||
let c_vt_mode: CVtMode = cmd.read()?;
|
||||
let vt_mode: VtMode = c_vt_mode.try_into()?;
|
||||
|
||||
vt_console.set_vt_mode(vt_mode);
|
||||
vt_console.set_pid(Some(current!().pid()));
|
||||
Ok(Some(0))
|
||||
});
|
||||
}
|
||||
cmd @ ActivateVt => {
|
||||
let vt_index = VtIndex::new(cmd.get() as u8)
|
||||
.ok_or_else(|| Error::with_message(Errno::ENXIO, "invalid VT index"))?;
|
||||
|
||||
vtm.switch_vt(vt_index)?;
|
||||
}
|
||||
cmd @ WaitForVtActive => {
|
||||
let vt_index = VtIndex::new(cmd.get() as u8)
|
||||
.ok_or_else(|| Error::with_message(Errno::ENXIO, "invalid VT index"))?;
|
||||
|
||||
vtm.wait_for_vt_active(vt_index)?;
|
||||
}
|
||||
cmd @ SetGraphicsMode => {
|
||||
return with_vt_console(&mut |vt_console| {
|
||||
let mode = ConsoleMode::try_from(cmd.get())
|
||||
.map_err(|_| Error::with_message(Errno::EINVAL, "invalid console mode"))?;
|
||||
let _ = vt_console.set_mode(mode);
|
||||
Ok(Some(0))
|
||||
});
|
||||
}
|
||||
cmd @ GetGraphicsMode => {
|
||||
return with_vt_console(&mut |vt_console| {
|
||||
let mode = vt_console.mode().unwrap_or(ConsoleMode::Text);
|
||||
cmd.write(&(mode as i32))?;
|
||||
Ok(Some(0))
|
||||
});
|
||||
}
|
||||
cmd @ SetKeyboardMode => {
|
||||
return with_vt_console(&mut |vt_console| {
|
||||
let mode = KeyboardMode::try_from(cmd.get())
|
||||
.map_err(|_| Error::with_message(Errno::EINVAL, "invalid keyboard mode"))?;
|
||||
let _ = vt_console.set_keyboard_mode(mode);
|
||||
Ok(Some(0))
|
||||
});
|
||||
}
|
||||
cmd @ GetKeyboardMode => {
|
||||
return with_vt_console(&mut |vt_console| {
|
||||
let mode = vt_console.keyboard_mode().unwrap_or(KeyboardMode::Xlate);
|
||||
cmd.write(&(mode as i32))?;
|
||||
Ok(Some(0))
|
||||
});
|
||||
}
|
||||
cmd @ ReleaseDisplay => {
|
||||
let release_display_type = ReleaseDisplayType::try_from(cmd.get()).
|
||||
map_err(|_| Error::with_message(Errno::EINVAL, "invalid release display type"))?;
|
||||
vtm.handle_reldisp(release_display_type)?
|
||||
}
|
||||
_ => {
|
||||
log::debug!("VtDriver: unhandled ioctl cmd {:#x}", raw_ioctl.cmd());
|
||||
return Ok(None);
|
||||
}
|
||||
}};
|
||||
|
||||
Ok(Some(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl VtDriver {
|
||||
pub(in crate::device::tty::vt) fn new() -> Self {
|
||||
Self {
|
||||
console: VtConsoleProxy::new_dummy(),
|
||||
open_count: SpinLock::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the real VT console if currently in Real backend.
|
||||
pub(in crate::device::tty::vt) fn vt_console(&self) -> Option<Arc<VtConsole>> {
|
||||
self.console.vt_console()
|
||||
}
|
||||
|
||||
/// Try to upgrade the backend to a real VT console and activate it.
|
||||
pub(in crate::device::tty::vt) fn try_upgrade_to_real_and_activate(&self) {
|
||||
self.console.try_upgrade_to_real();
|
||||
if let Some(vt_console) = self.vt_console() {
|
||||
vt_console.activate();
|
||||
}
|
||||
}
|
||||
|
||||
/// Increments the open count.
|
||||
pub(in crate::device::tty::vt) fn inc_open(&self) {
|
||||
let mut guard = self.open_count.lock();
|
||||
if *guard == 0 && self.vt_console().is_none() {
|
||||
self.console.try_upgrade_to_real();
|
||||
}
|
||||
*guard += 1;
|
||||
}
|
||||
|
||||
/// Decrements the open count.
|
||||
pub(in crate::device::tty::vt) fn dec_open(&self) {
|
||||
let mut guard = self.open_count.lock();
|
||||
if *guard == 0 {
|
||||
log::warn!("VtDriver: dec_open called when open_count is already zero");
|
||||
return;
|
||||
}
|
||||
*guard -= 1;
|
||||
if *guard == 0 {
|
||||
self.console.try_downgrade_to_dummy();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the VT is currently open.
|
||||
pub(in crate::device::tty::vt) fn is_open(&self) -> bool {
|
||||
*self.open_count.lock() > 0
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use inherit_methods_macro::inherit_methods;
|
||||
|
||||
use crate::{
|
||||
device::tty::{Tty, vt::VtDriver},
|
||||
events::IoEvents,
|
||||
fs::{
|
||||
inode_handle::FileIo,
|
||||
utils::{InodeIo, StatusFlags},
|
||||
},
|
||||
prelude::*,
|
||||
process::signal::{PollHandle, Pollable},
|
||||
util::ioctl::RawIoctl,
|
||||
};
|
||||
|
||||
/// The file representation of a virtual terminal (VT) device.
|
||||
pub(in crate::device::tty::vt) struct VtFile(Arc<Tty<VtDriver>>);
|
||||
|
||||
impl VtFile {
|
||||
pub(in crate::device::tty::vt) fn new(tty: Arc<Tty<VtDriver>>) -> Result<VtFile> {
|
||||
tty.driver().inc_open();
|
||||
Ok(VtFile(tty))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VtFile {
|
||||
fn drop(&mut self) {
|
||||
self.0.driver().dec_open();
|
||||
}
|
||||
}
|
||||
|
||||
#[inherit_methods(from = "self.0")]
|
||||
impl Pollable for VtFile {
|
||||
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents;
|
||||
}
|
||||
|
||||
impl InodeIo for VtFile {
|
||||
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 FileIo for VtFile {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::{
|
||||
device::tty::vt::console::{VtMode, VtModeType},
|
||||
prelude::{Errno, Result, TryFromInt},
|
||||
process::signal::sig_num::SigNum,
|
||||
util::ioctl::{InData, OutData, PassByVal, ioc},
|
||||
};
|
||||
|
||||
/// C-compatible representation of `struct vt_stat`.
|
||||
///
|
||||
/// References: <https://elixir.bootlin.com/linux/v6.17/source/include/uapi/linux/vt.h#L34-L38>
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Pod)]
|
||||
pub struct CVtState {
|
||||
/// Currently active VT number (starting from 1).
|
||||
pub active: u16,
|
||||
/// Signal number to be sent on VT switch.
|
||||
pub signal: u16,
|
||||
/// Bitmask representing VT state.
|
||||
pub state: u16,
|
||||
}
|
||||
|
||||
/// C-compatible representation of `struct vt_mode`.
|
||||
///
|
||||
// References: <https://elixir.bootlin.com/linux/v6.17/source/include/uapi/linux/vt.h#L21-L27>
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Pod)]
|
||||
pub struct CVtMode {
|
||||
/// VT mode type.
|
||||
pub mode: u8,
|
||||
/// If non-zero, writes block while this VT is inactive.
|
||||
pub waitv: u8,
|
||||
/// Signal sent when this VT is released.
|
||||
pub relsig: u16,
|
||||
/// Signal sent when this VT is acquired.
|
||||
pub acqsig: u16,
|
||||
/// Unused field. Must be set to 0.
|
||||
pub frsig: u16,
|
||||
}
|
||||
|
||||
impl From<VtMode> for CVtMode {
|
||||
fn from(mode: VtMode) -> Self {
|
||||
CVtMode {
|
||||
mode: mode.mode_type as u8,
|
||||
waitv: mode.wait_on_inactive as u8,
|
||||
relsig: mode.release_signal.map_or(0, |s| s.as_u8() as u16),
|
||||
acqsig: mode.acquire_signal.map_or(0, |s| s.as_u8() as u16),
|
||||
frsig: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<VtMode> for CVtMode {
|
||||
type Error = crate::prelude::Error;
|
||||
|
||||
fn try_into(self) -> Result<VtMode> {
|
||||
let mode_type = VtModeType::try_from(self.mode)
|
||||
.map_err(|_| Self::Error::with_message(Errno::EINVAL, "invalid VT mode type"))?;
|
||||
let wait_on_inactive = self.waitv != 0;
|
||||
let release_signal = SigNum::try_from(self.relsig as u8).map(Some)?;
|
||||
let acquire_signal = SigNum::try_from(self.acqsig as u8).map(Some)?;
|
||||
|
||||
Ok(VtMode {
|
||||
mode_type,
|
||||
wait_on_inactive,
|
||||
release_signal,
|
||||
acquire_signal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Argument values for the `VT_RELDISP` ioctl.
|
||||
///
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.13/source/drivers/tty/vt/vt_ioctl.c#L872-L881>
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromInt)]
|
||||
pub enum ReleaseDisplayType {
|
||||
/// Deny a pending VT release request.
|
||||
DenyRelease = 0,
|
||||
/// Allow a pending VT release request.
|
||||
AllowRelease = 1,
|
||||
/// Acknowledge completion of VT acquisition.
|
||||
AckAcquire = 2,
|
||||
}
|
||||
|
||||
/// Returns the first available (non-opened) VT number.
|
||||
/// If all VTs are in use, returns -1.
|
||||
///
|
||||
/// Valid VT numbers start from 1.
|
||||
pub type GetAvailableVt = ioc!(VT_OPENQRY, 0x5600, OutData<i32>);
|
||||
|
||||
/// Get the VT mode.
|
||||
pub type GetVtMode = ioc!(VT_GETMODE, 0x5601, OutData<CVtMode>);
|
||||
/// Set the VT mode.
|
||||
pub type SetVtMode = ioc!(VT_SETMODE, 0x5602, InData<CVtMode>);
|
||||
|
||||
/// Get the global VT state.
|
||||
///
|
||||
/// Note:
|
||||
/// - VT 0 is always open (alias for active VT).
|
||||
/// - At most 16 VT states can be returned due to ABI constraints.
|
||||
pub type GetVtState = ioc!(VT_GETSTATE, 0x5603, OutData<CVtState>);
|
||||
|
||||
/// Activate the specified VT (VT numbers start from 1).
|
||||
///
|
||||
/// Switching to VT 0 is not allowed.
|
||||
pub type ActivateVt = ioc!(VT_ACTIVATE, 0x5606, InData<i32, PassByVal>);
|
||||
/// Block until the specified VT becomes active.
|
||||
pub type WaitForVtActive = ioc!(VT_WAITACTIVE, 0x5607, InData<i32, PassByVal>);
|
||||
|
||||
/// Get the display mode.
|
||||
pub type GetGraphicsMode = ioc!(KDGETMODE, 0x4B3B, OutData<i32>);
|
||||
/// Set the display mode.
|
||||
pub type SetGraphicsMode = ioc!(KDSETMODE, 0x4B3A, InData<i32, PassByVal>);
|
||||
|
||||
/// Get the keyboard mode.
|
||||
pub type GetKeyboardMode = ioc!(KDGKBMODE, 0x4B44, OutData<i32>);
|
||||
/// Set the keyboard mode.
|
||||
pub type SetKeyboardMode = ioc!(KDSKBMODE, 0x4B45, InData<i32, PassByVal>);
|
||||
|
||||
/// Used in process-controlled VT switching to allow or deny
|
||||
/// VT release, or to acknowledge VT acquisition.
|
||||
pub type ReleaseDisplay = ioc!(VT_RELDISP, 0x5605, InData<i32, PassByVal>);
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
mod console;
|
||||
mod driver;
|
||||
mod file;
|
||||
mod ioctl_defs;
|
||||
mod keyboard;
|
||||
mod manager;
|
||||
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
pub use driver::VtDriver;
|
||||
pub use manager::active_vt;
|
||||
|
||||
use crate::prelude::Result;
|
||||
|
||||
const MAX_CONSOLES: usize = 63;
|
||||
|
||||
/// Virtual terminal index and always in the range `1..=MAX_CONSOLES`.
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct VtIndex(NonZeroU8);
|
||||
|
||||
impl VtIndex {
|
||||
/// Creates a `VtIndex` from a 1-based VT number.
|
||||
///
|
||||
/// Returns `None` if `value == 0` or `value > MAX_CONSOLES`.
|
||||
const fn new(value: u8) -> Option<Self> {
|
||||
if value == 0 || value as usize > MAX_CONSOLES {
|
||||
None
|
||||
} else {
|
||||
Some(VtIndex(NonZeroU8::new(value).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the 1-based VT number.
|
||||
#[inline]
|
||||
const fn get(self) -> u8 {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
/// Converts this VT number to a 0-based index for internal array access.
|
||||
///
|
||||
/// Safe because `VtIndex` is guaranteed to be non-zero.
|
||||
#[inline]
|
||||
const fn to_zero_based(self) -> usize {
|
||||
(self.get() as usize) - 1
|
||||
}
|
||||
|
||||
/// Returns the next VT index, wrapping around to VT1 after reaching VT`MAX_CONSOLES`.
|
||||
fn next_wrap(self) -> Self {
|
||||
let next = if (self.get() as usize) >= MAX_CONSOLES {
|
||||
1
|
||||
} else {
|
||||
self.get() + 1
|
||||
};
|
||||
|
||||
Self(NonZeroU8::new(next).unwrap())
|
||||
}
|
||||
|
||||
/// Returns the previous VT index, wrapping around to VT`MAX_CONSOLES` when at VT1.
|
||||
fn prev_wrap(self) -> Self {
|
||||
let prev = if self.get() == 1 {
|
||||
MAX_CONSOLES as u8
|
||||
} else {
|
||||
self.get() - 1
|
||||
};
|
||||
|
||||
Self(NonZeroU8::new(prev).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn init_in_first_process() -> Result<()> {
|
||||
keyboard::init();
|
||||
manager::init()?;
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in New Issue