bootproof/src/main.rs

124 lines
5.1 KiB
Rust
Raw Normal View History

2020-07-12 22:55:13 -07:00
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
#![feature(alloc_error_handler)]
#![feature(asm)]
2020-07-17 16:51:24 -07:00
#![feature(abi_x86_interrupt)]
#![feature(generic_associated_types)]
extern crate alloc;
2020-07-12 22:55:13 -07:00
mod arch;
mod driver;
mod graphics;
mod memory;
mod logger;
use alloc::vec::Vec;
2020-07-12 22:55:13 -07:00
use uefi::prelude::*;
2020-07-12 22:55:13 -07:00
#[entry]
fn efi_main(handle: Handle, st_boot: SystemTable<Boot>) -> Status {
use crate::memory::allocator::{ALLOCATOR, GlobalAllocator};
2020-07-12 22:55:13 -07:00
unsafe {
// Generally speaking, we want to depend on UEFI as little as possible,
// so the need for a UEFI allocator may seem a bit strange.
// However, there's this awkward time during booting when we need
// to allocate space for our "real" allocator's data structures and the UEFI memory maps,
// the result being that we need an allocator for our allocator.
// In theory there are probably ways to get around it, but why bother?
// Just taking advantage of the UEFI allocator briefly is a lot easier.
// (This also lets us use `println!` prior to our main allocator being set up.)
use crate::memory::allocator::uefi::UefiAllocator;
// ABSOLUTELY DO NOT FORGET TO DISABLE THIS AFTER LEAVING UEFI BOOT SERVICES.
// ALL ALLOCATIONS MUST BE STATIC OR BE FREED BEFORE BOOT SERVICES EXITS.
// If the're not, Rust still try to free UEFI-allocated data using the new allocator,
// which is undefined behavior.
ALLOCATOR = GlobalAllocator::Uefi(UefiAllocator::new(st_boot.unsafe_clone()));
use crate::driver::tty::serial::{COM1_PORT, SerialTty};
logger::set_tty(SerialTty::new(COM1_PORT));
logger::init().unwrap();
2020-07-12 22:55:13 -07:00
}
// 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.
// 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.
// We can't let the memory map be de-allocated because it is allocated using the UEFI allocator,
// but would end up being freed using the standard allocator, which is undefined behavior.
// We must provide a buffer (mmap_buf) for UEFI to write the memory map to.
let mut mmap_buf = Vec::new();
let (_mmap, st) = {
let bs = st_boot.boot_services();
// More allocations can happen between the allocation of the buffer and the buffer being filled,
// so add space for 32 more memory descriptors (an arbitrary number) just to make sure there's enough.
mmap_buf.resize(bs.memory_map_size() + 1024, 0);
// HACK: I hate having to use the UEFI allocator just to set up another allocator!
// There's got to be a better way.
use crate::memory::allocator::standard::StandardAllocator;
let mut allocator;
{
let mut mmap = bs.memory_map(mmap_buf.as_mut_slice())
.expect_success("Failed to exit the UEFI boot services.").1;
allocator = StandardAllocator::new(&mut mmap);
}
// Finally, we actually exit the UEFI boot services.
let (st, mut mmap) = st_boot.exit_boot_services(handle, mmap_buf.as_mut_slice())
.expect_success("Failed to exit the UEFI boot services.");
allocator.populate(&mut mmap);
unsafe { ALLOCATOR = GlobalAllocator::Standard(allocator); }
(mmap, st)
};
// Set up the stuff I need to handle interrupts, which is necessary to write drivers for most devices.
2020-07-17 16:51:24 -07:00
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.
2020-07-17 16:51:24 -07:00
gdt::load();
idt::load();
interrupts::enable();
// 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)
}
fn main(_st: SystemTable<uefi::table::Runtime>) -> ! {
// 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(); }
}
#[macro_export]
macro_rules! panic {
($($arg:expr),*) => {{
log::error!($($arg),*);
// FIXME: Panic shouldn't depend on an architecture-specific function.
crate::arch::x86_64::halt()
}}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
panic!("{}", info);
}