diff --git a/Cargo.lock b/Cargo.lock index 40073d8..50a91b4 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 = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + [[package]] name = "bit_field" version = "0.9.0" @@ -23,6 +29,7 @@ name = "bootproof" version = "0.1.0" dependencies = [ "compiler_builtins", + "num-integer", "uefi", "x86_64", ] @@ -40,13 +47,32 @@ source = "git+https://github.com/rust-lang/compiler-builtins#f3846bc05da87b8a71c [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + [[package]] name = "proc-macro2" version = "1.0.18" @@ -87,9 +113,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab1f1403ecbad37d25120161acc3db12066febf3446efcc40b7631d30678505d" +checksum = "08fcd8a8b1c2488ea5adb67f3840584557a7089150c6f209148567397f767e12" dependencies = [ "bitflags", "log", diff --git a/Cargo.toml b/Cargo.toml index d7d68b6..95fbee9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,9 @@ license = "GPL-3.0+" [dependencies] compiler_builtins = { git = "https://github.com/rust-lang/compiler-builtins" } -uefi = "0.4.6" +uefi = "0.4.7" x86_64 = "0.11.1" + +[dependencies.num-integer] +version = "0.1.36" +default-features = false diff --git a/run.sh b/run.sh index 16b631e..9e02123 100755 --- a/run.sh +++ b/run.sh @@ -2,4 +2,4 @@ profile=${1:-"debug"} mkdir -p drive/EFI/Boot cp "target/x86_64-unknown-uefi/$profile/bootproof.efi" drive/EFI/Boot/BootX64.efi -qemu-system-x86_64 -nodefaults -machine "q35,accel=kvm:tcg" -smp 8 -m 128M -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd,readonly=on" -drive "format=raw,file=fat:rw:drive" -serial stdio -display none +qemu-system-x86_64 -nodefaults -cpu host -smp 8 -m 512M -machine "q35,accel=kvm:tcg" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd,readonly=on" -drive "format=raw,file=fat:rw:drive" -display gtk,gl=on -vga virtio -serial stdio diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 0000000..0d9b196 --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,43 @@ +use alloc::alloc::GlobalAlloc; +use core::alloc::Layout; +use uefi::table::boot::{AllocateType, MemoryDescriptor, 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) => { + crate::log!("Allocate {:?}", layout); + st.boot_services().allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, layout.size()) + .expect("Failed to allocate memory!") + .expect("Failed to allocate memory! 2") + as *mut u8 + }, + Allocator::None => panic!("No allocator available!") + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + match self { + Allocator::Uefi(st) => { + crate::log!("Free {:?}", layout); + st.boot_services().free_pages(ptr as u64, layout.size()); + }, + Allocator::None => { + panic!("No allocator available!"); + } + } + } +} + +#[global_allocator] +pub static mut ALLOCATOR: Allocator = Allocator::None; + +#[alloc_error_handler] +fn handle_error(layout: Layout) -> ! { + panic!("Failed to allocate {:?}", layout); +} diff --git a/src/graphics.rs b/src/graphics.rs new file mode 100644 index 0000000..10b165b --- /dev/null +++ b/src/graphics.rs @@ -0,0 +1,85 @@ +mod font; + +use crate::println; +use uefi::proto::console::gop::*; + +fn px_index(stride: usize, x: usize, y: usize) -> usize { + 4 * (y * stride + x) +} + +fn make_px(r: u8, g: u8, b: u8) -> [u8; 4] { + [b, g, r, 0] +} + +unsafe fn draw_char(fb: &mut FrameBuffer, stride: usize, cx: usize, cy: usize, c: char) { + let font = font::font(); + let glyph = font.lookup(c).expect("Character missing from font."); + let color = make_px(255, 255, 255); + for dx in 0..num_integer::div_ceil(glyph.width(), 8) * 8 { + for dy in 0..glyph.height() { + if glyph.get(dx, dy) { + let scale = 2; + for sdx in 0..scale { + for sdy in 0..scale { + let px = cx + scale * dx as usize + sdx; + let py = cy + scale * dy as usize + sdy; + fb.write_value(px_index(stride, px, py), color); + } + } + } + } + } +} + +unsafe fn draw_str(fb: &mut FrameBuffer, stride: usize, mut cx: usize, cy: usize, text: &str) { + let width = font::font().width as usize * 2; + for c in text.chars() { + draw_char(fb, stride, cx, cy, c); + cx += width; + } +} + +fn draw(fb: &mut FrameBuffer, stride: usize, width: usize, height: usize) { + for x in 0..width { + for y in 0..height { + let i = px_index(stride, x, y); + let r = (x * 256 / width) as u8; + let g = (y * 256 / height) as u8; + let b = 255 - ((r as u16 + g as u16) / 2) as u8; + let px = make_px(r, g, b); + unsafe { + fb.write_value(i, px); + } + } + } + + let c_width = width / 8; + let c_height = height / 16; + unsafe { + draw_str(fb, stride, 8, 8, "✔ Hello, world! ♡"); + } +} + +pub fn do_graphics(st: &uefi::prelude::SystemTable) { + let gop = st.boot_services().locate_protocol::() + .unwrap() + .expect("UEFI Graphics Output Protocol (GOP) is not present."); + let mut gop = unsafe { &mut *gop.get() }; + let mut mode = None; + for gop_mode in gop.modes() { + let gop_mode = gop_mode.expect("Warning while accessing GOP mode."); + if let PixelFormat::BGR = gop_mode.info().pixel_format() { + mode = Some(gop_mode); + } else { + println!("Ignoring non-BGR pixel format."); + } + } + let mode = mode.expect("No usable pixel formats found."); + let (width, height) = mode.info().resolution(); + let stride = mode.info().stride(); + println!("Using mode: {}x{} {:?}", width, height, mode.info().pixel_format()); + gop.set_mode(&mode).unwrap().expect("Failed to set UEFI Graphics Output mode."); + let mut fb = gop.frame_buffer(); + + draw(&mut fb, stride, width, height); +} diff --git a/src/graphics/font.rs b/src/graphics/font.rs new file mode 100644 index 0000000..220949b --- /dev/null +++ b/src/graphics/font.rs @@ -0,0 +1,79 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use psf::*; + +mod psf; + +static mut FONT: Option> = None; + +pub fn font() -> Arc { + unsafe { + FONT.clone().unwrap_or_else(|| { + let font = Arc::new(parse_font()); + FONT = Some(font.clone()); + font + }) + } +} + +fn pad(slice: &[u8]) -> [u8; 4] { + if slice.len() == 1 { + [slice[0], 0, 0, 0] + } else if slice.len() == 2 { + [slice[0], slice[1], 0, 0] + } else if slice.len() == 3 { + [slice[0], slice[1], slice[2], 0] + } else if slice.len() == 4 { + [slice[0], slice[1], slice[2], slice[3]] + } else { + crate::panic!("Bad character length {}", slice.len()) + } +} + +fn parse_font() -> PSF { + use core::convert::TryInto; + let font = core::include_bytes!("font/cozette.psf"); + let length = u32::from_le_bytes(font[16..20].try_into().unwrap()); + let charsize = u32::from_le_bytes(font[20..24].try_into().unwrap()); + let height = u32::from_le_bytes(font[24..28].try_into().unwrap()); + let width = u32::from_le_bytes(font[28..32].try_into().unwrap()); + crate::log!("{} {} {}", width, height, charsize); + + let glyphs_size = (length * charsize) as usize; + let mut glyphs = Vec::with_capacity(glyphs_size); + glyphs.extend_from_slice(&font[32..glyphs_size + 32]); + + let mut unicode_map = Vec::new(); + let mut unicode_info = &font[glyphs_size + 32..]; + let mut glyph = 0; + let mut i = 0; + while i < unicode_info.len() { + let mut nc = unicode_info[i]; + + while nc != 0xFE && nc != 0xFF { + let ch_bytes = nc.leading_ones().max(1) as usize; + let st = core::str::from_utf8(&unicode_info[i..i + ch_bytes as usize]).expect("Invalid character"); + let ch = st.chars().next().unwrap(); + unicode_map.push(UnicodeMap { c: ch, i: glyph }); + i += ch_bytes; + nc = unicode_info[i]; + } + + // Ignore multi-codepoint spellings of characters (for now). + while nc != 0xFF { + i += 1; + nc = unicode_info[i]; + } + + i += 1; + glyph += 1; + } + PSF { + width: width, + height: height, + length: length, + charsize: charsize, + glyphs: glyphs, + unicode: unicode_map, + } +} diff --git a/src/graphics/font/cozette-attribution.txt b/src/graphics/font/cozette-attribution.txt new file mode 100644 index 0000000..d01b1f2 --- /dev/null +++ b/src/graphics/font/cozette-attribution.txt @@ -0,0 +1 @@ +Cozette is an MIT-licensed font by Slavfox. Its source is available here: https://github.com/slavfox/Cozette diff --git a/src/graphics/font/cozette.psf b/src/graphics/font/cozette.psf new file mode 100644 index 0000000..8229a06 Binary files /dev/null and b/src/graphics/font/cozette.psf differ diff --git a/src/graphics/font/psf.rs b/src/graphics/font/psf.rs new file mode 100644 index 0000000..4a4de17 --- /dev/null +++ b/src/graphics/font/psf.rs @@ -0,0 +1,60 @@ +use alloc::vec::Vec; + +pub struct UnicodeMap { + pub c: char, + pub i: usize, +} + +pub struct PSF { + pub width: u32, + pub height: u32, + pub length: u32, + pub charsize: u32, + pub glyphs: Vec, + pub unicode: Vec, +} + +pub struct PSFGlyph<'a> { + width: u32, + height: u32, + bitmap: &'a [u8], +} + +impl PSF { + fn index_of(&self, c: char) -> Option { + for entry in &self.unicode { + if entry.c == c { + return Some(entry.i); + } + } + None + } + + fn get_bitmap<'a>(&'a self, index: usize) -> &'a [u8] { + let byte_index = self.charsize as usize * index; + &self.glyphs[byte_index..byte_index + self.charsize as usize] + } + + pub fn lookup<'a>(&'a self, c: char) -> Option> { + self.index_of(c).map(|i| PSFGlyph { + width: self.width, + height: self.height, + bitmap: self.get_bitmap(i) + }) + } +} + +impl PSFGlyph<'_> { + pub fn width(&self) -> u32 { self.width } + + pub fn height(&self) -> u32 { self.height } + + pub fn get(&self, x: u32, y: u32) -> bool { + let line_size = num_integer::div_ceil(self.width, 8); + let char_size = line_size * self.height; + let (line_byte_index, bit_index) = num_integer::div_rem(x, 8); + let mask = 0b10000000 >> bit_index; + let byte = self.bitmap[(y * line_size + line_byte_index) as usize]; + byte & mask > 0 + } +} diff --git a/src/logger.rs b/src/logger.rs index bd4dfa9..18f5866 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,5 +1,3 @@ -use crate::misc::halt; - use core::fmt::Write; pub enum LoggerBackend { @@ -45,7 +43,7 @@ impl Write for Logger<'_> { }; output.write_str(s) }, - _ => { + LoggerBackend::None => { // There's pretty much no way to recover from a missing logger. // What are we supposed to do-- log the error? Ok(()) @@ -61,7 +59,7 @@ macro_rules! log { ($( $arg:expr ),* ) => { unsafe { use core::fmt::Write; - core::writeln!(LOGGER_BACKEND.stderr(), $( $arg ),*).unwrap(); + core::writeln!(crate::logger::LOGGER_BACKEND.stderr(), $( $arg ),*).unwrap(); } } } @@ -71,7 +69,7 @@ macro_rules! print { ($( $arg:expr ),* ) => { unsafe { use core::fmt::Write; - core::write!(LOGGER_BACKEND.stdout(), $( $arg ),*).unwrap(); + core::write!(crate::logger::LOGGER_BACKEND.stdout(), $( $arg ),*).unwrap(); } } } @@ -81,7 +79,7 @@ macro_rules! println { ($( $arg:expr ),* ) => { unsafe { use core::fmt::Write; - core::writeln!(LOGGER_BACKEND.stdout(), $( $arg ),*).unwrap(); + core::writeln!(crate::logger::LOGGER_BACKEND.stdout(), $( $arg ),*).unwrap(); } } } @@ -89,13 +87,15 @@ macro_rules! println { #[macro_export] macro_rules! panic { ($( $arg:expr ),* ) => { - log!($( $arg ),*); - halt(); + { + use crate::misc::halt; + crate::log!($( $arg ),*); + halt() + } } } #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - log!("{}", info); - halt(); + panic!("{}", info); } diff --git a/src/main.rs b/src/main.rs index 088b25c..0074517 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,17 @@ #![no_std] #![no_main] #![feature(abi_efiapi)] +#![feature(alloc)] +#![feature(alloc_error_handler)] #![feature(asm)] +extern crate alloc; +mod allocator; +mod graphics; mod logger; mod misc; +use crate::allocator::{Allocator, ALLOCATOR}; use crate::logger::{LoggerBackend, LOGGER_BACKEND}; use crate::misc::halt; @@ -18,10 +24,26 @@ 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) -> ! { - halt(); + halt() } #[entry] @@ -30,6 +52,7 @@ fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { unsafe { LOGGER_BACKEND = LoggerBackend::UefiStdio(st_boot.unsafe_clone()); + ALLOCATOR = Allocator::Uefi(st_boot.unsafe_clone()); } setup(&st_boot, handle); @@ -55,6 +78,7 @@ fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { // I do not currently have an adequate stdout for post-UEFI, but the UEFI one is now invalid. unsafe { LOGGER_BACKEND = LoggerBackend::None; + ALLOCATOR = Allocator::None; } main(st_runtime, mmap);