From 145d6a4560cc33df96c01938cd8a8b6f3d03aa70 Mon Sep 17 00:00:00 2001 From: Juozas Vainauskas <71255955+JuozasVainauskas@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:38:15 +0200 Subject: [PATCH] Add VGA text mode support and `println!` macro (#3) --- .gitignore | 4 -- Cargo.lock | 40 +++++++++++++ Cargo.toml | 6 ++ src/main.rs | 14 ++--- src/vga_buffer.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 Cargo.lock create mode 100644 src/vga_buffer.rs diff --git a/.gitignore b/.gitignore index 6985cf1..73fab07 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,6 @@ debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7f8879a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,40 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bootloader" +version = "0.9.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e02311b16c9819e7c72866d379cdd3026c3b7b25c1edf161f548f8e887e7ff" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "os" +version = "0.1.0" +dependencies = [ + "bootloader", + "lazy_static", + "spin", + "volatile", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "volatile" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" diff --git a/Cargo.toml b/Cargo.toml index 4ba7dee..3de5301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,9 @@ edition = "2021" [dependencies] bootloader = "0.9.23" +volatile = "0.2.6" +spin = "0.5.2" + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 89ff1ed..eebe12d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,26 +5,20 @@ #![no_main] use core::panic::PanicInfo; -static HELLO: &[u8] = b"Hello World!"; +mod vga_buffer; #[no_mangle] pub extern "C" fn _start() -> ! { // this function is the entry point, since the linker looks for a function // named `_start` by default - let vga_buffer = 0xb8000 as *mut u8; - - for (i, &byte) in HELLO.iter().enumerate() { - unsafe { - *vga_buffer.offset(i as isize * 2) = byte; - *vga_buffer.offset(i as isize * 2 + 1) = 0xb; - } - } + println!("System booted successfully"); loop {} } // This function is called on panic. #[panic_handler] -fn panic(_info: &PanicInfo) -> ! { +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); loop {} } diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs new file mode 100644 index 0000000..cf11ae0 --- /dev/null +++ b/src/vga_buffer.rs @@ -0,0 +1,148 @@ +use volatile::Volatile; +use core::fmt; +use lazy_static::lazy_static; +use spin::Mutex; + +#[allow(dead_code)] +#[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, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +struct ColorCode(u8); + +impl ColorCode { + fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +const BUFFER_HEIGHT: usize = 25; +const BUFFER_WIDTH: usize = 80; + +#[repr(transparent)] +struct Buffer { + chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +pub struct Writer { + column_position: usize, + row_position: usize, + color_code: ColorCode, + buffer: &'static mut Buffer, +} + +impl Writer { + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + // printable ASCII byte or newline + 0x20..=0x7e | b'\n' => self.write_byte(byte), + _ => self.write_byte(0xfe) + } + } + } + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte => { + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + + let row = self.row_position; + let col = self.column_position; + + let color_code = self.color_code; + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + + fn new_line(&mut self) { + if self.row_position < (BUFFER_HEIGHT - 1) { + self.row_position += 1; + } else { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(self.row_position); + } + self.column_position = 0; + } + + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + ascii_character: b' ', + color_code: self.color_code, + }; + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } +} + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +lazy_static! { + pub static ref WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + row_position: 0, + color_code: ColorCode::new(Color::White, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer)}, + }); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +}