From aab190baeaae380877d4feedbb50030f63e1366c Mon Sep 17 00:00:00 2001 From: Sainnhe Park Date: Thu, 9 Dec 2021 10:34:40 +0800 Subject: [PATCH] VGA Text Mode --- .cargo/config.toml | 3 + Cargo.lock | 24 +++++++ Cargo.toml | 6 ++ setup.sh | 4 ++ src/main.rs | 16 ++--- src/vga_buffer.rs | 170 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 212 insertions(+), 11 deletions(-) create mode 100644 setup.sh create mode 100644 src/vga_buffer.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 098ed3e..14d634e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,6 @@ target = "x86_64-anos.json" [unstable] build-std-features = ["compiler-builtins-mem"] build-std = ["core", "compiler_builtins"] + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" diff --git a/Cargo.lock b/Cargo.lock index addcc1d..a317d0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,9 @@ name = "anos" version = "0.1.0" dependencies = [ "bootloader", + "lazy_static", + "spin", + "volatile", ] [[package]] @@ -14,3 +17,24 @@ name = "bootloader" version = "0.9.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c452074efc3c0bfb241fb7bc87df04741c7c85e926f6a07c05f8fbd6008240" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[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 b9b47b9..63a09b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,9 @@ panic = "abort" # 禁用 panic 时的栈展开 [dependencies] bootloader = "0.9.8" +volatile = "0.2.6" +spin = "0.5.2" + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..2780b9a --- /dev/null +++ b/setup.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +rustup component list --installed +cargo install bootimage diff --git a/src/main.rs b/src/main.rs index 7529037..7b1a594 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use core::panic::PanicInfo; +mod vga_buffer; + // 定义一个 byte string 类型的静态变量 static HELLO: &[u8] = b"Hello World!"; @@ -11,16 +13,7 @@ static HELLO: &[u8] = b"Hello World!"; // 函数名为 _start 是因为大多数系统默认用这个名字作为入口点名称 // -> ! 代表这是一个发散函数,不允许有任何返回值,因为它不会被任何函数调用,而是将直接被 bootloader 调用 pub extern "C" fn _start() -> ! { - // VGA Buffer 从 0xb8000 开始 - let vga_buffer = 0xb8000 as *mut u8; - - // 迭代 HELLO, 通过裸指针 vga_buffer 来定位待写入的缓冲区地址 - for (i, &byte) in HELLO.iter().enumerate() { - unsafe { - *vga_buffer.offset(i as isize * 2) = byte; // ASCII 码字节 - *vga_buffer.offset(i as isize * 2 + 1) = 0xb; // 颜色字节, 0xb 代表青色 - } - } + println!("Hello World{}", "!"); loop {} } @@ -28,6 +21,7 @@ pub extern "C" fn _start() -> ! { #[panic_handler] // panic! 时将会进行栈展开,执行各种 drop 函数来回收垃圾 // 禁用标准库之后,这些东西就都没有了,我们需要把它重写一遍 -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..2b7518d --- /dev/null +++ b/src/vga_buffer.rs @@ -0,0 +1,170 @@ +// 用 volatile 以防止我们针对内存的操作被优化掉 +// 因为我们是通过对内存地址 0xb8000 写入来操作 VGA Buffer 的 +// 这实际上是通过内存操作的副作用来实现的 +// 但由于我们从未访问过这段内存,所以它可能会在未来的 Rust 编译器中被优化掉 +// 为了告诉编译器,这部分针对内存的操作是我们故意的,就需要用到 volatile crate +use volatile::Volatile; +use core::fmt; +use lazy_static::lazy_static; +use spin::Mutex; + +#[allow(dead_code)] // 允许未使用的代码 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] // 启用 copy 语义 +#[repr(u8)] // 每个 enum variant 都以 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)] // 为了保证 ColorCode 和 u8 有同样的数据布局 +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)] // 使数据结构的布局和 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, // 当前光标所在列 + color_code: ColorCode, // 当前颜色代码 + buffer: &'static mut Buffer, // VGA Buffer 的引用,生命周期是 'static +} + +impl Writer { + pub fn write_byte(&mut self, byte: u8) { + match byte { + // 如果碰到 '\n' 则创建新行 + b'\n' => self.new_line(), + byte => { + // 如果列数超出限定的阈值,则创建新行 + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + let row = BUFFER_HEIGHT -1; + let col = self.column_position; + let color_code = self.color_code; + // 将 ScreenChar 写入到 Buffer 的相应位置 + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + fn new_line(&mut self) { + // 将所有字符上移一行 + 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(BUFFER_HEIGHT -1); + // 将当前列的位置设为 0 + 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); + } + } + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + // Rust 字符串是以 UTF-8 编码的,然而 VGA Buffer 只支持 ASCII字符,所以有可能存在无法打印的字符 + match byte { + // 当在 ASCII 字符范围内时,打印它 + 0x20..=0x7e | b'\n' => self.write_byte(byte), + // 否则打印 ■ + _ => self.write_byte(0xfe), + } + } + } +} + +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, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) } + }); +} + +pub fn print_something() { + use core::fmt::Write; + let mut writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + // VGA Buffer 从 0xb8000 开始 + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }; + writer.write_byte(b'H'); + writer.write_string("ello "); + writer.write_string("Wörld!"); + write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap(); +} + +// 将宏导出到 crate 的根,这样我们就可以用 +// use crate::println +// 来使用它 +#[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)] 来阻止它生成文档 +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +}