Remove all dependencies on UEFI boot services.

See you later graphics... who knows when I'll see you again.
master
James T. Martin 2020-07-18 00:50:26 -07:00
parent 538dfea78c
commit 1d536edcf6
Signed by: james
GPG Key ID: 4B7F3DA9351E577C
10 changed files with 106 additions and 241 deletions

View File

@ -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<uefi::prelude::Boot>)
}
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!");
}

View File

@ -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!");
}

View File

@ -16,3 +16,9 @@ pub fn breakpoint() {
asm!("int3");
}
}
pub fn halt() -> ! {
use x86_64::instructions::{interrupts, hlt};
interrupts::disable();
loop { hlt(); }
}

View File

@ -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<uefi::prelude::Boot>) {
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();
}

View File

@ -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 }

View File

@ -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::<GraphicsOutput>()
.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);
}
}
}
}
}

View File

@ -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<SerialTty> = None;
pub static mut STDERR: Option<SerialTty> = 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();
}
}

View File

@ -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<u16> = 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();
}
}

View File

@ -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<Boot>, _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<uefi::table::Runtime>, _mmap: uefi::table::boot::MemoryMapIter) -> ! {
crate::arch::x86_64::breakpoint();
halt()
}
#[entry]
fn efi_main(handle: Handle, st_boot: SystemTable<Boot>) -> 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::<MemoryDescriptor>();
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<uefi::table::Runtime>, _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(); }
}

View File

@ -1,5 +0,0 @@
pub fn halt() -> ! {
use x86_64::instructions::{interrupts, hlt};
interrupts::disable();
loop { hlt(); }
}