Created
May 7, 2018 22:54
-
-
Save Redrield/8d7055b0b2c5e9562e29af6c4cd3cc92 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![feature(lang_items)] // required for defining the panic handler | |
#![no_std] // don't link the Rust standard library | |
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points | |
#![cfg_attr(test, allow(dead_code, unused_macros))] // allow unused code in test mode | |
extern crate spin; | |
extern crate volatile; | |
#[macro_use] | |
extern crate lazy_static; | |
#[cfg(test)] | |
extern crate array_init; | |
#[cfg(test)] | |
extern crate std; | |
#[macro_use] | |
mod vga_buffer; | |
/// This function is the entry point, since the linker looks for a function | |
/// named `_start_` by default. | |
#[cfg(not(test))] | |
#[no_mangle] // don't mangle the name of this function | |
pub extern "C" fn _start() -> ! { | |
println!("Hello World{}", "!"); | |
loop {} | |
} | |
/// This function is called on panic. | |
#[cfg(not(test))] | |
#[lang = "panic_fmt"] | |
#[no_mangle] | |
pub extern "C" fn rust_begin_panic( | |
_msg: core::fmt::Arguments, | |
_file: &'static str, | |
_line: u32, | |
_column: u32, | |
) -> ! { | |
loop {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use core::fmt; | |
use spin::Mutex; | |
use volatile::Volatile; | |
#[cfg(test)] | |
mod test; | |
lazy_static! { | |
/// A global `Writer` instance that can be used for printing to the VGA text buffer. | |
/// | |
/// Used by the `print!` and `println!` macros. | |
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer { | |
column_position: 0, | |
color_code: ColorCode::new(Color::Yellow, Color::Black), | |
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, | |
}); | |
} | |
/// The standard color palette in VGA text mode. | |
#[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, | |
} | |
/// A combination of a foreground and a background color. | |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
struct ColorCode(u8); | |
impl ColorCode { | |
/// Create a new `ColorCode` with the given foreground and background colors. | |
fn new(foreground: Color, background: Color) -> ColorCode { | |
ColorCode((background as u8) << 4 | (foreground as u8)) | |
} | |
} | |
/// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`. | |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
#[repr(C)] | |
struct ScreenChar { | |
ascii_character: u8, | |
color_code: ColorCode, | |
} | |
/// The height of the text buffer (normally 25 lines). | |
const BUFFER_HEIGHT: usize = 25; | |
/// The width of the text buffer (normally 80 columns). | |
const BUFFER_WIDTH: usize = 80; | |
/// A structure representing the VGA text buffer. | |
struct Buffer { | |
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT], | |
} | |
/// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`. | |
/// | |
/// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the | |
/// `core::fmt::Write` trait. | |
pub struct Writer { | |
column_position: usize, | |
color_code: ColorCode, | |
buffer: &'static mut Buffer, | |
} | |
impl Writer { | |
/// Writes an ASCII byte to the buffer. | |
/// | |
/// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. | |
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 = BUFFER_HEIGHT - 1; | |
let col = self.column_position; | |
let color_code = self.color_code; | |
self.buffer.chars[row][col].write(ScreenChar { | |
ascii_character: byte, | |
color_code: color_code, | |
}); | |
self.column_position += 1; | |
} | |
} | |
} | |
/// Writes the given ASCII string to the buffer. | |
/// | |
/// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Does **not** | |
/// support strings with non-ASCII characters, since they can't be printed in the VGA text | |
/// mode. | |
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), | |
// not part of printable ASCII range | |
_ => self.write_byte(0xfe), | |
} | |
} | |
} | |
/// Shifts all lines one line up and clears the last row. | |
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); | |
self.column_position = 0; | |
} | |
/// Clears a row by overwriting it with blank characters. | |
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(()) | |
} | |
} | |
/// Like the `print!` macro in the standard library, but prints to the VGA text buffer. | |
macro_rules! print { | |
($($arg:tt)*) => ($crate::vga_buffer::print(format_args!($($arg)*))); | |
} | |
/// Like the `print!` macro in the standard library, but prints to the VGA text buffer. | |
macro_rules! println { | |
() => (print!("\n")); | |
($fmt:expr) => (print!(concat!($fmt, "\n"))); | |
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); | |
} | |
/// Prints the given formatted string to the VGA text buffer through the global `WRITER` instance. | |
pub fn print(args: fmt::Arguments) { | |
use core::fmt::Write; | |
WRITER.lock().write_fmt(args).unwrap(); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use super::*; | |
fn construct_writer() -> Writer { | |
use std::boxed::Box; | |
let buffer = construct_buffer(); | |
Writer { | |
column_position: 0, | |
color_code: ColorCode::new(Color::Blue, Color::Magenta), | |
buffer: Box::leak(Box::new(buffer)), | |
} | |
} | |
fn construct_buffer() -> Buffer { | |
use array_init::array_init; | |
Buffer { | |
chars: array_init(|_| array_init(|_| Volatile::new(empty_char()))), | |
} | |
} | |
fn empty_char() -> ScreenChar { | |
ScreenChar { | |
ascii_character: b' ', | |
color_code: ColorCode::new(Color::Green, Color::Brown), | |
} | |
} | |
#[test] | |
fn write_byte() { | |
let mut writer = construct_writer(); | |
writer.write_byte(b'X'); | |
writer.write_byte(b'Y'); | |
for (i, row) in writer.buffer.chars.iter().enumerate() { | |
for (j, screen_char) in row.iter().enumerate() { | |
let screen_char = screen_char.read(); | |
if i == BUFFER_HEIGHT - 1 && j == 0 { | |
assert_eq!(screen_char.ascii_character, b'X'); | |
assert_eq!(screen_char.color_code, writer.color_code); | |
} else if i == BUFFER_HEIGHT - 1 && j == 1 { | |
assert_eq!(screen_char.ascii_character, b'Y'); | |
assert_eq!(screen_char.color_code, writer.color_code); | |
} else { | |
assert_eq!(screen_char, empty_char()); | |
} | |
} | |
} | |
} | |
#[test] | |
fn write_formatted() { | |
use core::fmt::Write; | |
let mut writer = construct_writer(); | |
writeln!(&mut writer, "a").unwrap(); | |
writeln!(&mut writer, "b{}", "c").unwrap(); | |
for (i, row) in writer.buffer.chars.iter().enumerate() { | |
for (j, screen_char) in row.iter().enumerate() { | |
let screen_char = screen_char.read(); | |
if i == BUFFER_HEIGHT - 3 && j == 0 { | |
assert_eq!(screen_char.ascii_character, b'a'); | |
assert_eq!(screen_char.color_code, writer.color_code); | |
} else if i == BUFFER_HEIGHT - 2 && j == 0 { | |
assert_eq!(screen_char.ascii_character, b'b'); | |
assert_eq!(screen_char.color_code, writer.color_code); | |
} else if i == BUFFER_HEIGHT - 2 && j == 1 { | |
assert_eq!(screen_char.ascii_character, b'c'); | |
assert_eq!(screen_char.color_code, writer.color_code); | |
} else if i >= BUFFER_HEIGHT - 2 { | |
assert_eq!(screen_char.ascii_character, b' '); | |
assert_eq!(screen_char.color_code, writer.color_code); | |
} else { | |
assert_eq!(screen_char, empty_char()); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment