Initial commit
This commit is contained in:
commit
7e1770e591
8
.cargo/config.toml
Normal file
8
.cargo/config.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[build]
|
||||
target = "x86_64-unknown-uefi"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core", "compiler_builtins"]
|
||||
|
||||
[target.x86_64-unknown-uefi]
|
||||
rustflags = ["-Ccode-model=small", "-Clink-arg=/debug:none"]
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
194
Cargo.lock
generated
Normal file
194
Cargo.lock
generated
Normal file
@ -0,0 +1,194 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcada80daa06c42ed5f48c9a043865edea5dc44cbf9ac009fda3b89526e28607"
|
||||
dependencies = [
|
||||
"ptr_meta_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta_derive"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucs2"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad643914094137d475641b6bab89462505316ec2ce70907ad20102d28a79ab8"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b63e82686b4bdb0db74f18b2abbd60a0470354fb640aa69e115598d714d0a10"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"log",
|
||||
"ptr_meta",
|
||||
"ucs2",
|
||||
"uefi-macros",
|
||||
"uefi-raw",
|
||||
"uguid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi-macros"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "023d94ef8e135d068b9a3bd94614ef2610b2b0419ade0a9d8f3501fa9cd08e95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi-raw"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62642516099c6441a5f41b0da8486d5fc3515a0603b0fdaea67b31600e22082e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ptr_meta",
|
||||
"uguid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi-services"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44b32954ebbb4be5ebfde0df6699c2091f04e9f9c3762c65f3435dfb1a90a668"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"uefi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uguid"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16dfbd255defbd727b3a30e8950695d2e6d045841ee250ff0f1f7ced17917f8d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "yboot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"log",
|
||||
"uefi",
|
||||
"uefi-services",
|
||||
"yboot-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yboot-proto"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "yboot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
log = "0.4.19"
|
||||
uefi = "0.24.0"
|
||||
uefi-services = "0.21.0"
|
||||
yboot-proto = { git = "https://git.alnyan.me/yggdrasil/yboot-proto.git" }
|
||||
|
||||
[patch.'https://git.alnyan.me/yggdrasil/yboot-proto.git']
|
||||
yboot-proto = { path = "../yboot-proto" }
|
33
qemu.sh
Executable file
33
qemu.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
ARCH=x86_64-unknown-uefi
|
||||
PROFILE=debug
|
||||
O=target/${ARCH}/${PROFILE}
|
||||
|
||||
BIOS=/usr/share/edk2-ovmf/x64/OVMF_CODE.fd
|
||||
IMAGE=${O}/image.fat32
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p ${O}/image
|
||||
|
||||
cargo build
|
||||
|
||||
dd if=/dev/zero of=${IMAGE} bs=1M count=64
|
||||
mkfs.vfat -F32 ${IMAGE}
|
||||
mcopy -i ${IMAGE} ${O}/yboot.efi ::yboot.efi
|
||||
|
||||
if [ "${KERNEL_FILE}" != "" ]; then
|
||||
mcopy -i ${IMAGE} ${KERNEL_FILE} ::kernel.elf
|
||||
fi
|
||||
|
||||
qemu-system-x86_64 \
|
||||
-s \
|
||||
-serial mon:stdio \
|
||||
-m 256 \
|
||||
-drive format=raw,file=${BIOS},readonly=on,if=pflash \
|
||||
-drive format=raw,file=${IMAGE} \
|
||||
-net none \
|
||||
-enable-kvm \
|
||||
-M q35 \
|
||||
-cpu host
|
329
src/elf.rs
Normal file
329
src/elf.rs
Normal file
@ -0,0 +1,329 @@
|
||||
use core::mem::size_of;
|
||||
|
||||
use bytemuck::Zeroable;
|
||||
use log::{debug, error, info};
|
||||
// TODO use 'elf' crate
|
||||
use uefi::{
|
||||
proto::media::file::{File, FileAttribute, FileMode, RegularFile},
|
||||
table::boot::MemoryMap,
|
||||
CStr16, Error, Status,
|
||||
};
|
||||
use yboot_proto::LoadProtocolV1;
|
||||
|
||||
use crate::{
|
||||
elf::types::{PT_LOAD, SHF_ALLOC, SHF_WRITE, SHT_PROGBITS},
|
||||
mem::MemoryMapExt,
|
||||
};
|
||||
|
||||
use self::types::{Ehdr, Phdr, Shdr};
|
||||
|
||||
mod types {
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
pub type Off = u64;
|
||||
pub type Addr = u64;
|
||||
pub type Half = u16;
|
||||
pub type Word = u32;
|
||||
pub type XWord = u64;
|
||||
|
||||
pub const PT_LOAD: Word = 1;
|
||||
|
||||
pub const SHT_PROGBITS: Word = 1;
|
||||
|
||||
pub const SHF_WRITE: XWord = 1 << 0;
|
||||
pub const SHF_ALLOC: XWord = 1 << 1;
|
||||
|
||||
#[derive(Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Ehdr {
|
||||
pub ident: [u8; 16],
|
||||
pub type_: Half,
|
||||
pub machine: Half,
|
||||
pub version: Word,
|
||||
pub entry: Addr,
|
||||
pub phoff: Off,
|
||||
pub shoff: Off,
|
||||
pub flags: Word,
|
||||
pub ehsize: Half,
|
||||
pub phentsize: Half,
|
||||
pub phnum: Half,
|
||||
pub shentsize: Half,
|
||||
pub shnum: Half,
|
||||
pub shstrndx: Half,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Shdr {
|
||||
pub name: Word,
|
||||
pub type_: Word,
|
||||
pub flags: XWord,
|
||||
pub addr: Addr,
|
||||
pub offset: Off,
|
||||
pub size: XWord,
|
||||
pub link: Word,
|
||||
pub info: Word,
|
||||
pub addralign: XWord,
|
||||
pub entsize: XWord,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Phdr {
|
||||
pub type_: Word,
|
||||
pub flags: Word,
|
||||
pub offset: Off,
|
||||
pub vaddr: Addr,
|
||||
pub paddr: Addr,
|
||||
pub filesz: XWord,
|
||||
pub memsz: XWord,
|
||||
pub align: XWord,
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum address this loader can map in the target kernel
|
||||
pub const MAX_KERNEL_PHYS: u64 = 0x100000000;
|
||||
|
||||
pub struct Object {
|
||||
file: RegularFile,
|
||||
ehdr: Ehdr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadedObject {
|
||||
pub image_start: u64,
|
||||
pub image_end: u64,
|
||||
|
||||
pub entry: u64,
|
||||
|
||||
pub protocol_struct_paddr: u64,
|
||||
pub protocol_version: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LocatedProtocol {
|
||||
address: usize,
|
||||
offset: u64,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
trait ReadExact {
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl ReadExact for RegularFile {
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> {
|
||||
if self.read(buf)? != buf.len() {
|
||||
todo!();
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn open<D: File>(root: &mut D, path: &CStr16) -> Result<Self, Error> {
|
||||
let file = root.open(path, FileMode::Read, FileAttribute::empty())?;
|
||||
let mut file = file.into_regular_file().unwrap();
|
||||
|
||||
let mut ehdr = Ehdr::zeroed();
|
||||
file.set_position(0)?;
|
||||
file.read_exact(bytemuck::bytes_of_mut(&mut ehdr))?;
|
||||
|
||||
// Validate the the image is indeed ELF
|
||||
if &ehdr.ident[..4] != b"\x7FELF" {
|
||||
error!("Image has invalid ELF magic");
|
||||
return Err(Error::new(Status::LOAD_ERROR, ()));
|
||||
}
|
||||
|
||||
// Validate that we're loading a x86_64 image
|
||||
if ehdr.ident[4] != 2 {
|
||||
error!("Image is not 64-bit");
|
||||
return Err(Error::new(Status::LOAD_ERROR, ()));
|
||||
}
|
||||
|
||||
// Check that the entry point is set
|
||||
if ehdr.entry == 0 {
|
||||
error!("Image does not have a valid entry point");
|
||||
return Err(Error::new(Status::LOAD_ERROR, ()));
|
||||
}
|
||||
|
||||
Ok(Self { file, ehdr })
|
||||
}
|
||||
|
||||
// Returns the image entry point
|
||||
pub fn load(&mut self, mmap: &MemoryMap) -> Result<LoadedObject, Error> {
|
||||
// 1. Locate the protocol data structure
|
||||
let Some(loc_proto) = self.locate_protocol_data()? else {
|
||||
error!("The image does not have a valid protocol structure");
|
||||
todo!();
|
||||
};
|
||||
|
||||
info!("Protocol structure @ {:#x?}", loc_proto);
|
||||
// No other protocol versions currently implemented, so just try to interpret this as V1
|
||||
if loc_proto.size < size_of::<LoadProtocolV1>() {
|
||||
error!("The image's protocol structure has invalid size");
|
||||
todo!();
|
||||
}
|
||||
|
||||
// TODO avoid reading the whole protocol struct just to obtain the virtual offset
|
||||
let mut proto_data = LoadProtocolV1::zeroed();
|
||||
self.file.set_position(loc_proto.offset)?;
|
||||
self.file
|
||||
.read_exact(bytemuck::bytes_of_mut(&mut proto_data))?;
|
||||
|
||||
info!(
|
||||
"Kernel is virtually mapped at {:#x}",
|
||||
proto_data.kernel_virt_offset
|
||||
);
|
||||
|
||||
// 2. Find the kernel's range and check that the loaded physical addresses are actually
|
||||
// usable from UEFI
|
||||
let mut image_start = u64::MAX;
|
||||
let mut image_end = 0;
|
||||
|
||||
for i in 0..self.ehdr.phnum {
|
||||
let phdr = self.read_phdr(i as _)?;
|
||||
|
||||
if phdr.type_ != PT_LOAD {
|
||||
continue;
|
||||
}
|
||||
if phdr.paddr + phdr.memsz >= MAX_KERNEL_PHYS {
|
||||
error!(
|
||||
"Kernel segment cannot be mapped (above 4GiB): {:#x?}",
|
||||
phdr.paddr..phdr.paddr + phdr.memsz
|
||||
);
|
||||
todo!();
|
||||
}
|
||||
|
||||
let aligned_start = phdr.paddr & !0xFFF;
|
||||
let aligned_end = (phdr.paddr + phdr.memsz + 0xFFF) & !0xFFF;
|
||||
|
||||
if aligned_start < image_start {
|
||||
image_start = aligned_start;
|
||||
}
|
||||
if aligned_end > image_end {
|
||||
image_end = aligned_end;
|
||||
}
|
||||
|
||||
// Check all pages
|
||||
for page in (aligned_start..aligned_end).step_by(0x1000) {
|
||||
if !mmap.is_usable(page) {
|
||||
error!(
|
||||
"Cannot load segment {:#x?}: crosses loader-used or reserved memory",
|
||||
phdr.paddr..phdr.paddr + phdr.memsz
|
||||
);
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO maybe reserve the kernel memory in the boot services?
|
||||
// 3. Load the segments
|
||||
for i in 0..self.ehdr.phnum {
|
||||
let phdr = self.read_phdr(i as _)?;
|
||||
|
||||
if phdr.type_ != PT_LOAD {
|
||||
continue;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Load segment {}: {:#x?}",
|
||||
i,
|
||||
phdr.paddr..phdr.paddr + phdr.memsz
|
||||
);
|
||||
|
||||
if phdr.filesz > 0 {
|
||||
// The section has load data
|
||||
let dst = unsafe {
|
||||
core::slice::from_raw_parts_mut(phdr.paddr as *mut u8, phdr.filesz as usize)
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Load {:#x?} from ELF offset {:#x}",
|
||||
phdr.paddr..phdr.paddr + phdr.filesz,
|
||||
phdr.offset
|
||||
);
|
||||
self.file.set_position(phdr.offset)?;
|
||||
self.file.read_exact(dst)?;
|
||||
}
|
||||
|
||||
if phdr.memsz > 0 {
|
||||
let dst = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
(phdr.paddr + phdr.filesz) as *mut u8,
|
||||
(phdr.memsz - phdr.filesz) as usize,
|
||||
)
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Zero data {:#x?}",
|
||||
phdr.paddr + phdr.filesz..phdr.paddr + phdr.memsz
|
||||
);
|
||||
|
||||
dst.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the image is in memory, protocol structure can be written in the further steps
|
||||
let protocol_struct_paddr = (loc_proto.address as u64) - proto_data.kernel_virt_offset;
|
||||
let protocol_version = proto_data.header.version;
|
||||
|
||||
let entry = self.ehdr.entry;
|
||||
|
||||
Ok(LoadedObject {
|
||||
image_start,
|
||||
image_end,
|
||||
entry,
|
||||
protocol_struct_paddr,
|
||||
protocol_version,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_phdr(&mut self, index: usize) -> Result<Phdr, Error> {
|
||||
let mut phdr = Phdr::zeroed();
|
||||
self.file
|
||||
.set_position(self.ehdr.phoff + self.ehdr.phentsize as u64 * index as u64)?;
|
||||
self.file.read_exact(bytemuck::bytes_of_mut(&mut phdr))?;
|
||||
Ok(phdr)
|
||||
}
|
||||
|
||||
fn read_shdr(&mut self, index: usize) -> Result<Shdr, Error> {
|
||||
let mut shdr = Shdr::zeroed();
|
||||
self.file
|
||||
.set_position(self.ehdr.shoff + self.ehdr.shentsize as u64 * index as u64)?;
|
||||
self.file.read_exact(bytemuck::bytes_of_mut(&mut shdr))?;
|
||||
Ok(shdr)
|
||||
}
|
||||
|
||||
fn locate_protocol_data(&mut self) -> Result<Option<LocatedProtocol>, Error> {
|
||||
for i in 0..self.ehdr.shnum {
|
||||
let shdr = self.read_shdr(i as _)?;
|
||||
|
||||
if shdr.type_ != SHT_PROGBITS {
|
||||
continue;
|
||||
}
|
||||
if (shdr.flags & (SHF_ALLOC | SHF_WRITE)) != SHF_ALLOC | SHF_WRITE {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The protocol structure must be located at the start of the section
|
||||
// Read the magic field from the ELF section data
|
||||
let mut magic = 0;
|
||||
self.file.set_position(shdr.offset)?;
|
||||
self.file.read_exact(bytemuck::bytes_of_mut(&mut magic))?;
|
||||
|
||||
if magic != yboot_proto::KERNEL_MAGIC {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(Some(LocatedProtocol {
|
||||
offset: shdr.offset,
|
||||
address: shdr.addr as _,
|
||||
size: shdr.size as _,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
63
src/initrd.rs
Normal file
63
src/initrd.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use uefi::{
|
||||
proto::media::file::{Directory, File, FileAttribute, FileInfo, FileMode, RegularFile},
|
||||
table::boot::MemoryMap,
|
||||
CStr16, Error,
|
||||
};
|
||||
|
||||
use crate::{elf::LoadedObject, mem::MemoryMapExt};
|
||||
|
||||
fn can_place(mmap: &MemoryMap, base: u64, size: u64) -> bool {
|
||||
let start_aligned = base & !0xFFF;
|
||||
let end_aligned = (base + size + 0xFFF) & !0xFFF;
|
||||
for page in (start_aligned..end_aligned).step_by(0x1000) {
|
||||
if !mmap.is_usable(page) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn do_load(file: &mut RegularFile, base: u64, size: u64) -> Result<(), Error> {
|
||||
let buffer = unsafe { core::slice::from_raw_parts_mut(base as *mut u8, size as usize) };
|
||||
file.read(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_somewhere(
|
||||
root: &mut Directory,
|
||||
filename: &CStr16,
|
||||
mmap: &MemoryMap,
|
||||
obj: &LoadedObject,
|
||||
) -> Result<(u64, u64), Error> {
|
||||
const IMAGE_END_GAP: u64 = 0x3000;
|
||||
const MAXIMUM_ADDRESS: u64 = 0x100000000;
|
||||
|
||||
let mut info_buffer: [u8; 1024] = [0; 1024];
|
||||
|
||||
let file = root.open(filename, FileMode::Read, FileAttribute::empty())?;
|
||||
let mut file = file.into_regular_file().unwrap();
|
||||
|
||||
let file_info: &FileInfo = file.get_info(&mut info_buffer).unwrap();
|
||||
let size = file_info.file_size();
|
||||
|
||||
// 1. Try loading below the kernel
|
||||
if obj.image_start >= size {
|
||||
let start = (obj.image_start - size) & !0xFFF;
|
||||
|
||||
if can_place(mmap, start, size) {
|
||||
do_load(&mut file, start, size)?;
|
||||
return Ok((start, size));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try any location above the kernel
|
||||
let start = ((obj.image_end + 0xFFF) & !0xFFF) + IMAGE_END_GAP;
|
||||
for base in (start..MAXIMUM_ADDRESS).step_by(0x1000) {
|
||||
if can_place(mmap, base, size) {
|
||||
do_load(&mut file, base, size)?;
|
||||
return Ok((base, size));
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Could not place initrd");
|
||||
}
|
195
src/main.rs
Normal file
195
src/main.rs
Normal file
@ -0,0 +1,195 @@
|
||||
#![feature(asm_const)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
pub mod elf;
|
||||
pub mod initrd;
|
||||
pub mod mem;
|
||||
pub mod protocol_ext;
|
||||
|
||||
use core::{arch::asm, mem::size_of, ops::Deref};
|
||||
|
||||
use elf::Object;
|
||||
use log::{debug, error, info};
|
||||
use uefi::{
|
||||
prelude::*,
|
||||
proto::{
|
||||
console::gop::GraphicsOutput, device_path::DevicePath, loaded_image::LoadedImage,
|
||||
media::fs::SimpleFileSystem,
|
||||
},
|
||||
table::{
|
||||
boot::{AllocateType, MemoryType, ScopedProtocol},
|
||||
cfg,
|
||||
},
|
||||
Error,
|
||||
};
|
||||
use yboot_proto::{
|
||||
v1::{AvailableMemoryRegion, FramebufferOption},
|
||||
LoadProtocolV1, LOADER_MAGIC,
|
||||
};
|
||||
|
||||
use crate::{mem::MemoryDescriptorExt, protocol_ext::GraphicsOutputExt};
|
||||
|
||||
fn setup_framebuffer(bs: &BootServices, fb: &mut FramebufferOption) -> Result<(), Error> {
|
||||
let gop_handle = bs.get_handle_for_protocol::<GraphicsOutput>()?;
|
||||
let mut gop = bs.open_protocol_exclusive::<GraphicsOutput>(gop_handle)?;
|
||||
|
||||
// Find the requested mode
|
||||
let mode = gop.match_mode(fb.req_width, fb.req_height).ok_or_else(|| {
|
||||
error!(
|
||||
"Requested mode is not supported: {}x{}",
|
||||
fb.req_width, fb.req_height
|
||||
);
|
||||
Error::new(Status::INVALID_PARAMETER, ())
|
||||
})?;
|
||||
|
||||
gop.set_mode(&mode)?;
|
||||
|
||||
let mut result = gop.frame_buffer();
|
||||
|
||||
fb.res_width = fb.req_width;
|
||||
fb.res_height = fb.req_height;
|
||||
fb.res_address = result.as_mut_ptr() as _;
|
||||
fb.res_stride = mode.info().stride() as u64 * 4;
|
||||
fb.res_size = result.size() as _;
|
||||
|
||||
info!(
|
||||
"Framebuffer: {}x{} @ {:#x}",
|
||||
fb.res_width, fb.res_height, fb.res_address
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn locate_rsdp(st: &SystemTable<Boot>) -> Option<u64> {
|
||||
for entry in st.config_table() {
|
||||
if entry.guid == cfg::ACPI_GUID {
|
||||
return Some(entry.address as u64);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn boot_partition(
|
||||
image: Handle,
|
||||
bs: &BootServices,
|
||||
) -> Result<ScopedProtocol<SimpleFileSystem>, Error> {
|
||||
let loaded_image = bs.open_protocol_exclusive::<LoadedImage>(image)?;
|
||||
let device_handle = loaded_image.device();
|
||||
|
||||
let device_path = bs.open_protocol_exclusive::<DevicePath>(device_handle)?;
|
||||
let mut device_path = device_path.deref();
|
||||
|
||||
let fs_handle = bs.locate_device_path::<SimpleFileSystem>(&mut device_path)?;
|
||||
bs.open_protocol_exclusive::<SimpleFileSystem>(fs_handle)
|
||||
}
|
||||
|
||||
fn load_kernel<'a, 'b>(
|
||||
ih: Handle,
|
||||
st: &'a SystemTable<Boot>,
|
||||
) -> Result<(u64, u64, &'b mut LoadProtocolV1), Error> {
|
||||
let bs = st.boot_services();
|
||||
|
||||
// Obtain the memory map
|
||||
let mmap = mem::memory_map(bs)?;
|
||||
|
||||
let mut fs = boot_partition(ih, bs)?;
|
||||
let mut root = fs.open_volume()?;
|
||||
|
||||
let mut kernel_obj = Object::open(&mut root, cstr16!("kernel.elf"))?;
|
||||
let loaded_obj = kernel_obj.load(&mmap)?;
|
||||
|
||||
debug!("Loaded object: {:#x?}", loaded_obj);
|
||||
|
||||
// Load initrd
|
||||
let (initrd_start, initrd_size) =
|
||||
initrd::load_somewhere(&mut root, cstr16!("initrd.img"), &mmap, &loaded_obj)?;
|
||||
debug!(
|
||||
"Loaded initrd: {:#x?}",
|
||||
initrd_start..initrd_start + initrd_size
|
||||
);
|
||||
|
||||
// Other versions are not existent yet
|
||||
assert_eq!(loaded_obj.protocol_version, 1);
|
||||
let proto_data = unsafe { &mut *(loaded_obj.protocol_struct_paddr as *mut LoadProtocolV1) };
|
||||
|
||||
let rsdp = locate_rsdp(st).ok_or_else(|| {
|
||||
error!("Failed to find a RSDP config entry");
|
||||
Error::new(Status::NOT_FOUND, ())
|
||||
})?;
|
||||
info!("RSDP at {:#x}", rsdp);
|
||||
|
||||
proto_data.rsdp_address = rsdp;
|
||||
proto_data.initrd_address = initrd_start;
|
||||
proto_data.initrd_size = initrd_size;
|
||||
|
||||
// 32768 bytes should be enough
|
||||
let mmap_memory = bs.allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, 8)?;
|
||||
info!("Memory map is placed at {:#x}", mmap_memory);
|
||||
|
||||
// Setup framebuffer if needed
|
||||
if proto_data.opt_framebuffer.req_width != 0 && proto_data.opt_framebuffer.req_height != 0 {
|
||||
setup_framebuffer(bs, &mut proto_data.opt_framebuffer)?;
|
||||
}
|
||||
|
||||
// Not yet supported
|
||||
if proto_data.kernel_virt_offset == 0 {
|
||||
todo!();
|
||||
}
|
||||
|
||||
// TODO handle other offsets
|
||||
assert_eq!(proto_data.kernel_virt_offset, 0xFFFFFF8000000000);
|
||||
info!(
|
||||
"Kernel entry will be at {:#x}",
|
||||
loaded_obj.entry + proto_data.kernel_virt_offset
|
||||
);
|
||||
|
||||
let entry = loaded_obj.entry + proto_data.kernel_virt_offset;
|
||||
|
||||
Ok((entry, mmap_memory, proto_data))
|
||||
}
|
||||
|
||||
unsafe fn map_and_enter_kernel(
|
||||
st: SystemTable<Boot>,
|
||||
proto_data: &mut LoadProtocolV1,
|
||||
mmap_memory: u64,
|
||||
entry: u64,
|
||||
) -> ! {
|
||||
let (_, mmap) = st.exit_boot_services();
|
||||
|
||||
let mut index = 0;
|
||||
|
||||
let mmap_data = core::slice::from_raw_parts_mut(
|
||||
mmap_memory as *mut AvailableMemoryRegion,
|
||||
8 * 0x1000 / size_of::<AvailableMemoryRegion>(),
|
||||
);
|
||||
|
||||
for entry in mmap.entries() {
|
||||
if entry.is_runtime_usable() {
|
||||
mmap_data[index] = AvailableMemoryRegion {
|
||||
start_address: entry.phys_start,
|
||||
page_count: entry.page_count,
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
proto_data.memory_map.address = mmap_memory;
|
||||
proto_data.memory_map.len = index as _;
|
||||
|
||||
let cr3 = mem::map_image();
|
||||
asm!("mov {0}, %cr3", in(reg) cr3, options(att_syntax));
|
||||
|
||||
asm!("jmp *{0}", in(reg) entry, in("eax") LOADER_MAGIC, options(noreturn, att_syntax));
|
||||
}
|
||||
|
||||
#[entry]
|
||||
fn efi_main(image_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||
uefi_services::init(&mut system_table).unwrap();
|
||||
|
||||
let (entry, mmap_memory, proto_data) = load_kernel(image_handle, &system_table).unwrap();
|
||||
unsafe {
|
||||
map_and_enter_kernel(system_table, proto_data, mmap_memory, entry);
|
||||
}
|
||||
}
|
100
src/mem.rs
Normal file
100
src/mem.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use uefi::{
|
||||
prelude::BootServices,
|
||||
table::boot::{MemoryDescriptor, MemoryMap, MemoryType},
|
||||
Error,
|
||||
};
|
||||
|
||||
const PTE_PRESENT: u64 = 1 << 0;
|
||||
const PTE_WRITABLE: u64 = 1 << 1;
|
||||
const PTE_BLOCK: u64 = 1 << 7;
|
||||
|
||||
const MMAP_BUFFER_SIZE: usize = 32768;
|
||||
|
||||
#[repr(C, align(0x10))]
|
||||
struct MmapBuffer {
|
||||
data: [u8; MMAP_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, align(0x1000))]
|
||||
struct Table {
|
||||
data: [u64; 512],
|
||||
}
|
||||
|
||||
static mut MMAP_BUFFER: MmapBuffer = MmapBuffer {
|
||||
data: [0; MMAP_BUFFER_SIZE],
|
||||
};
|
||||
|
||||
static mut PML4: Table = Table { data: [0; 512] };
|
||||
static mut PDPT: Table = Table { data: [0; 512] };
|
||||
static mut PDS: [Table; 4] = [Table { data: [0; 512] }; 4];
|
||||
|
||||
pub trait MemoryMapExt {
|
||||
fn is_usable(&self, page: u64) -> bool;
|
||||
}
|
||||
|
||||
pub trait MemoryDescriptorExt {
|
||||
fn is_runtime_usable(&self) -> bool;
|
||||
}
|
||||
|
||||
impl MemoryMapExt for MemoryMap<'_> {
|
||||
fn is_usable(&self, page: u64) -> bool {
|
||||
assert_eq!(page & 0xFFF, 0);
|
||||
for entry in self.entries() {
|
||||
let range = entry.phys_start..entry.phys_start + entry.page_count * 0x1000;
|
||||
|
||||
if range.contains(&page) {
|
||||
return entry.ty == MemoryType::CONVENTIONAL;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found in the memory map
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryDescriptorExt for MemoryDescriptor {
|
||||
fn is_runtime_usable(&self) -> bool {
|
||||
self.ty == MemoryType::LOADER_DATA
|
||||
|| self.ty == MemoryType::LOADER_CODE
|
||||
|| self.ty == MemoryType::BOOT_SERVICES_CODE
|
||||
|| self.ty == MemoryType::BOOT_SERVICES_DATA
|
||||
|| self.ty == MemoryType::RUNTIME_SERVICES_DATA
|
||||
|| self.ty == MemoryType::RUNTIME_SERVICES_CODE
|
||||
|| self.ty == MemoryType::CONVENTIONAL
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory_map(bs: &BootServices) -> Result<MemoryMap, Error> {
|
||||
bs.memory_map(unsafe { &mut MMAP_BUFFER.data })
|
||||
}
|
||||
|
||||
// TODO handle other offsets
|
||||
pub unsafe fn map_image() -> u64 {
|
||||
// Load the original cr3 to obtain the first 512GiB, these are still in use by UEFI
|
||||
let mut cr3: usize;
|
||||
core::arch::asm!("mov %cr3, {0}", out(reg) cr3, options(att_syntax));
|
||||
let orig_pml4 = core::slice::from_raw_parts(cr3 as *const u64, 512);
|
||||
|
||||
// Setup the mapping tables
|
||||
for i in 0..512 * PDS.len() {
|
||||
let pd_index = i / 512;
|
||||
let pd_offset = i % 512;
|
||||
|
||||
PDS[pd_index].data[pd_offset] = (i << 21) as u64 | PTE_BLOCK | PTE_WRITABLE | PTE_PRESENT;
|
||||
}
|
||||
|
||||
for i in 0..PDS.len() {
|
||||
let addr = PDS[i].data.as_mut_ptr() as u64;
|
||||
PDPT.data[i] = addr | PTE_WRITABLE | PTE_PRESENT;
|
||||
}
|
||||
|
||||
let addr = PDPT.data.as_mut_ptr() as u64;
|
||||
|
||||
// Clone the lower mapping from the UEFI's table
|
||||
PML4.data[0] = orig_pml4[0];
|
||||
// Set up upper mapping for the kernel
|
||||
PML4.data[511] = addr | PTE_WRITABLE | PTE_PRESENT;
|
||||
|
||||
PML4.data.as_mut_ptr() as u64
|
||||
}
|
20
src/protocol_ext.rs
Normal file
20
src/protocol_ext.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use uefi::proto::console::gop::{GraphicsOutput, Mode};
|
||||
|
||||
pub trait GraphicsOutputExt {
|
||||
fn match_mode(&self, width: u32, height: u32) -> Option<Mode>;
|
||||
}
|
||||
|
||||
impl GraphicsOutputExt for GraphicsOutput {
|
||||
fn match_mode(&self, width: u32, height: u32) -> Option<Mode> {
|
||||
for mode in self.modes() {
|
||||
let mode_info = mode.info();
|
||||
let (mode_w, mode_h) = mode_info.resolution();
|
||||
|
||||
if (mode_w as u32 == width) && (mode_h as u32 == height) {
|
||||
return Some(mode);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user