commit 354012852a0f4c54bb6763b05a4cb17b1e08477f Author: Janis Date: Sun Jan 15 22:04:14 2023 +0100 asdf diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..3df4c8b --- /dev/null +++ b/.cargo/config @@ -0,0 +1,13 @@ +[build] +target = "x86_64-unknown-uefi" + +[unstable] +build-std = ["core", "compiler_builtins", "alloc"] +build-std-features = ["compiler-builtins-mem"] + +[alias] +qemu = "run --target x86_64-unknown-uefi" + +[target.'x86_64-unknown-uefi'] +rustflags = ["-Ctarget-feature=+sse,+mmx,+sse2,-soft-float"] +runner = "./qemu.sh" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7484371 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/esp/EFI/BOOT/BOOTX64.EFI +/Cargo.lock +/target/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5504194 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["bootloader"] \ No newline at end of file diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml new file mode 100644 index 0000000..0f4af9a --- /dev/null +++ b/bootloader/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bootloader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +log = "0.4" +anyhow = {version = "1.0", default-features = false} +thiserror = {path = "../../nostd/thiserror", default-features = false} +itertools = {version = "0.10", default-features = false, features = ["use_alloc"]} + +volatile = "0.4" +bitfield = "0.14" +bytemuck = "1.12.3" + +num-complex = {version = "0.4", default-features = false, features = ["libm"]} +fontdue = {version = "0.7"} +goblin = {version = "0.6", default-features = false, features = ["alloc", "pe32", "pe64"]} + +uefi = { version = "0.18", features = ["alloc", "logger", "exts"] } +uefi-services = "0.15" +raw-cpuid = "10.0" +x86_64 = "0.14" + +ucs2 = {path = "../../nostd/ucs2-rs"} +terminal = {path = "../../nostd/terminal"} +serde_ini = {path = "../../nostd/serde-ini"} +serde = {version = "1.0", default-features = false, features = ["alloc", "derive"]} \ No newline at end of file diff --git a/bootloader/src/context.rs b/bootloader/src/context.rs new file mode 100644 index 0000000..db573a0 --- /dev/null +++ b/bootloader/src/context.rs @@ -0,0 +1,23 @@ +use uefi::{ + prelude::BootServices, proto::loaded_image::LoadedImage, table::boot::ScopedProtocol, Handle, +}; + +use crate::Result; + +pub struct Context<'a> { + handle: Handle, + image: ScopedProtocol<'a, LoadedImage>, + boot_services: &'a BootServices, +} + +impl<'a> Context<'a> { + pub fn new(handle: Handle, boot_services: &'a BootServices) -> Result { + let image = boot_services.open_protocol_exclusive::(handle)?; + + Ok(Self { + handle, + boot_services, + image, + }) + } +} diff --git a/bootloader/src/error.rs b/bootloader/src/error.rs new file mode 100644 index 0000000..9e327c4 --- /dev/null +++ b/bootloader/src/error.rs @@ -0,0 +1,49 @@ +use core::fmt::Display; + +pub type Result = core::result::Result; + +#[allow(dead_code)] +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SSE/SSE2 not available on this CPU")] + NoSSE, + #[error("CpuId failed to retrieve features")] + NoCpuIdFeatures, + #[error("could not find aligned subslice in slice")] + NoAlignedSubslice, + #[error("No Mode found for this GOP")] + NoModeFound, + #[error("{0}")] + FontError(&'static str), + #[error(transparent)] + UefiError(#[from] UefiError), + #[error(transparent)] + LayoutError(#[from] alloc::alloc::LayoutError), + #[error(transparent)] + AllocError(#[from] alloc::alloc::AllocError), + #[error("FromStrWithBufError: {0:?}")] + FromStrWithBufError(uefi::data_types::FromStrWithBufError), +} + +#[derive(Debug, thiserror::Error)] +pub struct UefiError { + inner: uefi::Error, +} + +impl From for Error { + fn from(value: uefi::data_types::FromStrWithBufError) -> Self { + Self::FromStrWithBufError(value) + } +} + +impl From for Error { + fn from(inner: uefi::Error) -> Self { + Self::UefiError(UefiError { inner }) + } +} + +impl Display for UefiError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "UefiStatus: {:?}", self.inner.status()) + } +} diff --git a/bootloader/src/fpu.rs b/bootloader/src/fpu.rs new file mode 100644 index 0000000..52ea341 --- /dev/null +++ b/bootloader/src/fpu.rs @@ -0,0 +1,33 @@ +use core::arch::asm; + +use crate::error::{Error, Result}; +use raw_cpuid::CpuId; +use x86_64::registers::control::{Cr0, Cr0Flags}; + +pub fn enable_fpu() -> Result<()> { + let cpuid = CpuId::new(); + + if let Some(features) = cpuid.get_feature_info() { + if features.has_fpu() && features.has_sse() && features.has_sse2() { + let mut cr0 = Cr0::read(); + + cr0.set(Cr0Flags::MONITOR_COPROCESSOR, true); + cr0.set(Cr0Flags::EXTENSION_TYPE, true); + + cr0.set(Cr0Flags::EMULATE_COPROCESSOR, false); + cr0.set(Cr0Flags::TASK_SWITCHED, false); + + unsafe { + Cr0::write(cr0); + asm!("fninit"); + } + + log::info!("FPU enabled"); + Ok(()) + } else { + Err(Error::NoSSE) + } + } else { + Err(Error::NoCpuIdFeatures) + } +} diff --git a/bootloader/src/fs.rs b/bootloader/src/fs.rs new file mode 100644 index 0000000..69cb8a3 --- /dev/null +++ b/bootloader/src/fs.rs @@ -0,0 +1,255 @@ +use core::{ + alloc::{AllocError, Allocator, Layout}, + borrow::{Borrow, BorrowMut}, + cell::{Ref, RefCell, RefMut}, + mem::MaybeUninit, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec, vec::Vec}; +use log::{debug, info}; +use uefi::{ + data_types::Align, + proto::media::{ + file::{Directory as UefiDirectory, File, FileAttribute, FileHandle, FileInfo, FileMode}, + fs::SimpleFileSystem, + }, + table::boot::ScopedProtocol, + CStr16, ResultExt, Status, +}; + +use crate::error::{Error, Result}; + +pub struct FileSystem<'a> { + inner: RefCell>, +} + +pub struct FileInfoIterator { + dir: Directory, +} + +impl FileInfoIterator { + pub fn new(dir: Directory) -> Self { + Self { dir } + } + + pub fn next_entry(&mut self) -> Result>> { + let layout = Layout::from_size_align(0, FileInfo::alignment())?; + let mut ptr = alloc::alloc::Global.allocate(layout)?; + + if let Some(error) = self.dir.read_entry(unsafe { ptr.as_mut() }).err() { + match error.status() { + Status::BUFFER_TOO_SMALL => { + let layout = Layout::from_size_align( + error.data().expect("no size given after buffer_too_small!"), + FileInfo::alignment(), + )?; + let mut ptr = alloc::alloc::Global.allocate(layout)?; + + let file_info = self + .dir + .read_entry(unsafe { ptr.as_mut() }) + .map(|info| { + info.map(|info| unsafe { Box::::from_raw(info as *mut _) }) + .ok_or_else(|| unsafe { + alloc::alloc::Global.deallocate(ptr.cast(), layout); + AllocError + }) + }) + .discard_errdata()??; + Ok(Some(file_info)) + } + _ => Err(error).discard_errdata()?, + } + } else { + Ok(None) + } + } +} + +pub mod dir { + use core::{ + alloc::{AllocError, Allocator, Layout}, + ops::Deref, + }; + + use alloc::boxed::Box; + use uefi::{ + data_types::Align, + proto::media::file::{File, FileAttribute, FileHandle, FileInfo, FileMode}, + ResultExt, Status, + }; + + use crate::Result; + + use super::Directory; + + pub struct DirectoryEntry { + dir: Directory, + file_info: Box, + } + + impl core::fmt::Debug for DirectoryEntry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("DirectoryEntry") + .field("file_info", &self.file_info) + .finish() + } + } + + impl DirectoryEntry { + pub fn open( + &self, + open_mode: FileMode, + attribs: Option, + ) -> Result { + let handle = self.dir.get_mut().open( + self.file_info.file_name(), + open_mode, + attribs.unwrap_or(FileAttribute::empty()), + )?; + + Ok(handle) + } + } + + impl AsRef for DirectoryEntry { + fn as_ref(&self) -> &FileInfo { + &self.file_info + } + } + + impl Deref for DirectoryEntry { + type Target = FileInfo; + + fn deref(&self) -> &Self::Target { + &self.file_info + } + } + + impl Directory { + pub fn next_entry(&mut self) -> Result>> { + let layout = Layout::from_size_align(0, FileInfo::alignment())?; + let mut ptr = alloc::alloc::Global.allocate(layout)?; + + if let Some(error) = self.read_entry(unsafe { ptr.as_mut() }).err() { + match error.status() { + Status::BUFFER_TOO_SMALL => { + let layout = Layout::from_size_align( + error.data().expect("no size given after buffer_too_small!"), + FileInfo::alignment(), + )?; + let mut ptr = alloc::alloc::Global.allocate(layout)?; + + let file_info = self + .read_entry(unsafe { ptr.as_mut() }) + .map(|info| { + info.map(|info| unsafe { + Box::::from_raw(info as *mut _) + }) + .ok_or_else(|| unsafe { + alloc::alloc::Global.deallocate(ptr.cast(), layout); + AllocError + }) + }) + .discard_errdata()??; + Ok(Some(file_info)) + } + _ => Err(error).discard_errdata()?, + } + } else { + Ok(None) + } + } + } + + impl Iterator for Directory { + type Item = DirectoryEntry; + + fn next(&mut self) -> Option { + self.next_entry() + .ok() + .flatten() + .map(|file_info| DirectoryEntry { + dir: self.clone(), + file_info, + }) + } + } +} + +impl Iterator for FileInfoIterator { + type Item = Box; + + fn next(&mut self) -> Option { + self.next_entry().ok().flatten() + } +} + +#[repr(transparent)] +#[derive(Clone)] +pub struct Directory { + dir: Arc>, +} + +impl Directory { + pub fn get(&self) -> Ref<'_, UefiDirectory> { + RefCell::borrow(&self.dir) + } + + pub fn get_mut<'a>(&'a self) -> RefMut<'a, UefiDirectory> { + RefCell::borrow_mut(&self.dir) + } + + pub fn open( + &self, + filename: &CStr16, + open_mode: FileMode, + attributes: FileAttribute, + ) -> uefi::Result { + self.get_mut().open(filename, open_mode, attributes) + } + + pub fn read_entry<'buf>( + &mut self, + buffer: &'buf mut [u8], + ) -> uefi::Result, Option> { + self.get_mut().read_entry(buffer) + } +} + +impl From for Directory { + fn from(value: UefiDirectory) -> Self { + Self { + dir: Arc::new(RefCell::new(value)), + } + } +} + +impl<'a> FileSystem<'a> { + pub fn new(inner: ScopedProtocol<'a, SimpleFileSystem>) -> Self { + Self { + inner: RefCell::new(inner), + } + } + + pub fn open( + &self, + path: &str, + open_mode: FileMode, + attribs: Option, + ) -> Result { + let mut buf = vec![0; path.len() + 1]; + let cstr = CStr16::from_str_with_buf(path, &mut buf)?; + Ok(self.root_dir()?.get_mut().open( + cstr, + open_mode, + attribs.unwrap_or(FileAttribute::empty()), + )?) + } + + pub fn root_dir(&self) -> Result { + Ok(self.inner.borrow_mut().open_volume()?.into()) + } +} diff --git a/bootloader/src/graphics.rs b/bootloader/src/graphics.rs new file mode 100644 index 0000000..b13b643 --- /dev/null +++ b/bootloader/src/graphics.rs @@ -0,0 +1,96 @@ +use alloc::{borrow::ToOwned, vec::Vec}; +use itertools::Itertools; +use terminal::image::{EncodableLayout, RgbaImage}; +use uefi::{proto::console::gop, table::boot::ScopedProtocol}; + +use crate::{error::Error, Result}; + +pub struct GraphicsOutput<'a, 'b> { + gop: ScopedProtocol<'a, gop::GraphicsOutput<'b>>, + info: gop::ModeInfo, + #[allow(dead_code)] + backbuffer: Vec, +} + +impl<'a, 'b> GraphicsOutput<'a, 'b> { + pub fn from_gop(mut gop: ScopedProtocol<'a, gop::GraphicsOutput<'b>>) -> Result { + let mode = { + let mode = gop + .modes() + .map(|mode| { + let info = mode.info().to_owned(); + (mode, info.resolution().0 * info.resolution().1) + }) + .sorted_by(|a, b| a.1.cmp(&b.1)) + .next(); + + mode.map(|(mode, _)| mode) + } + .ok_or(Error::NoModeFound)?; + + gop.set_mode(&mode)?; + + let info = mode.info(); + //let pixel_width = + + Ok(Self::new(gop, *info)) + } + + pub fn resolution(&self) -> (usize, usize) { + self.info.resolution() + } + + pub fn from_gop_with_mode( + mut gop: ScopedProtocol<'a, gop::GraphicsOutput<'b>>, + mode: gop::Mode, + ) -> Result { + gop.set_mode(&mode)?; + + Ok(Self::new(gop, *mode.info())) + } + + pub fn new(gop: ScopedProtocol<'a, gop::GraphicsOutput<'b>>, info: gop::ModeInfo) -> Self { + let black = gop::BltPixel::new(0, 0, 0); + Self { + gop, + info, + backbuffer: alloc::vec![black; info.stride() * info.resolution().1], + } + } + + fn image_as_bltpixels(image: &RgbaImage) -> &[gop::BltPixel] { + let len = (image.width() * image.height()) as usize; + + unsafe { + core::slice::from_raw_parts(image.as_bytes().as_ptr() as *const gop::BltPixel, len) + } + } + + pub fn blt_image(&mut self, image: &RgbaImage) -> uefi::Result<()> { + let (width, height) = image.dimensions(); + let op = gop::BltOp::BufferToVideo { + buffer: Self::image_as_bltpixels(image), + src: gop::BltRegion::Full, + dest: (0, 0), + dims: (width as usize, height as usize), + }; + self.gop.blt(op)?; + Ok(()) + } + + pub fn blt_image_position( + &mut self, + x: usize, + y: usize, + image: &RgbaImage, + ) -> uefi::Result<()> { + let op = gop::BltOp::BufferToVideo { + buffer: Self::image_as_bltpixels(image), + src: gop::BltRegion::Full, + dest: (x, y), + dims: (image.width() as usize, image.height() as usize), + }; + self.gop.blt(op)?; + Ok(()) + } +} diff --git a/bootloader/src/input.rs b/bootloader/src/input.rs new file mode 100644 index 0000000..ce0441d --- /dev/null +++ b/bootloader/src/input.rs @@ -0,0 +1,51 @@ +use ucs2::decode::traits::DecodeUCS2Char; +use uefi::{ + proto::console::text::{Input as Stdin, Key, ScanCode}, + table::{Boot, SystemTable}, + Char16, Event, +}; + +pub enum CharOrScanCode { + Char(char), + ScanCode(ScanCode), +} + +pub struct Input { + st: SystemTable, + event: [Event; 1], +} + +impl Input { + pub fn new(st: &SystemTable) -> Self { + let (st, event) = unsafe { + let mut st = st.unsafe_clone(); + let event = [st.stdin().wait_for_key_event().unsafe_clone()]; + (st, event) + }; + + Self { st, event } + } + + fn stdin(&mut self) -> &mut Stdin { + self.st.stdin() + } + + fn decode_char16(ch: Char16) -> Option { + DecodeUCS2Char::decode_ucs2(u16::from(ch)).into_char() + } + + pub fn next_key(&mut self) -> Option { + let ref mut events = self.event; + + self.st.boot_services().wait_for_event(events).ok()?; + + if let Some(key) = self.stdin().read_key().ok()? { + match key { + Key::Printable(ch) => Some(CharOrScanCode::Char(Self::decode_char16(ch)?)), + Key::Special(ch) => Some(CharOrScanCode::ScanCode(ch)), + } + } else { + None + } + } +} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs new file mode 100644 index 0000000..f4ce6cf --- /dev/null +++ b/bootloader/src/main.rs @@ -0,0 +1,169 @@ +#![feature(abi_efiapi)] +#![feature(allocator_api)] +#![feature(error_in_core)] +#![feature(alloc_error_handler)] +#![feature(negative_impls)] +#![feature(int_roundings)] +#![feature(core_intrinsics)] +#![feature(iter_intersperse)] +#![no_std] +#![no_main] + +use alloc::{collections::BTreeMap, string::String, vec}; +use graphics::GraphicsOutput; +use log::info; +use serde::Deserialize; +use uefi::{ + prelude::*, + proto::{ + console::gop, + media::file::{File, FileAttribute, FileMode}, + }, + table::{ + boot::{OpenProtocolAttributes, OpenProtocolParams, SearchType}, + Boot, SystemTable, + }, + Identify, Status, +}; + +extern crate alloc; + +mod context; +mod error; +mod fpu; +mod fs; +mod graphics; +mod input; + +use error::{Error, Result}; + +use crate::fpu::enable_fpu; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct Config { + #[serde(rename = "FREELOADER")] + freeloader: FreeLoaderConfig, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct FreeLoaderConfig { + timeout: u64, + #[serde(rename = "DefaultOS")] + default_os: String, + font_path: String, +} + +#[derive(Debug)] +pub struct Context { + handle: Handle, + st: SystemTable, +} + +impl Context { + pub fn new(handle: Handle, mut st: SystemTable) -> uefi::Result { + uefi_services::init(&mut st)?; + Ok(Self { handle, st }) + } + + pub fn fs(&self) -> Result { + let file_system = self.st.boot_services().get_image_file_system(self.handle)?; + + Ok(fs::FileSystem::new(file_system)) + } + + pub fn system_table(&self) -> &SystemTable { + &self.st + } + + pub fn handle(&self) -> Handle { + self.handle + } + + pub fn system_table_mut(&mut self) -> &mut SystemTable { + &mut self.st + } + + pub fn boot_services(&self) -> &BootServices { + self.st.boot_services() + } + + pub fn get_graphics_output(&self) -> Result { + let handles = self + .boot_services() + .locate_handle_buffer(SearchType::ByProtocol(&gop::GraphicsOutput::GUID))?; + + let gops = handles.handles().iter().filter_map(|handle| unsafe { + self.boot_services() + .open_protocol::( + OpenProtocolParams { + handle: handle.clone(), + agent: self.handle, + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .ok() + }); + + let (_, (gop, mode)) = gops + .map(|gop| { + let (pixel_count, mode) = gop + .modes() + .map(|mode| { + let (x, y) = mode.info().resolution(); + (x * y, mode) + }) + .collect::>() + .pop_last() + .expect("gop has 0 modes !?"); + + (pixel_count, (gop, mode)) + }) + .collect::>() + .pop_last() + .expect("no GOP found!"); + + GraphicsOutput::from_gop_with_mode(gop, mode) + } + + pub fn run(mut self) -> Result { + info!("Hello, UEFI!"); + self.system_table_mut().stdin().reset(false)?; + + enable_fpu().expect("no FPU/SSE/SSE2!"); + + let fs = self.fs()?; + + for entry in fs.root_dir()?.into_iter() { + info!("{:#?}", entry); + if !entry.attribute().contains(FileAttribute::DIRECTORY) { + let mut file = entry + .open(FileMode::Read, None)? + .into_regular_file() + .unwrap(); + + let mut buf = vec![0u8; entry.file_size() as usize]; + _ = file.read(&mut buf).unwrap(); + let s = String::from_utf8_lossy(&buf); + info!("{:?}", s); + } + } + + let mut display = self.get_graphics_output()?; + let (width, height) = display.resolution(); + + info!("creating terminal"); + + loop {} + + Ok(Status::SUCCESS) + } +} + +#[entry] +fn efi_entry(handle: Handle, system_table: SystemTable) -> Status { + let ctx = Context::new(handle, system_table).unwrap(); + ctx.run().unwrap() +} diff --git a/esp/EFI/BOOT/FREELDR.INI b/esp/EFI/BOOT/FREELDR.INI new file mode 100644 index 0000000..0babcc2 --- /dev/null +++ b/esp/EFI/BOOT/FREELDR.INI @@ -0,0 +1,3 @@ +[FREELOADER] +TimeOut=10 +DefaultOS=Windows diff --git a/esp/test.ini b/esp/test.ini new file mode 100644 index 0000000..0babcc2 --- /dev/null +++ b/esp/test.ini @@ -0,0 +1,3 @@ +[FREELOADER] +TimeOut=10 +DefaultOS=Windows diff --git a/qemu.sh b/qemu.sh new file mode 100755 index 0000000..a002c3b --- /dev/null +++ b/qemu.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +mkdir -vp esp/EFI/BOOT +cp $1 esp/EFI/BOOT/BOOTX64.EFI + +exec qemu-system-x86_64 -accel kvm \ + -m 4G \ + -cpu host \ + -smp 2,sockets=1,dies=1,cores=2,threads=1 \ + -vga virtio \ + -no-reboot \ + -serial stdio \ + -usb -device usb-mouse \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/ovmf/x64/OVMF_CODE.fd \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/ovmf/x64/OVMF_VARS.fd \ + -drive format=raw,file=fat:rw:esp diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..87b5402 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] \ No newline at end of file