From 14dccef3dc87e1fd61d33e21c96d229f65a7a154 Mon Sep 17 00:00:00 2001 From: wyt8 <2253457010@qq.com> Date: Tue, 10 Feb 2026 17:13:19 +0000 Subject: [PATCH] Add virtual terminal driver and related functions, including console, file representation, and IOCTL definition --- kernel/src/device/tty/vt/console.rs | 298 +++++++++++++++++++++++++ kernel/src/device/tty/vt/driver.rs | 223 ++++++++++++++++++ kernel/src/device/tty/vt/file.rs | 69 ++++++ kernel/src/device/tty/vt/ioctl_defs.rs | 124 ++++++++++ kernel/src/device/tty/vt/mod.rs | 77 +++++++ 5 files changed, 791 insertions(+) create mode 100644 kernel/src/device/tty/vt/console.rs create mode 100644 kernel/src/device/tty/vt/driver.rs create mode 100644 kernel/src/device/tty/vt/file.rs create mode 100644 kernel/src/device/tty/vt/ioctl_defs.rs create mode 100644 kernel/src/device/tty/vt/mod.rs diff --git a/kernel/src/device/tty/vt/console.rs b/kernel/src/device/tty/vt/console.rs new file mode 100644 index 000000000..14bfa21a8 --- /dev/null +++ b/kernel/src/device/tty/vt/console.rs @@ -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, + pub acquire_signal: Option, +} + +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, + pid: SpinLock>, + 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) -> 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 { + let guard = self.pid.lock(); + *guard + } + + /// Set the controlling process ID. + pub(in crate::device::tty::vt) fn set_pid(&self, pid: Option) { + 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 { + 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 { + Some(self.keyboard.mode()) + } +} + +#[derive(Debug)] +pub(in crate::device::tty::vt) enum VtConsoleBackend { + /// Framebuffer exists and is usable. + Real(Arc), + /// No framebuffer device available; or not yet initialized. + Dummy(Arc), +} + +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, +} + +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> { + 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 { + 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 { + self.backend.lock().console().keyboard_mode() + } +} diff --git a/kernel/src/device/tty/vt/driver.rs b/kernel/src/device/tty/vt/driver.rs new file mode 100644 index 000000000..8ac3761df --- /dev/null +++ b/kernel/src/device/tty/vt/driver.rs @@ -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, +} + +impl TtyDriver for VtDriver { + // Reference: . + const DEVICE_MAJOR_ID: u32 = 4; + + fn devtmpfs_path(&self, index: u32) -> Option { + Some(format!("tty{}", index)) + } + + fn open(tty: Arc>) -> Result> { + log::debug!("VtDriver: opening VT{}", tty.index()); + Ok(Box::new(VtFile::new(tty)?)) + } + + fn push_output(&self, chs: &[u8]) -> Result { + 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, raw_ioctl: RawIoctl) -> Result> { + use super::{ioctl_defs::*, manager::VIRTUAL_TERMINAL_MANAGER}; + + let with_vt_console = |f: &mut dyn FnMut(&VtConsole) -> Result>| { + 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> { + 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 + } +} diff --git a/kernel/src/device/tty/vt/file.rs b/kernel/src/device/tty/vt/file.rs new file mode 100644 index 000000000..93c6d856c --- /dev/null +++ b/kernel/src/device/tty/vt/file.rs @@ -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>); + +impl VtFile { + pub(in crate::device::tty::vt) fn new(tty: Arc>) -> Result { + 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 { + self.0.read(writer, status_flags) + } + + fn write_at( + &self, + _offset: usize, + reader: &mut VmReader, + status_flags: StatusFlags, + ) -> Result { + self.0.write(reader, status_flags) + } +} + +#[inherit_methods(from = "self.0")] +impl FileIo for VtFile { + fn ioctl(&self, raw_ioctl: RawIoctl) -> Result; + + fn check_seekable(&self) -> Result<()> { + return_errno_with_message!(Errno::ESPIPE, "the inode is a TTY"); + } + + fn is_offset_aware(&self) -> bool { + false + } +} diff --git a/kernel/src/device/tty/vt/ioctl_defs.rs b/kernel/src/device/tty/vt/ioctl_defs.rs new file mode 100644 index 000000000..22e0e84b3 --- /dev/null +++ b/kernel/src/device/tty/vt/ioctl_defs.rs @@ -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: +#[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: +#[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 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 for CVtMode { + type Error = crate::prelude::Error; + + fn try_into(self) -> Result { + 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: +#[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); + +/// Get the VT mode. +pub type GetVtMode = ioc!(VT_GETMODE, 0x5601, OutData); +/// Set the VT mode. +pub type SetVtMode = ioc!(VT_SETMODE, 0x5602, InData); + +/// 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); + +/// Activate the specified VT (VT numbers start from 1). +/// +/// Switching to VT 0 is not allowed. +pub type ActivateVt = ioc!(VT_ACTIVATE, 0x5606, InData); +/// Block until the specified VT becomes active. +pub type WaitForVtActive = ioc!(VT_WAITACTIVE, 0x5607, InData); + +/// Get the display mode. +pub type GetGraphicsMode = ioc!(KDGETMODE, 0x4B3B, OutData); +/// Set the display mode. +pub type SetGraphicsMode = ioc!(KDSETMODE, 0x4B3A, InData); + +/// Get the keyboard mode. +pub type GetKeyboardMode = ioc!(KDGKBMODE, 0x4B44, OutData); +/// Set the keyboard mode. +pub type SetKeyboardMode = ioc!(KDSKBMODE, 0x4B45, InData); + +/// 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); diff --git a/kernel/src/device/tty/vt/mod.rs b/kernel/src/device/tty/vt/mod.rs new file mode 100644 index 000000000..6fb46d611 --- /dev/null +++ b/kernel/src/device/tty/vt/mod.rs @@ -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 { + 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(()) +}