Remove all dependencies on UEFI boot services.
See you later graphics... who knows when I'll see you again.master
parent
538dfea78c
commit
1d536edcf6
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -16,3 +16,9 @@ pub fn breakpoint() {
|
|||
asm!("int3");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn halt() -> ! {
|
||||
use x86_64::instructions::{interrupts, hlt};
|
||||
interrupts::disable();
|
||||
loop { hlt(); }
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
96
src/main.rs
96
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<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(); }
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
pub fn halt() -> ! {
|
||||
use x86_64::instructions::{interrupts, hlt};
|
||||
interrupts::disable();
|
||||
loop { hlt(); }
|
||||
}
|
Loading…
Reference in New Issue