252 lines
7.1 KiB
Rust
252 lines
7.1 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//! Console and kernel output
|
|
|
|
use core::fmt::{self, Write};
|
|
use crate::sync::Spinlock;
|
|
use crate::error::Result;
|
|
|
|
/// Console writer
|
|
static CONSOLE: Spinlock<Console> = Spinlock::new(Console::new());
|
|
|
|
/// VGA text mode colors
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
pub enum Color {
|
|
Black = 0,
|
|
Blue = 1,
|
|
Green = 2,
|
|
Cyan = 3,
|
|
Red = 4,
|
|
Magenta = 5,
|
|
Brown = 6,
|
|
LightGray = 7,
|
|
DarkGray = 8,
|
|
LightBlue = 9,
|
|
LightGreen = 10,
|
|
LightCyan = 11,
|
|
LightRed = 12,
|
|
Pink = 13,
|
|
Yellow = 14,
|
|
White = 15,
|
|
}
|
|
|
|
/// VGA text mode color code combining foreground and background colors
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(transparent)]
|
|
struct ColorCode(u8);
|
|
|
|
impl ColorCode {
|
|
const fn new(foreground: Color, background: Color) -> ColorCode {
|
|
ColorCode((background as u8) << 4 | (foreground as u8))
|
|
}
|
|
}
|
|
|
|
/// VGA text mode screen character
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
struct ScreenChar {
|
|
ascii_character: u8,
|
|
color_code: ColorCode,
|
|
}
|
|
|
|
/// VGA text mode buffer dimensions
|
|
const BUFFER_HEIGHT: usize = 25;
|
|
const BUFFER_WIDTH: usize = 80;
|
|
|
|
/// VGA text mode buffer structure
|
|
#[repr(transparent)]
|
|
struct Buffer {
|
|
chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
|
}
|
|
|
|
struct Console {
|
|
initialized: bool,
|
|
vga_buffer: Option<&'static mut Buffer>,
|
|
column_position: usize,
|
|
color_code: ColorCode,
|
|
}
|
|
|
|
impl Console {
|
|
const fn new() -> Self {
|
|
Self {
|
|
initialized: false,
|
|
vga_buffer: None,
|
|
column_position: 0,
|
|
color_code: ColorCode::new(Color::Yellow, Color::Black),
|
|
}
|
|
}
|
|
|
|
fn init(&mut self) -> Result<()> {
|
|
// Initialize VGA text mode buffer
|
|
self.vga_buffer = Some(unsafe { &mut *(0xb8000 as *mut Buffer) });
|
|
|
|
// Initialize serial port (COM1)
|
|
self.init_serial();
|
|
|
|
self.clear_screen();
|
|
self.initialized = true;
|
|
Ok(())
|
|
}
|
|
|
|
fn init_serial(&self) {
|
|
unsafe {
|
|
// Disable interrupts
|
|
core::arch::asm!("out dx, al", in("dx") 0x3F9u16, in("al") 0x00u8);
|
|
// Set baud rate divisor
|
|
core::arch::asm!("out dx, al", in("dx") 0x3FBu16, in("al") 0x80u8); // Enable DLAB
|
|
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") 0x03u8); // Divisor low byte (38400 baud)
|
|
core::arch::asm!("out dx, al", in("dx") 0x3F9u16, in("al") 0x00u8); // Divisor high byte
|
|
// Configure line
|
|
core::arch::asm!("out dx, al", in("dx") 0x3FBu16, in("al") 0x03u8); // 8 bits, no parity, one stop bit
|
|
core::arch::asm!("out dx, al", in("dx") 0x3FCu16, in("al") 0xC7u8); // Enable FIFO, clear, 14-byte threshold
|
|
core::arch::asm!("out dx, al", in("dx") 0x3FEu16, in("al") 0x0Bu8); // IRQs enabled, RTS/DSR set
|
|
}
|
|
}
|
|
|
|
fn clear_screen(&mut self) {
|
|
if let Some(ref mut buffer) = self.vga_buffer {
|
|
let blank = ScreenChar {
|
|
ascii_character: b' ',
|
|
color_code: self.color_code,
|
|
};
|
|
|
|
for row in 0..BUFFER_HEIGHT {
|
|
for col in 0..BUFFER_WIDTH {
|
|
unsafe {
|
|
core::ptr::write_volatile(&mut buffer.chars[row][col] as *mut ScreenChar, blank);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self.column_position = 0;
|
|
}
|
|
|
|
fn write_str(&mut self, s: &str) {
|
|
if !self.initialized {
|
|
return;
|
|
}
|
|
|
|
for byte in s.bytes() {
|
|
match byte {
|
|
b'\n' => self.new_line(),
|
|
byte => {
|
|
self.write_byte(byte);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_byte(&mut self, byte: u8) {
|
|
// Write to serial port
|
|
self.write_serial(byte);
|
|
|
|
// Write to VGA buffer
|
|
match byte {
|
|
b'\n' => self.new_line(),
|
|
byte => {
|
|
if self.column_position >= BUFFER_WIDTH {
|
|
self.new_line();
|
|
}
|
|
|
|
if let Some(ref mut buffer) = self.vga_buffer {
|
|
let row = BUFFER_HEIGHT - 1;
|
|
let col = self.column_position;
|
|
let color_code = self.color_code;
|
|
|
|
unsafe {
|
|
core::ptr::write_volatile(&mut buffer.chars[row][col] as *mut ScreenChar, ScreenChar {
|
|
ascii_character: byte,
|
|
color_code,
|
|
});
|
|
}
|
|
}
|
|
self.column_position += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_serial(&self, byte: u8) {
|
|
unsafe {
|
|
// Wait for transmit holding register to be empty
|
|
loop {
|
|
let mut status: u8;
|
|
core::arch::asm!("in al, dx", out("al") status, in("dx") 0x3FDu16);
|
|
if (status & 0x20) != 0 {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Write byte to serial port
|
|
core::arch::asm!(
|
|
"out dx, al",
|
|
in("dx") 0x3F8u16,
|
|
in("al") byte,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn new_line(&mut self) {
|
|
if let Some(ref mut buffer) = self.vga_buffer {
|
|
// Scroll up
|
|
for row in 1..BUFFER_HEIGHT {
|
|
for col in 0..BUFFER_WIDTH {
|
|
unsafe {
|
|
let character = core::ptr::read_volatile(&buffer.chars[row][col] as *const ScreenChar);
|
|
core::ptr::write_volatile(&mut buffer.chars[row - 1][col] as *mut ScreenChar, character);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear bottom row
|
|
let blank = ScreenChar {
|
|
ascii_character: b' ',
|
|
color_code: self.color_code,
|
|
};
|
|
for col in 0..BUFFER_WIDTH {
|
|
unsafe {
|
|
core::ptr::write_volatile(&mut buffer.chars[BUFFER_HEIGHT - 1][col] as *mut ScreenChar, blank);
|
|
}
|
|
}
|
|
}
|
|
self.column_position = 0;
|
|
}
|
|
}
|
|
|
|
/// Initialize console
|
|
pub fn init() -> Result<()> {
|
|
let mut console = CONSOLE.lock();
|
|
console.init()
|
|
}
|
|
|
|
/// Print function for kernel output
|
|
pub fn _print(args: fmt::Arguments) {
|
|
let mut console = CONSOLE.lock();
|
|
let mut writer = ConsoleWriter(&mut *console);
|
|
writer.write_fmt(args).unwrap();
|
|
}
|
|
|
|
/// Print function for kernel messages with prefix
|
|
pub fn _kprint(args: fmt::Arguments) {
|
|
let mut console = CONSOLE.lock();
|
|
let mut writer = ConsoleWriter(&mut *console);
|
|
writer.write_fmt(args).unwrap();
|
|
}
|
|
|
|
/// Print informational message
|
|
pub fn print_info(message: &str) {
|
|
let mut console = CONSOLE.lock();
|
|
let mut writer = ConsoleWriter(&mut *console);
|
|
writer.write_str("[INFO] ").unwrap();
|
|
writer.write_str(message).unwrap();
|
|
}
|
|
|
|
struct ConsoleWriter<'a>(&'a mut Console);
|
|
|
|
impl Write for ConsoleWriter<'_> {
|
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
self.0.write_str(s);
|
|
Ok(())
|
|
}
|
|
}
|