basic vga and vt driver
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![deny(clippy::float_arithmetic)]
|
#![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
|
/// Triggers a linker failure if this reaches that phase
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
@@ -14,30 +21,32 @@ fn panic(_info: &PanicInfo) -> ! {
|
|||||||
|
|
||||||
use core::{arch::asm, panic::PanicInfo};
|
use core::{arch::asm, panic::PanicInfo};
|
||||||
|
|
||||||
|
use crate::vga::VgaBuffer;
|
||||||
|
|
||||||
pub mod multiboot2;
|
pub mod multiboot2;
|
||||||
|
pub mod vga;
|
||||||
|
|
||||||
#[used]
|
#[used]
|
||||||
#[unsafe(link_section = ".multiboot2_header")]
|
#[unsafe(link_section = ".multiboot2_header")]
|
||||||
static HEADER: multiboot2::Header = multiboot2::Header::new();
|
static HEADER: multiboot2::Header = multiboot2::Header::new();
|
||||||
|
|
||||||
const VGA_BUFFER: *mut u8 = 0xb8000 as *mut u8;
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
fn start() -> ! {
|
fn start() -> ! {
|
||||||
// notes
|
let mut vga = unsafe { VgaBuffer::<vga::text_modes::VGAText2>::new_uninitialized() };
|
||||||
// 1st nibble bg
|
|
||||||
// 2nd nibble fg
|
vga.write_at(0, 1, b'H');
|
||||||
//
|
vga.write_at(1, 2, b'E');
|
||||||
// 2: green 010
|
vga.write_at(2, 3, b'Y');
|
||||||
// 4: red 100
|
|
||||||
// F: white FFFF
|
let mut vt = vga::vt::Writer::new(vga);
|
||||||
// 1: blue 001
|
vt.put_at(10, 5);
|
||||||
// conclussion: 4bit LRGB where L: Lightness
|
vt.slide();
|
||||||
unsafe {
|
vt.write(b"testinggg");
|
||||||
*VGA_BUFFER.offset(0) = b'O';
|
// loop {
|
||||||
*VGA_BUFFER.offset(2) = b'K';
|
// for x in b'a'..b'z' {
|
||||||
*VGA_BUFFER.offset(1) = 0x2F;
|
// vt.write(&[x]);
|
||||||
*VGA_BUFFER.offset(3) = 0xAF;
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
unsafe { asm!("hlt", "ud2", options(noreturn)) };
|
unsafe { asm!("hlt", "ud2", options(noreturn)) };
|
||||||
}
|
}
|
||||||
|
|||||||
147
members/oxide/src/vga/mod.rs
Normal file
147
members/oxide/src/vga/mod.rs
Normal file
@@ -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<M: TextMode>(PhantomData<M>);
|
||||||
|
|
||||||
|
impl<M: TextMode> VgaBuffer<M> {
|
||||||
|
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::<usize>::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
members/oxide/src/vga/vt.rs
Normal file
83
members/oxide/src/vga/vt.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use crate::vga::{ColorNibble, TextMode, VgaBuffer, VgaColor};
|
||||||
|
|
||||||
|
pub struct Writer<M: TextMode> {
|
||||||
|
vga: VgaBuffer<M>,
|
||||||
|
cur_color: VgaColor,
|
||||||
|
pos: (u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: TextMode> Writer<M>
|
||||||
|
where
|
||||||
|
[[(u8, u8); M::WIDTH as usize]; M::HEIGHT as usize]:,
|
||||||
|
{
|
||||||
|
pub const fn new(vga: VgaBuffer<M>) -> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user