From 1d536edcf67eed47248c0021dc44415d285132ad Mon Sep 17 00:00:00 2001 From: James Martin Date: Sat, 18 Jul 2020 00:50:26 -0700 Subject: [PATCH] Remove all dependencies on UEFI boot services. See you later graphics... who knows when I'll see you again. --- src/allocator.rs | 13 ----- src/arch/x86_64/idt.rs | 12 ++--- src/arch/x86_64/mod.rs | 6 +++ src/graphics.rs | 32 ------------- src/graphics/display.rs | 2 - src/graphics/display/gop.rs | 67 -------------------------- src/graphics/tty.rs | 65 +++++++++++++++++++------ src/graphics/tty/uefi.rs | 49 ------------------- src/main.rs | 96 +++++++++++++++++-------------------- src/misc.rs | 5 -- 10 files changed, 106 insertions(+), 241 deletions(-) delete mode 100644 src/graphics/display/gop.rs delete mode 100644 src/graphics/tty/uefi.rs delete mode 100644 src/misc.rs diff --git a/src/allocator.rs b/src/allocator.rs index e5685d4..a94b2c0 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -1,32 +1,19 @@ use alloc::alloc::GlobalAlloc; use core::alloc::Layout; -use uefi::table::boot::{AllocateType, MemoryType}; pub enum Allocator { None, - Uefi(uefi::prelude::SystemTable) } unsafe impl GlobalAlloc for Allocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { match self { - Allocator::Uefi(st) => { - st.boot_services().allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, layout.size()) - .expect("Failed to allocate memory!") - .unwrap() - as *mut u8 - }, Allocator::None => panic!("No allocator available!") } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { match self { - Allocator::Uefi(st) => { - st.boot_services().free_pages(ptr as u64, layout.size()) - .expect("Failed to free memory!") - .unwrap(); - }, Allocator::None => { panic!("No allocator available!"); } diff --git a/src/arch/x86_64/idt.rs b/src/arch/x86_64/idt.rs index 9c614c3..4a9e0b1 100644 --- a/src/arch/x86_64/idt.rs +++ b/src/arch/x86_64/idt.rs @@ -1,18 +1,16 @@ +use crate::graphics::tty::Tty; +use crate::println; use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); pub fn load() { unsafe { + IDT.breakpoint.set_handler_fn(breakpoint_handler); IDT.load(); - IDT.breakpoint.set_handler_fn(breakpoint); } } -extern "x86-interrupt" fn breakpoint(_: &mut InterruptStackFrame) { - use crate::graphics::tty::Tty; - use crate::graphics::tty::serial::SerialTty; - - let mut stdout = unsafe { SerialTty::new(0x3F8) }; - stdout.puts("Breakpoint reached!"); +extern "x86-interrupt" fn breakpoint_handler(_: &mut InterruptStackFrame) { + println!("Breakpoint reached!"); } diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index ae88c44..c0b9484 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -16,3 +16,9 @@ pub fn breakpoint() { asm!("int3"); } } + +pub fn halt() -> ! { + use x86_64::instructions::{interrupts, hlt}; + interrupts::disable(); + loop { hlt(); } +} diff --git a/src/graphics.rs b/src/graphics.rs index 7504cc8..ffea653 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -4,35 +4,3 @@ pub mod font; pub mod terminal; #[macro_use] pub mod tty; - -use crate::graphics::color::{COLOR_BLACK, COLOR_WHITE}; -use crate::graphics::display::gop::GopDisplay; -use crate::graphics::font::psf::PSF; -use crate::graphics::terminal::display::DisplayTerminal; -use crate::graphics::tty::Tty; -use crate::graphics::tty::terminal::TerminalTty; - -pub fn do_graphics(st: &uefi::prelude::SystemTable) { - let font_data = core::include_bytes!("graphics/font/cozette.psf"); - let font = PSF::parse(font_data); - - let mut display = GopDisplay::init(st.boot_services()); - let mut terminal = DisplayTerminal::new(&mut display, &font, COLOR_BLACK, COLOR_WHITE); - let mut tty = TerminalTty::new(&mut terminal); - - for _ in 0..30 { - for c in 'a'..'z' { - tty.putc(c); - tty.putc('\n'); - } - } - - for _ in 0..20 { - for c in 'a'..'z' { - tty.putc(c); - } - } - tty.putc('\n'); - tty.puts("✔ Hello, world! ♡"); - tty.flush(); -} diff --git a/src/graphics/display.rs b/src/graphics/display.rs index 2fc6dfb..cfc4263 100644 --- a/src/graphics/display.rs +++ b/src/graphics/display.rs @@ -1,8 +1,6 @@ use crate::graphics::color::RGB; use crate::graphics::font::Glyph; -pub mod gop; - pub trait Display { fn resolution(&self) -> (usize, usize); fn width(&self) -> usize { self.resolution().0 } diff --git a/src/graphics/display/gop.rs b/src/graphics/display/gop.rs deleted file mode 100644 index eee1b39..0000000 --- a/src/graphics/display/gop.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::println; -use crate::graphics::color::{Color, RGB}; -use crate::graphics::display::Display; -use crate::graphics::tty::Tty; -use uefi::proto::console::gop::{FrameBuffer, GraphicsOutput, ModeInfo, PixelFormat}; - -const PIXEL_WIDTH_BYTES: usize = 4; - -pub struct GopDisplay<'a> { - fb: FrameBuffer<'a>, - mode: ModeInfo, -} - -impl GopDisplay<'_> { - pub fn init<'boot>(bs: &'boot uefi::table::boot::BootServices) -> GopDisplay<'boot> { - let gop = bs.locate_protocol::() - .expect("UEFI Graphics Output Protocol (GOP) is not present.") - .unwrap(); - let gop = unsafe { &mut *gop.get() }; - let mut mode = None; - for gop_mode in gop.modes() { - let gop_mode = gop_mode.unwrap(); - if let PixelFormat::BGR = gop_mode.info().pixel_format() { - mode = Some(gop_mode); - } - } - let mode = mode.expect("No usable pixel formats found."); - let (width, height) = mode.info().resolution(); - println!("Using mode: {}x{} {:?}", width, height, mode.info().pixel_format()); - gop.set_mode(&mode).expect("Failed to set UEFI Graphics Output mode.").unwrap(); - - let info = gop.current_mode_info(); - GopDisplay { - fb: gop.frame_buffer(), - mode: info, - } - } - - // Convert a color to a BGR-formatted byte array. - fn make_pixel(&self, color: impl Color) -> [u8; 4] { - [color.b(), color.g(), color.r(), 0] - } - - fn pixel_index(&self, x: usize, y: usize) -> usize { - PIXEL_WIDTH_BYTES * (self.mode.stride() * y + x) - } -} - -impl Display for GopDisplay<'_> { - fn resolution(&self) -> (usize, usize) { self.mode.resolution() } - - unsafe fn set_pixel(&mut self, color: RGB, x: usize, y: usize) { - self.fb.write_value(self.pixel_index(x, y), self.make_pixel(color)); - } - - fn clear(&mut self, color: RGB) { - let (width, height) = self.resolution(); - let px = self.make_pixel(color); - for x in 0..width { - for y in 0..height { - unsafe { - self.fb.write_value(self.pixel_index(x, y), px); - } - } - } - } -} diff --git a/src/graphics/tty.rs b/src/graphics/tty.rs index 0760dc7..2ce810f 100644 --- a/src/graphics/tty.rs +++ b/src/graphics/tty.rs @@ -1,6 +1,5 @@ pub mod serial; pub mod terminal; -pub mod uefi; use crate::graphics::tty::serial::SerialTty; @@ -14,26 +13,47 @@ pub trait Tty { pub static mut STDOUT: Option = None; pub static mut STDERR: Option = None; +// HACK: These macros are horribly repetitive. There's got to be a better way... + #[macro_export] macro_rules! print { - ($( $arg:expr ),* ) => { + // These additional single-argument cases are necessary because `format!` requires allocation, + // which I don't necessarily *have* (not to mention it's inefficient). + ($s:expr) => { let mut tty; unsafe { tty = crate::graphics::tty::STDOUT.clone().unwrap(); } - tty.puts(&alloc::format!($( $arg ),*)); + tty.puts($s); + tty.flush(); + }; + ($($arg:expr),*) => { + let mut tty; + unsafe { + tty = crate::graphics::tty::STDOUT.clone().unwrap(); + } + tty.puts(&alloc::format!($($arg),*)); tty.flush(); } } #[macro_export] macro_rules! println { - ($( $arg:expr ),* ) => { + ($s:expr) => { let mut tty; unsafe { tty = crate::graphics::tty::STDOUT.clone().unwrap(); } - tty.puts(&alloc::format!($( $arg ),*)); + tty.puts($s); + tty.putc('\n'); + tty.flush(); + }; + ($($arg:expr),*) => { + let mut tty; + unsafe { + tty = crate::graphics::tty::STDOUT.clone().unwrap(); + } + tty.puts(&alloc::format!($($arg),*)); tty.putc('\n'); tty.flush(); } @@ -41,24 +61,41 @@ macro_rules! println { #[macro_export] macro_rules! eprint { - ($( $arg:expr ),* ) => { + ($s:expr) => { let mut tty; unsafe { - tty = crate::graphics::tty::STDOUT.clone().unwrap(); + tty = crate::graphics::tty::STDERR.clone().unwrap(); } - tty.puts(&alloc::format!($( $arg ),*)); + tty.puts($s); + tty.flush(); + }; + ($($arg:expr),*) => { + let mut tty; + unsafe { + tty = crate::graphics::tty::STDERR.clone().unwrap(); + } + tty.puts(&alloc::format!($($arg),*)); tty.flush(); } } #[macro_export] macro_rules! eprintln { - ($( $arg:expr ),* ) => { + ($s:expr) => { let mut tty; unsafe { - tty = crate::graphics::tty::STDOUT.clone().unwrap(); + tty = crate::graphics::tty::STDERR.clone().unwrap(); } - tty.puts(&alloc::format!($( $arg ),*)); + tty.puts($s); + tty.putc('\n'); + tty.flush(); + }; + ($($arg:expr),*) => { + let mut tty; + unsafe { + tty = crate::graphics::tty::STDERR.clone().unwrap(); + } + tty.puts(&alloc::format!($($arg),*)); tty.putc('\n'); tty.flush(); } @@ -66,9 +103,9 @@ macro_rules! eprintln { #[macro_export] macro_rules! panic { - ($( $arg:expr ),* ) => { - crate::eprintln!($( $arg ),*); - crate::misc::halt() + ($($arg:expr),*) => { + crate::eprintln!($($arg),*); + crate::arch::x86_64::halt(); } } diff --git a/src/graphics/tty/uefi.rs b/src/graphics/tty/uefi.rs deleted file mode 100644 index d20d3ff..0000000 --- a/src/graphics/tty/uefi.rs +++ /dev/null @@ -1,49 +0,0 @@ -use alloc::string::String; -use alloc::vec::Vec; -use crate::graphics::tty::Tty; -use uefi::proto::console::text::Output; - -struct UefiTty<'boot> { - // It is impossible to get ownership of an Output, - // so instead we must pass in the entire boot system table. - output: Output<'boot>, - buffer: String, -} - -impl UefiTty<'_> { - pub fn new<'boot>(output: Output<'boot>) -> UefiTty<'boot> { - UefiTty { - output: output, - buffer: String::new(), - } - } -} - -impl Tty for UefiTty<'_> { - fn putc(&mut self, c: char) { - self.buffer.push(c); - } - - fn puts(&mut self, s: &str) { - self.buffer.push_str(s); - } - - fn clear(&mut self) { - // VT100 escape code to reset the terminal: `ESC C`. - self.output.clear().unwrap().unwrap(); - } - - fn flush(&mut self) { - let mut codes: Vec = Vec::new(); - for c in self.buffer.chars() { - codes.push(c as u16); - } - codes.push(0); - - let s = uefi::CStr16::from_u16_with_nul(&codes) - .unwrap_or_else(|_| panic!("Failed to convert to UCS-2: {}", self.buffer)); - self.output.output_string(s).unwrap().unwrap(); - - self.buffer.clear(); - } -} diff --git a/src/main.rs b/src/main.rs index 06530d9..26356da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,13 @@ #![feature(abi_efiapi)] #![feature(alloc_error_handler)] #![feature(asm)] -#![feature(naked_functions)] #![feature(abi_x86_interrupt)] +#![feature(generic_associated_types)] extern crate alloc; mod allocator; #[macro_use] mod graphics; -mod misc; mod arch; use core::mem; @@ -18,81 +17,74 @@ use core::slice; use crate::allocator::{Allocator, ALLOCATOR}; use crate::graphics::tty::{Tty, STDOUT, STDERR}; use crate::graphics::tty::serial::SerialTty; -use crate::misc::halt; use uefi::prelude::*; use uefi::table::boot::{AllocateType, MemoryDescriptor, MemoryType}; -fn setup(st: &SystemTable, _handle: Handle) { - st.stdout().reset(false).expect_success("Failed to reset UEFI stdout."); - - println!("Booting..."); - - for entry in st.config_table() { - use uefi::table::cfg::*; - if entry.guid == ACPI2_GUID { - print!("ACPI2"); - } else if entry.guid == SMBIOS_GUID { - print!("SMBIOS"); - } else if entry.guid == SMBIOS3_GUID { - print!("SMBIOS3"); - } else { - print!("{}", entry.guid); - } - println!(": 0x{:016X}", entry.address as u64); - } - - graphics::do_graphics(st); -} - -fn main(_st: SystemTable, _mmap: uefi::table::boot::MemoryMapIter) -> ! { - crate::arch::x86_64::breakpoint(); - - halt() -} - #[entry] fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { - // Tasks that require the UEFI boot services. - + // Although serial ports don't physically exist on modern devices, + // they're still supposed by emulators (for QEMU you can set `-serial stdio`), + // and they're extremely useful for debugging + // because they don't require any setup and are trivial to use. unsafe { - ALLOCATOR = Allocator::Uefi(st_boot.unsafe_clone()); STDOUT = Some(SerialTty::new(0x3F8)); STDERR = Some(SerialTty::new(0x3F8)); } - setup(&st_boot, handle); + // Our first task is to exit the UEFI boot services. + // UEFI provides a whole bunch of useful device drivers, + // but they're unusable after you exit the boot services, + // and you absolutely *have* to exit boot services to do most OS-related things. - // Exit the UEFI boot services. - - // The memory map buffer must live at least as long as the memory map iterator. - let mmap_buf; - let (st_runtime, mmap) = { + // When we exit boot services, UEFI provides us with a memory map, + // which describes where a bunch of important stuff lies in memory + // (e.g. memory-mapped devices and the ACPI tables), + // and what memory is available for us to use safely. + let (st, mmap) = { + // We must provide a buffer (mmap_buf) for UEFI to write the memory map to. let bs = st_boot.boot_services(); // More allocations can happen between the allocation of the buffer and the buffer being filled, - // so arbitrarily add space for 8 more memory descriptors just to make sure there's enough. + // so add space for 8 more memory descriptors (an arbitrary number) just to make sure there's enough. let mmap_buf_size = bs.memory_map_size() + 8 * mem::size_of::(); - mmap_buf = bs.allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, mmap_buf_size) - .expect_success("Could not allocate space for UEFI memory map."); + + // In the memory map, the OS's own code and data is included as LOADER_CODE and LOADER_DATA. + // This is good to know so we don't accidentally write over it! + // We allocate the mmap_buf as LOADER_DATA for this purpose. + let mmap_buf = bs.allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, mmap_buf_size) + .expect_success("Failed to allocate memory for UEFI memory map buffer."); + + // allocate_pages returns a u64 address, but we need a `&mut [u8]`. let mmap_buf_slice = unsafe { slice::from_raw_parts_mut(mmap_buf as *mut u8, mmap_buf_size) }; + + // Finally, we actually exit the UEFI boot services. st_boot.exit_boot_services(handle, mmap_buf_slice).expect_success("Failed to exit the UEFI boot services.") }; - // Replace the GDT and IDT with my own so I can start handling interrupts. + // Set up the stuff I need to handle interrupts, which is necessary to write drivers for most devices. use x86_64::instructions::interrupts; use crate::arch::x86_64::{gdt, idt}; interrupts::disable(); + // TODO: I've actually found that resetting the GDT isn't necessary in the emulator. + // However, I'm not sure if that's true in general, and at worst it seems harmless, so it stays for now. + // That said, further research is needed. gdt::load(); idt::load(); interrupts::enable(); - // Tasks that do not require the UEFI boot services. - - unsafe { - // TODO: An allocator that works out of UEFI mode. - ALLOCATOR = Allocator::None; - } - - main(st_runtime, mmap) + // Everything up to this point has been setting up the CPU state, drivers, etc. + // Now we begin running actual programs + // (or in this case, since we don't support actual programs yet, whatever debug stuff I want to run). + main(st, mmap) +} + +fn main(_st: SystemTable, _mmap: uefi::table::boot::MemoryMapIter) -> ! { + // Put whatever code you want for debugging/testing purposes here... + arch::x86_64::breakpoint(); + + // There's nothing left for us to do at this point, because there are no meaningful programs to run. + // Instead, we'll just spin forever until the computer is turned off. + // We do *not* disable interrupts to allow for testing the interrupt handlers. + loop { x86_64::instructions::hlt(); } } diff --git a/src/misc.rs b/src/misc.rs deleted file mode 100644 index 33783e0..0000000 --- a/src/misc.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub fn halt() -> ! { - use x86_64::instructions::{interrupts, hlt}; - interrupts::disable(); - loop { hlt(); } -}