#[cfg(target_arch = "x86_64")]
use core::arch::naked_asm;
use core::{
ffi::c_void,
fmt::Write,
mem::MaybeUninit,
ptr::{addr_of_mut, null},
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
pub use uefi::Status;
#[cfg(target_arch = "loongarch64")]
pub use uefi::runtime::ResetType;
use uefi::{
Guid, Result,
boot::{self, MemoryDescriptor, MemoryType},
guid,
prelude::*,
proto::loaded_image::LoadedImage,
runtime::{self, set_virtual_address_map},
system::with_config_table,
table::{self, cfg::ConfigTableEntry},
};
use crate::{
ArchTrait,
acpi::set_rsdp,
arch::{Arch, relocate},
mem::{__io, __va},
};
const EFI_IMAGE_HANDLE_UNSET: usize = usize::MAX;
const EXIT_BOOT_MEMORY_MAP_BUFFER_SIZE: usize = 128 * 1024;
const EXIT_BOOT_MEMORY_MAP_DESCRIPTOR_CAPACITY: usize = 1024;
const EXIT_BOOT_MEMORY_MAP_RETRIES: usize = 3;
const FDT_TABLE_GUID: Guid = guid!("b1b621d5-f19c-41a5-830b-d9152c69aae0");
#[repr(align(8))]
struct AlignedBytes<const N: usize>([u8; N]);
static mut EXIT_BOOT_MEMORY_MAP_BUFFER: AlignedBytes<EXIT_BOOT_MEMORY_MAP_BUFFER_SIZE> =
AlignedBytes([0; EXIT_BOOT_MEMORY_MAP_BUFFER_SIZE]);
static mut EXIT_BOOT_MEMORY_MAP_DESCRIPTORS: [MaybeUninit<MemoryDescriptor>;
EXIT_BOOT_MEMORY_MAP_DESCRIPTOR_CAPACITY] =
[const { MaybeUninit::uninit() }; EXIT_BOOT_MEMORY_MAP_DESCRIPTOR_CAPACITY];
static EFI_IMAGE_HANDLE: AtomicUsize = AtomicUsize::new(EFI_IMAGE_HANDLE_UNSET);
pub(crate) fn setup_service(system_table: *const ::core::ffi::c_void) {
unsafe { table::set_system_table(system_table.cast()) };
if let Some(image_handle) = saved_image_handle() {
unsafe { boot::set_image_handle(image_handle) };
}
setup_console();
println!("UEFI console ok.");
find_fdt();
find_acpi_rsdp();
}
pub(crate) mod memmap;
pub mod pe;
#[cfg(target_arch = "x86_64")]
#[unsafe(naked)]
#[unsafe(no_mangle)]
#[unsafe(link_section = ".text")]
pub unsafe extern "C" fn __x86_64_efi_pe_entry() -> Status {
naked_asm!(
"sub rsp, 8",
"mov r12, rcx",
"mov r13, rdx",
"call {relocate}",
"mov rdi, r12",
"mov rsi, r13",
"add rsp, 8",
"jmp {entry}",
relocate = sym relocate,
entry = sym efi_pe_entry_main,
)
}
unsafe extern "C" fn efi_pe_entry_main(
image_handle: Handle,
system_table: *const ::core::ffi::c_void,
) -> Status {
unsafe {
save_image_handle(image_handle);
boot::set_image_handle(image_handle);
table::set_system_table(system_table.cast());
setup_console();
println!("UEFI application started.");
if Arch::efi_enter_kernel(system_table) {
Status::SUCCESS
} else {
unreachable!()
}
}
}
#[cfg(not(target_arch = "x86_64"))]
#[unsafe(export_name = "efi_pe_entry")]
#[unsafe(link_section = ".text")]
pub unsafe extern "efiapi" fn efi_pe_entry(
image_handle: Handle,
system_table: *const ::core::ffi::c_void,
) -> Status {
unsafe {
relocate();
efi_pe_entry_main(image_handle, system_table)
}
}
pub(crate) fn exit_boot_services() {
println!("Exiting UEFI boot services...");
UEFI_SERVICE_EXIT.store(true, core::sync::atomic::Ordering::Relaxed);
let mem_map = unsafe { exit_boot_services_no_alloc() };
println!("Exited boot services, memory map obtained.");
let mut new_map: heapless::Vec<MemoryDescriptor, 32> = heapless::Vec::new();
for entry in mem_map.entries() {
match entry.ty {
MemoryType::RUNTIME_SERVICES_CODE | MemoryType::RUNTIME_SERVICES_DATA => {
let mut en = *entry;
en.virt_start = __va(entry.phys_start as _) as usize as _;
new_map.push(en).unwrap();
}
MemoryType::MMIO => {
let mut en = *entry;
en.virt_start = __io(entry.phys_start as _) as usize as _;
new_map.push(en).unwrap();
}
_ => {}
}
}
unsafe {
if let Some(st) = uefi::table::system_table_raw() {
set_virtual_address_map(&mut new_map, __va(st.as_ptr() as _) as _)
.expect("Failed to set virtual address map");
}
}
memmap::setup_memory_map(mem_map.entries());
}
struct ExitBootMemoryMap {
entries: &'static [MemoryDescriptor],
}
impl ExitBootMemoryMap {
fn entries(&self) -> core::slice::Iter<'static, MemoryDescriptor> {
self.entries.iter()
}
}
unsafe fn exit_boot_services_no_alloc() -> ExitBootMemoryMap {
let Some(system_table) = uefi::table::system_table_raw() else {
reset_on_exit_boot_services_failure(Status::INVALID_PARAMETER);
};
let boot_services = unsafe {
system_table
.as_ref()
.boot_services
.as_ref()
.unwrap_or_else(|| reset_on_exit_boot_services_failure(Status::INVALID_PARAMETER))
};
let Some(image_handle) = saved_image_handle() else {
reset_on_exit_boot_services_failure(Status::INVALID_PARAMETER);
};
let mut status = Status::SUCCESS;
for _ in 0..EXIT_BOOT_MEMORY_MAP_RETRIES {
let mut map_size = EXIT_BOOT_MEMORY_MAP_BUFFER_SIZE;
let mut map_key = 0;
let mut desc_size = 0;
let mut desc_version = 0;
let map_ptr =
unsafe { addr_of_mut!(EXIT_BOOT_MEMORY_MAP_BUFFER.0).cast::<MemoryDescriptor>() };
status = unsafe {
(boot_services.get_memory_map)(
&mut map_size,
map_ptr,
&mut map_key,
&mut desc_size,
&mut desc_version,
)
};
if status == Status::BUFFER_TOO_SMALL {
continue;
}
if status != Status::SUCCESS {
reset_on_exit_boot_services_failure(status);
}
status = unsafe { (boot_services.exit_boot_services)(image_handle.as_ptr(), map_key) };
if status == Status::SUCCESS {
let entries =
unsafe { copy_exit_boot_memory_map(map_ptr.cast_const(), map_size, desc_size) };
return ExitBootMemoryMap { entries };
}
}
reset_on_exit_boot_services_failure(status);
}
unsafe fn copy_exit_boot_memory_map(
src: *const MemoryDescriptor,
map_size: usize,
desc_size: usize,
) -> &'static [MemoryDescriptor] {
assert!(
desc_size >= size_of::<MemoryDescriptor>(),
"UEFI memory descriptor size is too small"
);
let entry_count = map_size / desc_size;
assert!(
entry_count <= EXIT_BOOT_MEMORY_MAP_DESCRIPTOR_CAPACITY,
"UEFI memory map has too many entries"
);
let dst =
addr_of_mut!(EXIT_BOOT_MEMORY_MAP_DESCRIPTORS).cast::<MaybeUninit<MemoryDescriptor>>();
for index in 0..entry_count {
let entry = unsafe {
src.cast::<u8>()
.add(index * desc_size)
.cast::<MemoryDescriptor>()
};
unsafe { dst.add(index).write(MaybeUninit::new(entry.read())) };
}
unsafe { core::slice::from_raw_parts(dst.cast::<MemoryDescriptor>(), entry_count) }
}
fn save_image_handle(image_handle: Handle) {
EFI_IMAGE_HANDLE.store(image_handle.as_ptr() as usize, Ordering::Relaxed);
}
fn saved_image_handle() -> Option<Handle> {
let raw = EFI_IMAGE_HANDLE.load(Ordering::Relaxed);
if raw == EFI_IMAGE_HANDLE_UNSET {
return None;
}
unsafe { Handle::from_ptr(raw as *mut c_void) }
}
fn reset_on_exit_boot_services_failure(status: Status) -> ! {
unsafe {
if let Some(system_table) = uefi::table::system_table_raw()
&& let Some(runtime_services) = system_table.as_ref().runtime_services.as_ref()
{
(runtime_services.reset_system)(runtime::ResetType::COLD, status, 0, null());
}
}
loop {
core::hint::spin_loop();
}
}
pub(crate) fn setup_console() {
unsafe { crate::console::set_out(&UefiPrinter) };
}
#[allow(dead_code)]
fn efi_main() -> Result {
find_fdt();
find_acpi_rsdp();
println!("Page size: {:#x} bytes", crate::mem::page_size());
let h = boot::get_handle_for_protocol::<LoadedImage>()?;
let img = boot::open_protocol_exclusive::<LoadedImage>(h)?;
match img.load_options_as_cstr16() {
Ok(cmdline) => {
println!("Kernel command line: {}", cmdline);
}
Err(e) => {
println!("Failed to get load options as CStr16: {:?}", e);
}
}
Ok(())
}
static UEFI_SERVICE_EXIT: AtomicBool = AtomicBool::new(false);
struct UefiPrinter;
impl crate::console::Con for UefiPrinter {
fn write_str(&self, s: &str) {
if UEFI_SERVICE_EXIT.load(core::sync::atomic::Ordering::Relaxed) {
return;
}
uefi::system::with_stdout(|stdout| {
let _ = stdout.write_str(s);
});
}
}
fn find_fdt() {
with_config_table(|config_table| {
if let Some(addr) = find_fdt_address(config_table) {
println!("Found FDT at address: {:p}", addr);
unsafe {
crate::fdt::FDT_ADDR = addr as usize;
}
} else {
println!("No FDT found in UEFI config tables.");
}
})
}
fn find_fdt_address(config_table: &[ConfigTableEntry]) -> Option<*const c_void> {
config_table
.iter()
.find(|entry| entry.guid == FDT_TABLE_GUID)
.map(|entry| entry.address)
}
fn find_acpi_rsdp() {
with_config_table(|config_table| {
let mut version = 0;
let mut addr = null();
for entry in config_table {
if entry.guid == ConfigTableEntry::ACPI2_GUID {
println!("Found ACPI 2.0 RSDP at address: {:p}", entry.address);
version = 2;
addr = entry.address;
break;
}
if entry.guid == ConfigTableEntry::ACPI_GUID {
println!("Found ACPI 1.0 RSDP at address: {:p}", entry.address);
if version == 0 {
version = 1;
addr = entry.address;
}
}
}
if !addr.is_null() {
println!("Using ACPI {} RSDP at address: {:p}", version, addr);
set_rsdp(addr);
} else {
println!("No ACPI RSDP found in UEFI config tables.");
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finds_fdt_table_address_by_uefi_guid() {
let fdt_addr = 0x1234_0000usize as *const c_void;
let acpi_addr = 0x5678_0000usize as *const c_void;
let tables = [
ConfigTableEntry {
guid: ConfigTableEntry::ACPI2_GUID,
address: acpi_addr,
},
ConfigTableEntry {
guid: FDT_TABLE_GUID,
address: fdt_addr,
},
];
assert_eq!(find_fdt_address(&tables), Some(fdt_addr));
}
}
#[cfg(target_arch = "loongarch64")]
pub fn is_uefi_available() -> bool {
uefi::table::system_table_raw().is_some()
}
#[cfg(target_arch = "loongarch64")]
pub fn reset(reset_type: ResetType, status: Status, data: Option<&[u8]>) -> ! {
info!("Resetting system via UEFI...");
uefi::runtime::reset(reset_type, status, data)
}