From 24cfbc08fde19e9847ca5db920c560ace4d2653f Mon Sep 17 00:00:00 2001 From: javalsai Date: Sat, 21 Feb 2026 02:20:50 +0100 Subject: [PATCH] basic vga and vt driver --- members/oxide/src/main.rs | 43 ++++++---- members/oxide/src/vga/mod.rs | 147 +++++++++++++++++++++++++++++++++++ members/oxide/src/vga/vt.rs | 83 ++++++++++++++++++++ 3 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 members/oxide/src/vga/mod.rs create mode 100644 members/oxide/src/vga/vt.rs diff --git a/members/oxide/src/main.rs b/members/oxide/src/main.rs index 8db8368..057fcf0 100644 --- a/members/oxide/src/main.rs +++ b/members/oxide/src/main.rs @@ -1,6 +1,13 @@ #![no_std] #![no_main] #![deny(clippy::float_arithmetic)] +#![feature( + const_range, + const_trait_impl, + const_convert, + generic_const_exprs, + associated_type_defaults +)] /// Triggers a linker failure if this reaches that phase #[panic_handler] @@ -14,30 +21,32 @@ fn panic(_info: &PanicInfo) -> ! { use core::{arch::asm, panic::PanicInfo}; +use crate::vga::VgaBuffer; + pub mod multiboot2; +pub mod vga; #[used] #[unsafe(link_section = ".multiboot2_header")] static HEADER: multiboot2::Header = multiboot2::Header::new(); -const VGA_BUFFER: *mut u8 = 0xb8000 as *mut u8; - #[unsafe(no_mangle)] fn start() -> ! { - // notes - // 1st nibble bg - // 2nd nibble fg - // - // 2: green 010 - // 4: red 100 - // F: white FFFF - // 1: blue 001 - // conclussion: 4bit LRGB where L: Lightness - unsafe { - *VGA_BUFFER.offset(0) = b'O'; - *VGA_BUFFER.offset(2) = b'K'; - *VGA_BUFFER.offset(1) = 0x2F; - *VGA_BUFFER.offset(3) = 0xAF; - } + let mut vga = unsafe { VgaBuffer::::new_uninitialized() }; + + vga.write_at(0, 1, b'H'); + vga.write_at(1, 2, b'E'); + vga.write_at(2, 3, b'Y'); + + let mut vt = vga::vt::Writer::new(vga); + vt.put_at(10, 5); + vt.slide(); + vt.write(b"testinggg"); + // loop { + // for x in b'a'..b'z' { + // vt.write(&[x]); + // } + // } + unsafe { asm!("hlt", "ud2", options(noreturn)) }; } diff --git a/members/oxide/src/vga/mod.rs b/members/oxide/src/vga/mod.rs new file mode 100644 index 0000000..b19cbb1 --- /dev/null +++ b/members/oxide/src/vga/mod.rs @@ -0,0 +1,147 @@ +//! Only designed to handle VGA modes where the display is composed of (u8_char, u8_color). Where +//! u8_color is VgaColor + +use core::{marker::PhantomData, ops::Deref}; + +pub mod vt; + +/// Only ONE instance must exist at each time, represents ownership over the VGA memory region +pub struct VgaBuffer(PhantomData); + +impl VgaBuffer { + const VGA_ADDR: *mut u8 = 0xb8000 as *mut u8; + + pub fn set_mode() { + todo!() + // bios::video::set_video_mode(M::CODE); + } + + /// # Safety + /// Only an instance mmust exist at a time + pub unsafe fn new() -> Self { + Self::set_mode(); + Self(PhantomData) + } + + /// # Safety + /// Only an instance mmust exist at a time and the vga state is assumed to be this one, it's + /// not set + pub unsafe fn new_uninitialized() -> Self { + Self(PhantomData) + } + + pub fn get_addr(&mut self, col: u16, row: u16) -> *mut u8 { + // TODO: these assertions could be release ones too + debug_assert!((0..(M::WIDTH)).contains(&col)); + debug_assert!((0..(M::HEIGHT)).contains(&row)); + + unsafe { Self::VGA_ADDR.add(2 * Into::::into(col + row * M::WIDTH)) } + } + + pub fn write_at(&mut self, col: u16, row: u16, ch: u8) { + unsafe { + *self.get_addr(col, row).offset(0) = ch; + }; + } + + pub fn color_at(&mut self, col: u16, row: u16, color: VgaColor) { + unsafe { + *self.get_addr(col, row).offset(1) = *color; + }; + } + + pub fn as_cell_array(&mut self) -> &mut [[(u8, u8); M::WIDTH as usize]; M::HEIGHT as usize] + where + [[(u8, u8); M::WIDTH as usize]; M::HEIGHT as usize]:, + { + unsafe { core::mem::transmute(Self::VGA_ADDR) } + } +} + +// --- + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct ColorNibble(u8); + +impl ColorNibble { + pub const BLACK: Self = Self(0b0000); + + pub const RED: Self = Self(0b0100); + pub const GREEN: Self = Self(0b0010); + pub const BLUE: Self = Self(0b0001); + + pub const YELLOW: Self = Self(0b0110); + pub const CYAN: Self = Self(0b0011); + pub const MAGENTA: Self = Self(0b0101); + + pub const GRAY: Self = Self(0b1000); + pub const GRAY2: Self = Self(0b0111); + + pub const LIGHT_RED: Self = Self(0b1100); + pub const LIGHT_GREEN: Self = Self(0b1010); + pub const LIGHT_BLUE: Self = Self(0b1001); + + pub const LIGHT_YELLOW: Self = Self(0b1110); + pub const LIGHT_CYAN: Self = Self(0b1011); + pub const LIGHT_MAGENTA: Self = Self(0b1101); + + pub const WHITE: Self = Self(0b1111); + + pub const fn set(n: u8) -> Self { + assert!((0..=0x0f).contains(&n)); + Self(n) + } +} + +impl const Deref for ColorNibble { + type Target = u8; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// --- + +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct VgaColor(pub u8); + +impl VgaColor { + pub const fn new(fg: ColorNibble, bg: ColorNibble) -> Self { + Self((*bg << 4) | *fg) + } +} + +impl const Deref for VgaColor { + type Target = u8; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// +/// https://en.wikipedia.org/wiki/VGA_text_mode#PC_common_text_modes +pub trait TextMode { + const CODE: u8; + const WIDTH: u16; + const HEIGHT: u16; +} + +pub mod text_modes { + pub struct VGAText1; + + impl super::TextMode for VGAText1 { + const CODE: u8 = 1; // and 0 + const WIDTH: u16 = 40; + const HEIGHT: u16 = 25; + } + + pub struct VGAText2; + + impl super::TextMode for VGAText2 { + const CODE: u8 = 2; // and 3 + const WIDTH: u16 = 80; + const HEIGHT: u16 = 25; + } +} diff --git a/members/oxide/src/vga/vt.rs b/members/oxide/src/vga/vt.rs new file mode 100644 index 0000000..d44de4c --- /dev/null +++ b/members/oxide/src/vga/vt.rs @@ -0,0 +1,83 @@ +use crate::vga::{ColorNibble, TextMode, VgaBuffer, VgaColor}; + +pub struct Writer { + vga: VgaBuffer, + cur_color: VgaColor, + pos: (u16, u16), +} + +impl Writer +where + [[(u8, u8); M::WIDTH as usize]; M::HEIGHT as usize]:, +{ + pub const fn new(vga: VgaBuffer) -> Self { + Self { + vga, + cur_color: VgaColor::new(super::ColorNibble::WHITE, super::ColorNibble::BLACK), + pos: (0, 0), + } + } + + pub fn carry(&mut self) { + self.pos.0 = 0; + if self.pos.1 >= M::HEIGHT { + self.slide(); + } else { + self.pos.1 += 1; + } + } + + pub fn next_cell(&mut self) { + if self.pos.0 >= M::WIDTH { + self.carry(); + } else { + self.pos.0 += 1; + } + } + + pub fn put_at(&mut self, col: u16, row: u16) { + self.pos = (M::WIDTH.min(col), M::HEIGHT.min(row)); + } + + pub fn write(&mut self, data: &[u8]) { + const BAD_CHAR_COL: VgaColor = VgaColor::new(ColorNibble::WHITE, ColorNibble::RED); + + for &b in data { + let (col, row) = self.pos; + + match b { + b'\n' => self.carry(), + b'\r' => self.pos.0 = 0, + // b'\t' => todo!(), + // backspace + 0x08 | 127 => { + if self.pos.0 > 0 { + self.pos.0 -= 1 + } + } + d if d.is_ascii_graphic() || d == b' ' => { + self.vga.write_at(col, row, b); + self.vga.color_at(col, row, self.cur_color); + self.next_cell(); + } + _ => { + self.vga.write_at(col, row, b'?'); + self.vga.color_at(col, row, BAD_CHAR_COL); + self.next_cell(); + } + } + } + } + + pub fn slide(&mut self) { + let rows = self.vga.as_cell_array(); + rows.rotate_left(1); + + if let Some(last) = rows.last_mut() { + for cell in last { + cell.0 = b' '; + cell.1 = *self.cur_color; + } + } + } +}