initial commit of project, can be booted by UEFI firmware into an infinite loop, next step is to handover machine to an OS

This commit is contained in:
wmpod2xwie 2024-06-03 13:38:46 +10:00
commit f1827e6b23
15 changed files with 415 additions and 0 deletions

5
.cargo/config.toml Normal file
View File

@ -0,0 +1,5 @@
[build]
target = "x86_64-unknown-uefi"
[unstable]
build-std = ["core", "compiler_builtins", "alloc"]

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "uefi-os-loader"
version = "0.1.0"

26
Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "uefi-os-loader"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[[bin]]
name = "uefi_os_loader"
path = "src/uefi_os_loader.rs"
[features]
default = []
std = []
no_std = []
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
opt-level = "z"

BIN
docs/OVMF_CODE.fd Normal file

Binary file not shown.

BIN
docs/OVMF_VARS.fd Normal file

Binary file not shown.

Binary file not shown.

3
rust-toolchain.toml Normal file
View File

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
targets = ["x86_64-unknown-uefi"]

75
scripts/run_emulator.sh Executable file
View File

@ -0,0 +1,75 @@
#!/bin/bash
set -e
cleanup() {
echo "Error occurred, cleaning up."
sudo umount -v "${mount_directory}" || true # Unmount if mounted
sudo rm -rv "${mount_directory}" || true # Safely remove mount directory if it exists
sudo losetup -d "${loop_device}" || true # Detach loop device if attached
exit 1
}
trap cleanup ERR
# Build the project
cargo build --release
# Check if build was successful
if [ $? -eq 0 ]; then
# If successful, run the emulator script
echo "Building disk.img"
else
echo "Build failed, aborting."
exit 1
fi
script_directory=$(dirname "${BASH_SOURCE[0]}")
script_directory=$(realpath "$script_directory")
disk_image="$HOME/tmpfs/disk.img"
block_size="1M"
count="100"
disk_size="100MiB"
bootloader="${script_directory}/../target/x86_64-unknown-uefi/release/uefi_os_loader.efi"
# Get the size of the bootloader in bytes
bootloader_size=$(stat -c%s "$bootloader")
# Calculate the required disk image size with overhead
# Adding 20% overhead for FAT32 filesystem
overhead_percentage=20
overhead_size=$((bootloader_size * overhead_percentage / 100))
total_size=$((bootloader_size + overhead_size))
# Ensure the size is at least 4MB
minimum_size=$((4 * 1024 * 1024))
# Calculate the final size, which should be the maximum of total_size or minimum_size
final_size=$(( total_size > minimum_size ? total_size : minimum_size ))
# Ensure the size is aligned to a 1MB boundary
block_size=$((1024 * 1024))
count=$(( (final_size + block_size - 1) / block_size )) # Round up to nearest MB
echo "Creating disk image with size $((count * block_size)) bytes ($count MB)"
# Create the disk image
dd if=/dev/zero of="$disk_image" bs="$block_size" count="$count" status=progress
loop_device=$(sudo losetup -fP "$disk_image" --show)
echo "Using loop device "$loop_device"."
sudo parted --script "$loop_device" mklabel gpt
sudo parted --script "$loop_device" mkpart ESP fat32 1MiB 100%
sudo parted --script "$loop_device" set 1 esp on
sudo mkfs.fat -F 32 "${loop_device}p1" # Ensure the partition number is correct
mount_directory=$(mktemp --tmpdir="$script_directory" -d)
sudo mount -v "${loop_device}p1" "$mount_directory"
sudo mkdir -pv "${mount_directory}/EFI/BOOT/"
sudo cp -vf "$bootloader" "${mount_directory}/EFI/BOOT/BOOTX64.EFI"
sudo umount -v "${mount_directory}"
echo "Partition and formatting ✓"
sudo rm -rv "$mount_directory"
sudo losetup -d "$loop_device"
qemu-system-x86_64 -enable-kvm -nodefaults -vga std -monitor vc:1024x768 -machine q35,accel=kvm -m 1024 -cpu host -drive if=pflash,format=raw,readonly=on,file="${script_directory}/../docs/OVMF_CODE.fd" -drive if=pflash,format=raw,file="${script_directory}/../docs/OVMF_VARS.fd" -drive file="$disk_image",format=raw,index=0,media=disk -boot order=c

42
src/notes.md Normal file
View File

@ -0,0 +1,42 @@
//
// type efi_main = fn(i32) -> i32;
/*
Step 2: Obtain the Function Pointer
Assuming you're given a pointer to a table that contains function pointers, you'll first need to safely access this table and the specific function pointer. This is often done by casting the provided pointer to a Rust struct that mirrors the layout of the UEFI table. Here's a simplified example:
rust
#[repr(C)]
struct UefiTable {
example_function: *const ExampleFunction,
}
*/
/*
// Assume this is the function signature provided by UEFI.
type GetTime = unsafe extern "efiapi" fn(/* parameters */) -> /* return type */;
#[repr(C)]
struct RuntimeServices {
// Other fields omitted
get_time: *const GetTime,
// Other fields omitted
}
fn main() {
let runtime_services: *const RuntimeServices = /* provided by UEFI */;
unsafe {
if let Some(services) = runtime_services.as_ref() {
if let Some(get_time) = services.get_time.as_ref() {
get_time(/* arguments */);
}
}
}
}
*/

19
src/uefi_functions.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::uefi_structs::{MemoryDescriptor, Status, TPL};
// Task Priority Services
pub type raise_tpl = fn(efi_tpl: TPL) -> Status;
pub type restore_tpl = fn() -> ();
// Memory Services
pub type allocate_pages = fn() -> ();
pub type free_pages = fn() -> ();
pub type allocate_pool = fn() -> ();
pub type free_pool = fn() -> ();
pub type get_memory_map = extern "efiapi" fn(
memory_map_size: *mut usize,
memory_map: *mut MemoryDescriptor,
map_key: *mut usize,
descriptor_size: *mut usize,
descriptor_version: *mut u32,
) -> Status;

27
src/uefi_os_loader.rs Normal file
View File

@ -0,0 +1,27 @@
#![no_std]
#![no_main]
mod uefi_functions;
mod uefi_structs;
use core::panic::PanicInfo;
use uefi_structs::{Handle, SystemTable};
#[no_mangle]
#[export_name = "efi_main"]
extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemTable) -> usize {
unsafe {
let system_table_ref: &SystemTable = &*system_table;
let console_out: *mut uefi_structs::SimpleTextOutputProtocol = system_table_ref.console_out;
}
loop {};
0 // EFI_SUCCESS
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}

207
src/uefi_structs.rs Normal file
View File

@ -0,0 +1,207 @@
use crate::uefi_functions::raise_tpl;
#[repr(C)]
pub struct ImageEntryPoint {
image_handle: *const i32, // The firmware allocated handle for the UEFI image.
system_table_pointer: *const i32, // A pointer to the EFI System Table.
}
#[repr(C)]
pub struct TableSignatures;
impl TableSignatures {
pub const SYSTEM_TABLE_SIGNATURE: u64 = 0x5453595320494249;
pub const BOOT_TABLE_SIGNATURE: u64 = 0x56524553544f4f42;
}
#[repr(C)]
pub struct SystemTableRevisions;
impl SystemTableRevisions {
pub const EFI_2_90_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (90));
pub const EFI_2_80_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (80));
pub const EFI_2_70_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (70));
pub const EFI_2_60_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (60));
pub const EFI_2_50_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (50));
pub const EFI_2_40_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (40));
pub const EFI_2_31_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (31));
pub const EFI_2_30_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (30));
pub const EFI_2_20_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (20));
pub const EFI_2_10_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (10));
pub const EFI_2_00_SYSTEM_TABLE_REVISION: u32 = ((2 << 16) | (00));
pub const EFI_1_10_SYSTEM_TABLE_REVISION: u32 = ((1 << 16) | (10));
pub const EFI_1_02_SYSTEM_TABLE_REVISION: u32 = ((1 << 16) | (02));
}
/**
UEFI uses the EFI System Table, which contains pointers to the runtime and boot services tables. The
definition for this table is shown in the following code fragments. Except for the table header, all
elements in the service tables are pointers to functions as defined in Section 7 and Section 8. Prior to a
call to EFI_BOOT_SERVICES.ExitBootServices(), all of the fields of the EFI System Table are valid.
After an operating system has taken control of the platform with a call to ExitBootServices(), only
the Hdr, FirmwareVendor, FirmwareRevision, RuntimeServices, NumberOfTableEntries,
and ConfigurationTable fields are valid
**/
#[repr(C)]
pub struct SystemTable {
/// The table header for the EFI System Table. This header contains the EFI_SYSTEM_TABLE_SIGNATURE and EFI_SYSTEM_TABLE_REVISION values along with the size of the EFI_SYSTEM_TABLE structure and a 32-bit CRC to verify that the contents of the EFI System Table are valid.
pub efi_table_header: TableHeader,
/// A pointer to a null terminated string that identifies the vendor that produces the system firmware for the platform.
pub firmware_vendor: *mut u16,
/// A firmware vendor specific value that identifies the revision of the system firmware for the platform.
pub firmware_revision: SystemTableRevisions,
/// The handle for the active console input device. This handle must support EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. If there is no active console, these protocols must still be present.
pub console_in_handle: Handle,
/// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is associated with ConsoleInHandle.
pub console_in: *mut SimpleTextInputProtocol,
/// The handle for the active console output device. This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. If there is no active console, this protocol must still be present.
pub console_out_handle: Handle,
/// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface that is associated with ConsoleOutHandle.
pub console_out: *mut SimpleTextOutputProtocol,
/// The handle for the active standard error console device. This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. If there is no active console, this protocol must still be present.
pub standard_error_handle: Handle,
/// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface that is associated with StandardErrorHandle.
pub standard_error: *mut SimpleTextOutputProtocol,
/// A pointer to the EFI Runtime Services Table.
pub runtime_services_table_pointer: *mut RuntimeServicesTable,
/// A pointer to the EFI Boot Services Table.
pub boot_services_table_pointer: *mut BootServicesTable,
/// The number of system configuration tables in the buffer ConfigurationTable.
pub number_of_table_entries: usize,
/// A pointer to the system configuration tables. The number of entries in the table is NumberOfTableEntries.
pub configuration_table: ConfigurationTable,
// ACPIConfigurationTablePointer: *mut ACPIConfigurationTable,
// SMBIOSConfigurationTable: *mut SMBIOSConfigurationTable,
// SALSystemTable: *mut SALSystemTable
}
/**
UEFI uses the EFI Boot Services Table, which contains a table header and pointers to all of the boot
services. The definition for this table is shown in the following code fragments. Except for the table
header, all elements in the EFI Boot Services Tables are prototypes of function pointers to functions as
defined in Section 7. The function pointers in this table are not valid after the operating system has taken
control of the platform with a call to EFI_BOOT_SERVICES.ExitBootServices().
**/
#[repr(C)]
pub struct BootServicesTable {
efi_table_header: TableHeader,
load_image: extern "efiapi" fn(boot_policy: bool, parent_image_hanle: Handle),
}
#[repr(C)]
pub struct RuntimeServicesTable {
efi_table_header: TableHeader,
}
#[repr(C)]
pub struct ACPIConfigurationTable {}
#[repr(C)]
pub struct SMBIOSConfigurationTable {}
#[repr(C)]
pub struct SALSystemTable {}
#[repr(C)]
pub struct ConfigurationTable {}
#[repr(C)]
pub struct Handle {}
#[repr(C)]
pub struct SimpleTextInputProtocol {}
#[repr(C)]
pub struct SimpleTextOutputProtocol {}
// TODO:
/*
Signature A 64-bit signature that identifies the type of table that follows.
Unique signatures have been generated for the EFI System Table, the
EFI Boot Services Table, and the EFI Runtime Services Table.
Revision The revision of the EFI Specification to which this table conforms.
The upper 16 bits of this field contain the major revision value, and
the lower 16 bits contain the minor revision value. The minor
revision values are binary coded decimals and are limited to the
range of 00..99.
When printed or displayed UEFI spec revision is referred as (Major
revision).(Minor revision upper decimal).(Minor revision lower
decimal) or (Major revision).(Minor revision upper decimal) in case
Minor revision lower decimal is set to 0. For example:
A specification with the revision value ((2<<16) | (30)) would be
referred as 2.3;
A specification with the revision value ((2<<16) | (31)) would be
referred as 2.3.1
HeaderSize The size, in bytes, of the entire table including the
EFI_TABLE_HEADER.
CRC32 The 32-bit CRC for the entire table. This value is computed by setting
this field to 0, and computing the 32-bit CRC for HeaderSize bytes.
Reserved Reserved field that must be set to 0.
*/
#[repr(C)]
pub struct TableHeader {
signature: u64,
revision: u32,
header_size: u32,
crc32: u32,
reserved: u32,
}
#[repr(C)]
pub enum Status {
Success,
LoadError,
InvalidParameter,
Unsupported,
BadBufferSize,
BufferTooSmall,
NotReady,
DeviceError,
WriteProtected,
OutOfResources,
VolumeCorrupted,
VolumeFull,
NoMedia,
MediaChanged,
NotFound,
AccessDenied,
NoResponce,
NoMapping,
Timeout,
NotStarted,
AlreadyStarted,
Aborted,
IcmpError,
TftpError,
ProtocolError,
IncompatibleVersion,
SecurityViolation,
CrcError,
EndOfMedia,
EndOfFile,
InvalidLanguage,
CompromisedData,
IpAddressConflict,
HttpError,
}
#[repr(C)]
pub struct TPL {}
#[repr(C)]
pub struct EFIGUID {
data1: u32,
data2: u16,
data3: u16,
data4: [u8; 8],
}
#[repr(C)]
pub struct MemoryDescriptor {
pub type_: u32,
pub physical_start: u64,
pub virtual_start: u64,
pub number_of_pages: u64,
pub attribute: u64,
}

3
src/uefi_types.rs Normal file
View File

@ -0,0 +1,3 @@
use core;
pub type Handle = *mut core::ffi::c_void;
pub type PhysicalAddress = u64;