Skip to main content

strat9_kernel/vfs/
console_scheme.rs

1use alloc::{string::String, sync::Arc};
2use core::sync::atomic::{AtomicU64, Ordering};
3
4use crate::{sync::SpinLock, syscall::error::SyscallError};
5
6use super::{
7    fd::FileDescriptorTable,
8    file::OpenFile,
9    scheme::{
10        finalize_pseudo_stat, DynScheme, FileFlags, FileStat, OpenFlags, OpenResult, Scheme,
11        DEV_CONSOLE,
12    },
13};
14
15static NEXT_ID: AtomicU64 = AtomicU64::new(1);
16static CONSOLE: SpinLock<Option<Arc<ConsoleScheme>>> = SpinLock::new(None);
17
18/// Kernel console scheme : thin redirect to serial/VGA.
19///
20/// Architecture:
21/// - Writes always go to serial (for debug / early boot).
22/// - Before the display server starts, writes also go to VGA (boot mode).
23/// - Once the display server is active, VGA writes are skipped because
24///   the display server owns the framebuffer. Full redirect-to-display-server
25///   logic will be added when the console silo is integrated.
26///
27/// `VgaWriter` remains boot-only; the display server takes over the
28/// framebuffer at userspace startup.
29pub struct ConsoleScheme;
30
31impl ConsoleScheme {
32    /// Creates a new instance.
33    pub fn new() -> Self {
34        ConsoleScheme
35    }
36}
37
38impl Scheme for ConsoleScheme {
39    /// Performs the open operation.
40    fn open(&self, _path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
41        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
42        Ok(OpenResult {
43            file_id: id,
44            size: None,
45            flags: FileFlags::DEVICE,
46        })
47    }
48
49    /// Performs the read operation.
50    ///
51    /// The console scheme is write-only (output to serial/VGA).
52    /// Keyboard input must come from `/dev/input/kbd` via `InputScheme`.
53    /// Reading from the keyboard buffer here would compete with the userspace
54    /// console (strate-console) and silently steal keystrokes.
55    fn read(&self, _file_id: u64, _offset: u64, _buf: &mut [u8]) -> Result<usize, SyscallError> {
56        Err(SyscallError::Again)
57    }
58
59    /// Performs the write operation.
60    ///
61    /// Boot mode: serial + VGA (when VGA hardware is present).
62    /// Runtime mode: serial only : the display server owns the framebuffer.
63    /// TODO: skip VGA writes once the display server signals readiness.
64    fn write(&self, _file_id: u64, _offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
65        if let Ok(s) = core::str::from_utf8(buf) {
66            // Always write to serial (debug / crash output).
67            crate::serial_print!("{}", s);
68            // Boot-mode VGA output: write to the VGA buffer while the display
69            // server is not yet active. Once the display server starts, this
70            // branch should be gated on a readiness flag (e.g. DISPLAY_SERVER_ACTIVE).
71            if crate::arch::vga::is_available() {
72                crate::vga_print!("{}", s);
73            }
74        } else {
75            for &b in buf {
76                crate::serial_print!("{}", b as char);
77            }
78        }
79        Ok(buf.len())
80    }
81
82    /// Performs the close operation.
83    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
84        Ok(())
85    }
86
87    /// Performs the stat operation.
88    fn stat(&self, _file_id: u64) -> Result<FileStat, SyscallError> {
89        Ok(finalize_pseudo_stat(
90            FileStat {
91                st_ino: 0,
92                st_mode: 0o020666,
93                st_nlink: 1,
94                st_size: 0,
95                st_blksize: 1,
96                st_blocks: 0,
97                ..FileStat::zeroed()
98            },
99            DEV_CONSOLE,
100            1,
101        ))
102    }
103}
104
105/// Initializes console scheme.
106pub fn init_console_scheme() -> Arc<ConsoleScheme> {
107    let scheme = Arc::new(ConsoleScheme::new());
108    *CONSOLE.lock() = Some(scheme.clone());
109    scheme
110}
111
112/// Performs the setup stdio operation.
113pub fn setup_stdio(fd_table: &mut FileDescriptorTable) {
114    let scheme = match CONSOLE.lock().clone() {
115        Some(s) => s as DynScheme,
116        None => return,
117    };
118
119    // fd 0 : stdin (read)
120    let r0 = match scheme.open("console", OpenFlags::READ) {
121        Ok(r) => r,
122        Err(_) => return,
123    };
124    let stdin = Arc::new(OpenFile::new(
125        scheme.clone(),
126        r0.file_id,
127        String::from("/dev/console"),
128        OpenFlags::READ,
129        FileFlags::DEVICE,
130        None,
131    ));
132    fd_table.insert_at(0, stdin);
133
134    // fd 1 : stdout (write)
135    let r1 = match scheme.open("console", OpenFlags::WRITE) {
136        Ok(r) => r,
137        Err(_) => return,
138    };
139    let stdout = Arc::new(OpenFile::new(
140        scheme.clone(),
141        r1.file_id,
142        String::from("/dev/console"),
143        OpenFlags::WRITE,
144        FileFlags::DEVICE,
145        None,
146    ));
147    fd_table.insert_at(1, stdout);
148
149    // fd 2 : stderr (write)
150    let r2 = match scheme.open("console", OpenFlags::WRITE) {
151        Ok(r) => r,
152        Err(_) => return,
153    };
154    let stderr = Arc::new(OpenFile::new(
155        scheme,
156        r2.file_id,
157        String::from("/dev/console"),
158        OpenFlags::WRITE,
159        FileFlags::DEVICE,
160        None,
161    ));
162    fd_table.insert_at(2, stderr);
163}