diff --git a/.cargo/config.toml b/.cargo/config.toml index 657f10e0..c9733414 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,14 @@ [unstable] -build-std = ["core", "compiler_builtins", "alloc"] +# TODO: uncomment once https://github.com/rust-lang/cargo/issues/8687 is resolved +# build-std = ["core", "alloc"] [build] -target = "x86_64-blog_os.json" +# TODO: uncomment once https://github.com/rust-lang/cargo/issues/8687 is resolved +# target = "x86_64-blog_os.json" [target.'cfg(target_os = "none")'] -runner = "bootimage runner" +runner = "cargo run --package disk_image --bin runner --quiet" + +[alias] +xrun = "run --target x86_64-blog_os.json -Zbuild-std=core,alloc" +bootimage = "run --package disk_image --bin disk_image --quiet" diff --git a/Cargo.lock b/Cargo.lock index ead55a76..2ea18574 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" + [[package]] name = "autocfg" version = "1.0.0" @@ -25,6 +31,7 @@ dependencies = [ "bootloader", "conquer-once", "crossbeam-queue", + "font8x8", "futures-util", "lazy_static", "linked_list_allocator", @@ -40,8 +47,15 @@ dependencies = [ [[package]] name = "bootloader" version = "0.9.8" + +[[package]] +name = "bootloader-locator" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad686b9b47363de7d36c05fb6885f17d08d0f2d15b1bc782a101fe3c94a2c7c" +checksum = "aaaa9db3339d32c2622f2e5d0731eb82a468d3439797c9d4fe426744fe2bd551" +dependencies = [ + "json", +] [[package]] name = "cfg-if" @@ -51,9 +65,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "conquer-once" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f7644600a548ecad74e4a918392af1798f7dd045be610be3203b9e129b4f98f" +checksum = "96eb12fb69466716fbae9009d389e6a30830ae8975e170eff2d2cff579f9efa3" dependencies = [ "conquer-util", ] @@ -90,6 +104,22 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "disk_image" +version = "0.1.0" +dependencies = [ + "anyhow", + "bootloader-locator", + "locate-cargo-manifest", + "runner-utils", +] + +[[package]] +name = "font8x8" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44226c40489fb1d602344a1d8f1b544570c3435e396dda1eda7b5ef010d8f1be" + [[package]] name = "futures-core" version = "0.3.4" @@ -113,6 +143,12 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "lazy_static" version = "1.4.0" @@ -122,6 +158,12 @@ dependencies = [ "spin", ] +[[package]] +name = "libc" +version = "0.2.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" + [[package]] name = "linked_list_allocator" version = "0.8.0" @@ -131,6 +173,15 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "locate-cargo-manifest" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db985b63431fe09e8d71f50aeceffcc31e720cb86be8dad2f38d084c5a328466" +dependencies = [ + "json", +] + [[package]] name = "lock_api" version = "0.3.3" @@ -161,12 +212,40 @@ version = "0.1.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +[[package]] +name = "proc-macro2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175c513d55719db99da20232b06cda8bab6b83ec2d04e3283edf0213c37c1a29" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rlibc" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" +[[package]] +name = "runner-utils" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9dc6848b056990cd51e72aa5556bdbea4a96013e8b18635d183c84159c2988f" +dependencies = [ + "thiserror", + "wait-timeout", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -188,6 +267,37 @@ dependencies = [ "lock_api", ] +[[package]] +name = "syn" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963f7d3cc59b59b9325165add223142bbf1df27655d07789f109896d353d8350" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "uart_16550" version = "0.2.7" @@ -199,16 +309,29 @@ dependencies = [ ] [[package]] -name = "volatile" -version = "0.2.6" +name = "unicode-xid" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "volatile" +version = "0.3.0" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] [[package]] name = "x86_64" -version = "0.11.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365de37eb7c6da582cbb510dd0f3f1235d24ff6309a8a96e8a9909cc9bfd608f" +checksum = "7c6e9604d08cf91ba0e5cc2175e8cbbdeeb2b8230b818fd9052604c4c5ffd620" dependencies = [ "bit_field", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 13aa8618..d257a191 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,11 @@ version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2018" +[workspace] +members = [ + "disk_image", +] + [[test]] name = "should_panic" harness = false @@ -13,15 +18,16 @@ name = "stack_overflow" harness = false [dependencies] -bootloader = { version = "0.9.8", features = ["map_physical_memory"]} +bootloader = { path = "../../uefi-test"} rlibc = "1.0.0" -volatile = "0.2.6" spin = "0.5.2" x86_64 = "0.11.0" uart_16550 = "0.2.0" pic8259_simple = "0.2.0" pc-keyboard = "0.5.0" linked_list_allocator = "0.8.0" +volatile = { path= "../../volatile", features = ["unstable"] } +font8x8 = { version = "0.2.5", default-features = false, features = ["unicode"]} [dependencies.lazy_static] version = "1.0" @@ -33,7 +39,7 @@ default-features = false features = ["alloc"] [dependencies.conquer-once] -version = "0.2.0" +version = "0.2.1" default-features = false [dependencies.futures-util] @@ -41,9 +47,5 @@ version = "0.3.4" default-features = false features = ["alloc"] -[package.metadata.bootimage] -test-args = [ - "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", - "-display", "none" -] -test-success-exit-code = 33 # (0x10 << 1) | 1 +[package.metadata.bootloader] +map-physical-memory = true diff --git a/disk_image/Cargo.toml b/disk_image/Cargo.toml new file mode 100644 index 00000000..ea88ec44 --- /dev/null +++ b/disk_image/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "disk_image" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.32" +bootloader-locator = "0.0.4" +runner-utils = "0.0.2" +locate-cargo-manifest = "0.2.0" diff --git a/disk_image/src/bin/runner.rs b/disk_image/src/bin/runner.rs new file mode 100644 index 00000000..b66f2b23 --- /dev/null +++ b/disk_image/src/bin/runner.rs @@ -0,0 +1,57 @@ +use anyhow::anyhow; +use std::{ + path::PathBuf, + process::{Command, ExitStatus}, + time::Duration, +}; + +const RUN_ARGS: &[&str] = &["-d", "int", "--no-reboot", "-s"]; +const TEST_ARGS: &[&str] = &[ + "-device", + "isa-debug-exit,iobase=0xf4,iosize=0x04", + "-serial", + "stdio", + "-display", + "none", + "--no-reboot", +]; +const TEST_TIMEOUT_SECS: u64 = 10; + +fn main() -> anyhow::Result<()> { + let kernel_binary_path = { + let path = PathBuf::from(std::env::args().nth(1).unwrap()); + path.canonicalize()? + }; + + let disk_image = disk_image::create_disk_image(&kernel_binary_path, true)?; + + let mut run_cmd = Command::new("qemu-system-x86_64"); + run_cmd + .arg("-drive") + .arg(format!("format=raw,file={}", disk_image.display())); + + let binary_kind = runner_utils::binary_kind(&kernel_binary_path); + if binary_kind.is_test() { + run_cmd.args(TEST_ARGS); + + let exit_status = run_test_command(run_cmd)?; + match exit_status.code() { + Some(33) => {} // success + other => return Err(anyhow!("Test failed (exit code: {:?})", other)), + } + } else { + run_cmd.args(RUN_ARGS); + + let exit_status = run_cmd.status()?; + if !exit_status.success() { + std::process::exit(exit_status.code().unwrap_or(1)); + } + } + + Ok(()) +} + +fn run_test_command(mut cmd: Command) -> anyhow::Result { + let status = runner_utils::run_with_timeout(&mut cmd, Duration::from_secs(TEST_TIMEOUT_SECS))?; + Ok(status) +} diff --git a/disk_image/src/lib.rs b/disk_image/src/lib.rs new file mode 100644 index 00000000..27fed203 --- /dev/null +++ b/disk_image/src/lib.rs @@ -0,0 +1,45 @@ +use anyhow::anyhow; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; + +pub fn create_disk_image(kernel_binary_path: &Path, bios_only: bool) -> anyhow::Result { + let bootloader_manifest_path = bootloader_locator::locate_bootloader("bootloader")?; + let kernel_manifest_path = locate_cargo_manifest::locate_manifest()?; + + let mut build_cmd = Command::new(env!("CARGO")); + build_cmd.current_dir(bootloader_manifest_path.parent().unwrap()); + build_cmd.arg("builder"); + build_cmd + .arg("--kernel-manifest") + .arg(&kernel_manifest_path); + build_cmd.arg("--kernel-binary").arg(&kernel_binary_path); + build_cmd + .arg("--target-dir") + .arg(kernel_manifest_path.parent().unwrap().join("target")); + build_cmd + .arg("--out-dir") + .arg(kernel_binary_path.parent().unwrap()); + build_cmd.arg("--quiet"); + if bios_only { + build_cmd.arg("--firmware").arg("bios"); + } + + if !build_cmd.status()?.success() { + return Err(anyhow!("build failed")); + } + + let kernel_binary_name = kernel_binary_path.file_name().unwrap().to_str().unwrap(); + let disk_image = kernel_binary_path + .parent() + .unwrap() + .join(format!("bootimage-bios-{}.bin", kernel_binary_name)); + if !disk_image.exists() { + return Err(anyhow!( + "Disk image does not exist at {} after bootloader build", + disk_image.display() + )); + } + Ok(disk_image) +} diff --git a/disk_image/src/main.rs b/disk_image/src/main.rs new file mode 100644 index 00000000..7aa778a3 --- /dev/null +++ b/disk_image/src/main.rs @@ -0,0 +1,40 @@ +use anyhow::anyhow; +use std::process::Command; + +const TARGET_NAME: &str = "x86_64-blog_os"; +const KERNEL_BINARIES: &[&str] = &["blog_os"]; + +fn main() -> anyhow::Result<()> { + // build all binaries + let mut build_cmd = Command::new(env!("CARGO")); + build_cmd.arg("build"); + build_cmd.arg("--release"); + build_cmd.arg("-Zbuild-std=core"); + build_cmd + .arg("--target") + .arg(format!("{}.json", TARGET_NAME)); + if !build_cmd.status()?.success() { + return Err(anyhow!("build failed")); + }; + + let kernel_manifest = locate_cargo_manifest::locate_manifest()?; + let target_dir_root = kernel_manifest.parent().unwrap().join("target"); + let target_dir = target_dir_root.join(TARGET_NAME).join("release"); + + for binary_name in KERNEL_BINARIES { + let binary_path = { + let path = target_dir.join(binary_name); + path.canonicalize()? + }; + + let disk_image = disk_image::create_disk_image(&binary_path, false)?; + + println!( + "Created disk image for binary {} at {}", + binary_name, + disk_image.display() + ); + } + + Ok(()) +} diff --git a/src/framebuffer.rs b/src/framebuffer.rs new file mode 100644 index 00000000..84200c01 --- /dev/null +++ b/src/framebuffer.rs @@ -0,0 +1,172 @@ +use bootloader::boot_info::PixelFormat; +use core::{fmt, slice}; +use font8x8::UnicodeFonts; +use spin::Mutex; +use volatile::Volatile; + +pub static WRITER: Mutex> = Mutex::new(None); + +pub fn init(framebuffer: &'static mut bootloader::boot_info::FrameBuffer) { + let mut writer = Writer { + info: framebuffer.info(), + buffer: Volatile::new(framebuffer.buffer()), + x_pos: 0, + y_pos: 0, + }; + writer.clear(); + + // global writer should not be locked here + let mut global_writer = WRITER.try_lock().unwrap(); + assert!(global_writer.is_none(), "Global writer already initialized"); + *global_writer = Some(writer); +} + +pub struct Writer { + buffer: Volatile<&'static mut [u8]>, + info: bootloader::boot_info::FrameBufferInfo, + x_pos: usize, + y_pos: usize, +} + +impl Writer { + fn newline(&mut self) { + self.y_pos += 8; + self.carriage_return(); + } + + fn carriage_return(&mut self) { + self.x_pos = 0; + } + + /// Erases all text on the screen + pub fn clear(&mut self) { + self.x_pos = 0; + self.y_pos = 0; + self.buffer.fill(0); + } + + fn width(&self) -> usize { + self.info.horizontal_resolution + } + + fn height(&self) -> usize { + self.info.vertical_resolution + } + + fn write_char(&mut self, c: char) { + match c { + '\n' => self.newline(), + '\r' => self.carriage_return(), + c => { + if self.x_pos >= self.width() { + self.newline(); + } + if self.y_pos >= (self.height() - 8) { + self.clear(); + } + let rendered = font8x8::BASIC_FONTS + .get(c) + .expect("character not found in basic font"); + self.write_rendered_char(rendered); + } + } + } + + fn write_rendered_char(&mut self, rendered_char: [u8; 8]) { + for (y, byte) in rendered_char.iter().enumerate() { + for (x, bit) in (0..8).enumerate() { + let on = *byte & (1 << bit) != 0; + self.write_pixel(self.x_pos + x, self.y_pos + y, on); + } + } + self.x_pos += 8; + } + + fn write_pixel(&mut self, x: usize, y: usize, on: bool) { + let pixel_offset = y * self.info.stride + x; + let color = if on { + match self.info.pixel_format { + PixelFormat::RGB => [0x33, 0xff, 0x66, 0], + other => panic!("unknown pixel format {:?}", other), + } + } else { + [0, 0, 0, 0] + }; + let bytes_per_pixel = self.info.bytes_per_pixel; + let byte_offset = pixel_offset * bytes_per_pixel; + self.buffer + .index_mut(byte_offset..(byte_offset + bytes_per_pixel)) + .copy_from_slice(&color[..bytes_per_pixel]); + } + + /// 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 char in s.chars() { + self.write_char(char); + } + } +} + +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_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::framebuffer::_print(format_args!($($arg)*))); +} + +/// Like the `println!` macro in the standard library, but prints to the VGA text buffer. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +/// Prints the given formatted string to the VGA text buffer +/// through the global `WRITER` instance. +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + WRITER.lock().as_mut().unwrap().write_fmt(args).unwrap(); + }); +} + +#[test_case] +fn test_println_simple() { + println!("test_println_simple output"); +} + +#[test_case] +fn test_println_many() { + for _ in 0..200 { + println!("test_println_many output"); + } +} + +#[test_case] +fn test_println_output() { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + let s = "Some test string that fits on a single line"; + interrupts::without_interrupts(|| { + let mut writer = WRITER.lock(); + writeln!(writer, "\n{}", s).expect("writeln failed"); + for (i, c) in s.chars().enumerate() { + let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } + }); +} diff --git a/src/lib.rs b/src/lib.rs index 2f8adfc4..212dbde7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,12 +15,12 @@ extern crate rlibc; use core::panic::PanicInfo; pub mod allocator; +pub mod framebuffer; pub mod gdt; pub mod interrupts; pub mod memory; pub mod serial; pub mod task; -pub mod vga_buffer; pub fn init() { gdt::init(); diff --git a/src/main.rs b/src/main.rs index 4f68111e..b057f743 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,17 +13,19 @@ use core::panic::PanicInfo; entry_point!(kernel_main); -fn kernel_main(boot_info: &'static BootInfo) -> ! { +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { use blog_os::allocator; use blog_os::memory::{self, BootInfoFrameAllocator}; use x86_64::VirtAddr; + blog_os::framebuffer::init(boot_info.framebuffer.as_mut().unwrap()); + println!("Hello World{}", "!"); blog_os::init(); - let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset); + let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset.unwrap()); let mut mapper = unsafe { memory::init(phys_mem_offset) }; - let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; + let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_regions) }; allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); diff --git a/src/memory.rs b/src/memory.rs index c64b72d4..698ca109 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,4 +1,4 @@ -use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; +use bootloader::memory_map::{MemoryRegion, MemoryRegionKind}; use x86_64::{ structures::paging::{ FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB, @@ -64,7 +64,7 @@ unsafe impl FrameAllocator for EmptyFrameAllocator { /// A FrameAllocator that returns usable frames from the bootloader's memory map. pub struct BootInfoFrameAllocator { - memory_map: &'static MemoryMap, + memory_map: &'static [MemoryRegion], next: usize, } @@ -74,7 +74,7 @@ impl BootInfoFrameAllocator { /// This function is unsafe because the caller must guarantee that the passed /// memory map is valid. The main requirement is that all frames that are marked /// as `USABLE` in it are really unused. - pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { + pub unsafe fn init(memory_map: &'static [MemoryRegion]) -> Self { BootInfoFrameAllocator { memory_map, next: 0, @@ -85,9 +85,9 @@ impl BootInfoFrameAllocator { fn usable_frames(&self) -> impl Iterator { // get usable regions from memory map let regions = self.memory_map.iter(); - let usable_regions = regions.filter(|r| r.region_type == MemoryRegionType::Usable); + let usable_regions = regions.filter(|r| r.kind == MemoryRegionKind::Usable); // map each region to its address range - let addr_ranges = usable_regions.map(|r| r.range.start_addr()..r.range.end_addr()); + let addr_ranges = usable_regions.map(|r| r.start..r.end); // transform to an iterator of frame start addresses let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096)); // create `PhysFrame` types from the start addresses diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs deleted file mode 100644 index 8052d20f..00000000 --- a/src/vga_buffer.rs +++ /dev/null @@ -1,204 +0,0 @@ -use core::fmt; -use lazy_static::lazy_static; -use spin::Mutex; -use volatile::Volatile; - -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 = 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)] -#[repr(transparent)] -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. -#[repr(transparent)] -struct Buffer { - chars: [[Volatile; 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, - }); - 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_export] -macro_rules! print { - ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); -} - -/// Like the `println!` macro in the standard library, but prints to the VGA text buffer. -#[macro_export] -macro_rules! println { - () => ($crate::print!("\n")); - ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); -} - -/// Prints the given formatted string to the VGA text buffer -/// through the global `WRITER` instance. -#[doc(hidden)] -pub fn _print(args: fmt::Arguments) { - use core::fmt::Write; - use x86_64::instructions::interrupts; - - interrupts::without_interrupts(|| { - WRITER.lock().write_fmt(args).unwrap(); - }); -} - -#[test_case] -fn test_println_simple() { - println!("test_println_simple output"); -} - -#[test_case] -fn test_println_many() { - for _ in 0..200 { - println!("test_println_many output"); - } -} - -#[test_case] -fn test_println_output() { - use core::fmt::Write; - use x86_64::instructions::interrupts; - - let s = "Some test string that fits on a single line"; - interrupts::without_interrupts(|| { - let mut writer = WRITER.lock(); - writeln!(writer, "\n{}", s).expect("writeln failed"); - for (i, c) in s.chars().enumerate() { - let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); - assert_eq!(char::from(screen_char.ascii_character), c); - } - }); -} diff --git a/tests/heap_allocation.rs b/tests/heap_allocation.rs index bcdeac65..7b5b1be8 100644 --- a/tests/heap_allocation.rs +++ b/tests/heap_allocation.rs @@ -19,9 +19,9 @@ fn main(boot_info: &'static BootInfo) -> ! { use x86_64::VirtAddr; blog_os::init(); - let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset); + let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset.unwrap()); let mut mapper = unsafe { memory::init(phys_mem_offset) }; - let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; + let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_regions) }; allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); test_main();