Add virtual terminal driver and related functions, including console, file representation, and IOCTL definition

This commit is contained in:
wyt8 2026-02-10 17:13:19 +00:00
parent 77b6d82809
commit 14dccef3dc
5 changed files with 791 additions and 0 deletions

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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>);

View File

@ -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(())
}