This commit is contained in:
Janis 2023-01-15 22:04:14 +01:00
commit 354012852a
15 changed files with 750 additions and 0 deletions

13
.cargo/config Normal file
View file

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

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/esp/EFI/BOOT/BOOTX64.EFI
/Cargo.lock
/target/

2
Cargo.toml Normal file
View file

@ -0,0 +1,2 @@
[workspace]
members = ["bootloader"]

31
bootloader/Cargo.toml Normal file
View file

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

23
bootloader/src/context.rs Normal file
View file

@ -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<Self> {
let image = boot_services.open_protocol_exclusive::<LoadedImage>(handle)?;
Ok(Self {
handle,
boot_services,
image,
})
}
}

49
bootloader/src/error.rs Normal file
View file

@ -0,0 +1,49 @@
use core::fmt::Display;
pub type Result<T> = core::result::Result<T, Error>;
#[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<uefi::data_types::FromStrWithBufError> for Error {
fn from(value: uefi::data_types::FromStrWithBufError) -> Self {
Self::FromStrWithBufError(value)
}
}
impl From<uefi::Error> 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())
}
}

33
bootloader/src/fpu.rs Normal file
View file

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

255
bootloader/src/fs.rs Normal file
View file

@ -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<ScopedProtocol<'a, SimpleFileSystem>>,
}
pub struct FileInfoIterator {
dir: Directory,
}
impl FileInfoIterator {
pub fn new(dir: Directory) -> Self {
Self { dir }
}
pub fn next_entry(&mut self) -> Result<Option<Box<FileInfo>>> {
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::<FileInfo>::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<FileInfo>,
}
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<FileAttribute>,
) -> Result<FileHandle> {
let handle = self.dir.get_mut().open(
self.file_info.file_name(),
open_mode,
attribs.unwrap_or(FileAttribute::empty()),
)?;
Ok(handle)
}
}
impl AsRef<FileInfo> 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<Option<Box<FileInfo>>> {
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::<FileInfo>::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::Item> {
self.next_entry()
.ok()
.flatten()
.map(|file_info| DirectoryEntry {
dir: self.clone(),
file_info,
})
}
}
}
impl Iterator for FileInfoIterator {
type Item = Box<FileInfo>;
fn next(&mut self) -> Option<Self::Item> {
self.next_entry().ok().flatten()
}
}
#[repr(transparent)]
#[derive(Clone)]
pub struct Directory {
dir: Arc<RefCell<UefiDirectory>>,
}
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<FileHandle> {
self.get_mut().open(filename, open_mode, attributes)
}
pub fn read_entry<'buf>(
&mut self,
buffer: &'buf mut [u8],
) -> uefi::Result<Option<&'buf mut FileInfo>, Option<usize>> {
self.get_mut().read_entry(buffer)
}
}
impl From<UefiDirectory> 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<FileAttribute>,
) -> Result<FileHandle> {
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<Directory> {
Ok(self.inner.borrow_mut().open_volume()?.into())
}
}

View file

@ -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<gop::BltPixel>,
}
impl<'a, 'b> GraphicsOutput<'a, 'b> {
pub fn from_gop(mut gop: ScopedProtocol<'a, gop::GraphicsOutput<'b>>) -> Result<Self> {
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<Self> {
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(())
}
}

51
bootloader/src/input.rs Normal file
View file

@ -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<Boot>,
event: [Event; 1],
}
impl Input {
pub fn new(st: &SystemTable<Boot>) -> 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<char> {
DecodeUCS2Char::decode_ucs2(u16::from(ch)).into_char()
}
pub fn next_key(&mut self) -> Option<CharOrScanCode> {
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
}
}
}

169
bootloader/src/main.rs Normal file
View file

@ -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<Boot>,
}
impl Context {
pub fn new(handle: Handle, mut st: SystemTable<Boot>) -> uefi::Result<Self> {
uefi_services::init(&mut st)?;
Ok(Self { handle, st })
}
pub fn fs(&self) -> Result<fs::FileSystem> {
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<Boot> {
&self.st
}
pub fn handle(&self) -> Handle {
self.handle
}
pub fn system_table_mut(&mut self) -> &mut SystemTable<Boot> {
&mut self.st
}
pub fn boot_services(&self) -> &BootServices {
self.st.boot_services()
}
pub fn get_graphics_output(&self) -> Result<GraphicsOutput> {
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::<gop::GraphicsOutput>(
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::<BTreeMap<_, _>>()
.pop_last()
.expect("gop has 0 modes !?");
(pixel_count, (gop, mode))
})
.collect::<BTreeMap<_, _>>()
.pop_last()
.expect("no GOP found!");
GraphicsOutput::from_gop_with_mode(gop, mode)
}
pub fn run(mut self) -> Result<Status> {
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<Boot>) -> Status {
let ctx = Context::new(handle, system_table).unwrap();
ctx.run().unwrap()
}

3
esp/EFI/BOOT/FREELDR.INI Normal file
View file

@ -0,0 +1,3 @@
[FREELOADER]
TimeOut=10
DefaultOS=Windows

3
esp/test.ini Normal file
View file

@ -0,0 +1,3 @@
[FREELOADER]
TimeOut=10
DefaultOS=Windows

16
qemu.sh Executable file
View file

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

3
rust-toolchain.toml Normal file
View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
components = ["rust-src"]