use core::{ffi::c_void, slice::from_raw_parts, sync::atomic::Ordering};
use patina::{
guids,
pi::{protocols, status_code},
};
use patina_internal_cpu::interrupts;
use r_efi::efi;
use spin::Once;
use crate::{
GCD,
allocator::terminate_memory_map,
events::EVENT_DB,
protocols::PROTOCOL_DB,
systemtables::{EfiSystemTable, SYSTEM_TABLE},
};
struct ArchProtocolPtr<T>(Once<*mut T>);
impl<T> ArchProtocolPtr<T> {
const fn new() -> Self {
ArchProtocolPtr(Once::new())
}
fn get(&self) -> Option<*mut T> {
self.0.get().copied()
}
unsafe fn init(&self, ptr: *mut c_void) {
assert!(!self.0.is_completed(), "Attempted to set ArchProtocolPtr more than once.");
let _ = self.0.call_once(|| ptr as *mut T);
}
}
unsafe impl<T> Send for ArchProtocolPtr<T> {}
unsafe impl<T> Sync for ArchProtocolPtr<T> {}
static METRONOME_ARCH_PTR: ArchProtocolPtr<protocols::metronome::Protocol> = ArchProtocolPtr::new();
static WATCHDOG_ARCH_PTR: ArchProtocolPtr<protocols::watchdog::Protocol> = ArchProtocolPtr::new();
pub const PRE_EBS_GUID: efi::Guid =
efi::Guid::from_fields(0x5f1d7e16, 0x784a, 0x4da2, 0xb0, 0x84, &[0xf8, 0x12, 0xf2, 0x3a, 0x8d, 0xce]);
extern "efiapi" fn calculate_crc32(data: *mut c_void, data_size: usize, crc_32: *mut u32) -> efi::Status {
if data.is_null() || data_size == 0 || crc_32.is_null() {
return efi::Status::INVALID_PARAMETER;
}
unsafe {
let buffer = from_raw_parts(data as *mut u8, data_size);
crc_32.write_unaligned(crc32fast::hash(buffer));
}
efi::Status::SUCCESS
}
extern "efiapi" fn stall(microseconds: usize) -> efi::Status {
if let Some(metronome_ptr) = METRONOME_ARCH_PTR.get() {
let metronome = unsafe { metronome_ptr.as_mut().unwrap() };
let ticks_100ns: u128 = (microseconds as u128) * 10;
let mut ticks = ticks_100ns / metronome.tick_period as u128;
while ticks > u32::MAX as u128 {
let status = (metronome.wait_for_tick)(metronome_ptr, u32::MAX);
if status.is_error() {
log::warn!("metronome.wait_for_tick returned unexpected error {status:#x?}");
}
ticks -= u32::MAX as u128;
}
if ticks != 0 {
let status = (metronome.wait_for_tick)(metronome_ptr, ticks as u32);
if status.is_error() {
log::warn!("metronome.wait_for_tick returned unexpected error {status:#x?}");
}
}
efi::Status::SUCCESS
} else {
efi::Status::NOT_READY }
}
extern "efiapi" fn set_watchdog_timer(
timeout: usize,
_watchdog_code: u64,
_data_size: usize,
_data: *mut efi::Char16,
) -> efi::Status {
const WATCHDOG_TIMER_CALIBRATE_PER_SECOND: u64 = 10000000;
if let Some(watchdog_ptr) = WATCHDOG_ARCH_PTR.get() {
let watchdog = unsafe { watchdog_ptr.as_mut().unwrap() };
let timeout = (timeout as u64).saturating_mul(WATCHDOG_TIMER_CALIBRATE_PER_SECOND);
let status = (watchdog.set_timer_period)(watchdog_ptr, timeout);
if status.is_error() {
return efi::Status::DEVICE_ERROR;
}
efi::Status::SUCCESS
} else {
efi::Status::NOT_READY
}
}
#[coverage(off)]
extern "efiapi" fn metronome_arch_available(event: efi::Event, _context: *mut c_void) {
match PROTOCOL_DB.locate_protocol(protocols::metronome::PROTOCOL_GUID) {
Ok(metronome_arch_ptr) => {
assert!(!metronome_arch_ptr.is_null(), "Located metronome protocol pointer is null.");
unsafe { METRONOME_ARCH_PTR.init(metronome_arch_ptr) };
if let Err(status_err) = EVENT_DB.close_event(event) {
log::warn!("Could not close event for metronome_arch_available due to error {status_err:?}");
}
}
Err(err) => panic!("Unable to retrieve metronome arch: {err:?}"),
}
}
#[coverage(off)]
extern "efiapi" fn watchdog_arch_available(event: efi::Event, _context: *mut c_void) {
match PROTOCOL_DB.locate_protocol(protocols::watchdog::PROTOCOL_GUID) {
Ok(watchdog_arch_ptr) => {
assert!(!watchdog_arch_ptr.is_null(), "Located watchdog protocol pointer is null.");
unsafe { WATCHDOG_ARCH_PTR.init(watchdog_arch_ptr) };
if let Err(status_err) = EVENT_DB.close_event(event) {
log::warn!("Could not close event for watchdog_arch_available due to error {status_err:?}");
}
}
Err(err) => panic!("Unable to retrieve watchdog arch: {err:?}"),
}
}
pub extern "efiapi" fn exit_boot_services(_handle: efi::Handle, map_key: usize) -> efi::Status {
static EXIT_BOOT_SERVICES_CALLED: Once<()> = Once::new();
log::info!("EBS initiated.");
if !EXIT_BOOT_SERVICES_CALLED.is_completed() {
EVENT_DB.signal_group(PRE_EBS_GUID);
EVENT_DB.signal_group(efi::EVENT_GROUP_BEFORE_EXIT_BOOT_SERVICES);
EXIT_BOOT_SERVICES_CALLED.call_once(|| ());
}
match PROTOCOL_DB.locate_protocol(protocols::timer::PROTOCOL_GUID) {
Ok(timer_arch_ptr) => {
let timer_arch_ptr = timer_arch_ptr as *mut protocols::timer::Protocol;
let timer_arch = unsafe { &*(timer_arch_ptr) };
(timer_arch.set_timer_period)(timer_arch_ptr, 0);
}
Err(err) => log::error!("Unable to locate timer arch: {err:?}"),
};
GCD.lock_memory_space();
match terminate_memory_map(map_key) {
Ok(_) => (),
Err(err) => {
log::error!("Failed to terminate memory map: {err:?}");
GCD.unlock_memory_space();
EVENT_DB.signal_group(guids::EBS_FAILED);
return err.into();
}
}
EVENT_DB.signal_group(efi::EVENT_GROUP_EXIT_BOOT_SERVICES);
match PROTOCOL_DB.locate_protocol(protocols::status_code::PROTOCOL_GUID) {
Ok(status_code_ptr) => {
let status_code_ptr = status_code_ptr as *mut protocols::status_code::Protocol;
let status_code_protocol = unsafe { &*(status_code_ptr) };
(status_code_protocol.report_status_code)(
status_code::EFI_PROGRESS_CODE,
status_code::EFI_SOFTWARE_EFI_BOOT_SERVICE | status_code::EFI_SW_BS_PC_EXIT_BOOT_SERVICES,
0,
&guids::DXE_CORE,
core::ptr::null(),
);
}
Err(err) => log::error!("Unable to locate status code runtime protocol: {err:?}"),
};
interrupts::disable_interrupts();
unsafe {
SYSTEM_TABLE
.lock()
.as_mut()
.expect("The System Table pointer is null. This is invalid.")
.clear_boot_time_services();
}
match PROTOCOL_DB.locate_protocol(protocols::runtime::PROTOCOL_GUID) {
Ok(rt_arch_ptr) => {
let rt_arch_ptr = rt_arch_ptr as *mut protocols::runtime::Protocol;
let rt_arch_protocol = unsafe { &mut *(rt_arch_ptr) };
rt_arch_protocol.at_runtime.store(true, Ordering::SeqCst);
}
Err(err) => log::error!("Unable to locate runtime architectural protocol: {err:?}"),
};
crate::runtime::finalize_runtime_support();
log::info!("EBS completed successfully.");
efi::Status::SUCCESS
}
pub fn init_misc_boot_services_support(st: &mut EfiSystemTable) {
let mut bs = st.boot_services().get();
bs.calculate_crc32 = calculate_crc32;
bs.exit_boot_services = exit_boot_services;
bs.stall = stall;
bs.set_watchdog_timer = set_watchdog_timer;
st.boot_services().set(bs);
let event = EVENT_DB
.create_event(efi::EVT_NOTIFY_SIGNAL, efi::TPL_CALLBACK, Some(metronome_arch_available), None, None)
.expect("Failed to create metronome available callback.");
PROTOCOL_DB
.register_protocol_notify(protocols::metronome::PROTOCOL_GUID, event)
.expect("Failed to register protocol notify on metronome available.");
let event = EVENT_DB
.create_event(efi::EVT_NOTIFY_SIGNAL, efi::TPL_CALLBACK, Some(watchdog_arch_available), None, None)
.expect("Failed to create watchdog available callback.");
PROTOCOL_DB
.register_protocol_notify(protocols::watchdog::PROTOCOL_GUID, event)
.expect("Failed to register protocol notify on metronome available.");
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use super::*;
use crate::{
systemtables::{self, EfiSystemTable},
test_support,
};
use core::{ffi::c_void, ptr};
use patina::pi::protocols::watchdog;
use r_efi::efi;
fn with_locked_state<F>(f: F)
where
F: Fn(&mut EfiSystemTable) + std::panic::RefUnwindSafe,
{
test_support::with_global_lock(|| {
test_support::init_test_logger();
unsafe {
crate::test_support::init_test_gcd(None);
crate::test_support::init_test_protocol_db();
}
crate::systemtables::init_system_table();
let _guard = test_support::StateGuard::new(|| {
unsafe {
crate::GCD.reset();
crate::PROTOCOL_DB.reset();
}
});
let mut st_guard = systemtables::SYSTEM_TABLE.lock();
let st = st_guard.as_mut().expect("System Table not initialized!");
f(st);
})
.unwrap();
}
#[test]
fn test_init_misc_boot_services_support() {
with_locked_state(|st| {
init_misc_boot_services_support(st);
});
}
#[test]
fn test_misc_calc_crc32() {
with_locked_state(|st| {
init_misc_boot_services_support(st);
static BUFFER: [u8; 16] = [0; 16];
let mut data_crc: u32 = 0;
let status = (st.boot_services().get().calculate_crc32)(
BUFFER.as_ptr() as *mut c_void,
BUFFER.len(),
&mut data_crc as *mut u32,
);
if status == efi::Status::SUCCESS {
let expected_crc = crc32fast::hash(&BUFFER);
if data_crc == expected_crc {
log::debug!("CRC32 calculation successful: {data_crc:#x}");
} else {
log::warn!("CRC32 mismatch: got {data_crc:#x}, expected {expected_crc:#x}");
}
} else {
log::warn!("CRC32 calculation failed with status: {status:#x?}");
}
let status = (st.boot_services().get().calculate_crc32)(
BUFFER.as_ptr() as *mut c_void,
0,
&mut data_crc as *mut u32,
);
if status == efi::Status::INVALID_PARAMETER {
log::debug!("Zero data size correctly returned INVALID_PARAMETER");
} else {
log::warn!("Zero data size returned unexpected status: {status:#x?}");
}
let status = (st.boot_services().get().calculate_crc32)(
core::ptr::null_mut(),
BUFFER.len(),
&mut data_crc as *mut u32,
);
if status == efi::Status::INVALID_PARAMETER {
log::debug!("Null data pointer correctly returned INVALID_PARAMETER");
} else {
log::warn!("Null data pointer returned unexpected status: {status:#x?}");
}
let status = (st.boot_services().get().calculate_crc32)(
BUFFER.as_ptr() as *mut c_void,
BUFFER.len(),
core::ptr::null_mut(),
);
if status == efi::Status::INVALID_PARAMETER {
log::debug!("Null output pointer correctly returned INVALID_PARAMETER");
} else {
log::warn!("Null output pointer returned unexpected status: {status:#x?}");
}
});
}
#[test]
fn test_misc_watchdog_timer() {
with_locked_state(|st| {
init_misc_boot_services_support(st);
let status = (st.boot_services().get().set_watchdog_timer)(300, 0, 0, ptr::null_mut());
if status == efi::Status::NOT_READY {
log::debug!("Set watchdog timer correctly returned NOT_READY (no watchdog protocol)");
} else {
log::warn!("Set watchdog timer returned unexpected status: {status:#x?}");
}
let status = (st.boot_services().get().set_watchdog_timer)(0, 0, 0, ptr::null_mut());
if status == efi::Status::NOT_READY {
log::debug!("Disable watchdog timer correctly returned NOT_READY");
} else {
log::warn!("Disable watchdog timer returned unexpected status: {status:#x?}");
}
let data: [efi::Char16; 6] = [b'H' as u16, b'e' as u16, b'l' as u16, b'l' as u16, b'o' as u16, 0];
let data_ptr = data.as_ptr() as *mut efi::Char16;
let status = (st.boot_services().get().set_watchdog_timer)(300, 0, data.len(), data_ptr);
if status == efi::Status::NOT_READY {
log::debug!("Set watchdog timer with data correctly returned NOT_READY");
} else {
log::warn!("Set watchdog timer with data returned unexpected status: {status:#x?}");
}
let status = (st.boot_services().get().set_watchdog_timer)(0, 0, data.len(), data_ptr);
if status == efi::Status::NOT_READY {
log::debug!("Disable watchdog timer with data correctly returned NOT_READY");
} else {
log::warn!("Disable watchdog timer with data returned unexpected status: {status:#x?}");
}
static SET_PERIOD_CALLED: Once<()> = Once::new();
extern "efiapi" fn register_handler(
_this: *const patina::pi::protocols::watchdog::Protocol,
_notify: watchdog::WatchdogTimerNotify,
) -> efi::Status {
unimplemented!()
}
extern "efiapi" fn set_timer_period(
_this: *const patina::pi::protocols::watchdog::Protocol,
_period: u64,
) -> efi::Status {
SET_PERIOD_CALLED.call_once(|| {
log::debug!("Mock set_timer_period called.");
});
efi::Status::SUCCESS
}
extern "efiapi" fn get_timer_period(
_this: *const patina::pi::protocols::watchdog::Protocol,
_period: *mut u64,
) -> efi::Status {
unimplemented!()
}
let watchdog = protocols::watchdog::Protocol { register_handler, set_timer_period, get_timer_period };
unsafe {
WATCHDOG_ARCH_PTR.init(&watchdog as *const _ as *mut c_void);
};
let status = (st.boot_services().get().set_watchdog_timer)(300, 0, 0, ptr::null_mut());
if status == efi::Status::SUCCESS {
log::debug!("Set watchdog timer correctly returned SUCCESS (watchdog protocol available)");
assert!(SET_PERIOD_CALLED.is_completed(), "set_timer_period was not called during set_watchdog_timer.");
} else {
log::warn!("Set watchdog timer returned unexpected status: {status:#x?}");
}
});
}
#[test]
fn test_misc_stall() {
with_locked_state(|st| {
init_misc_boot_services_support(st);
let status = (st.boot_services().get().stall)(10000);
if status == efi::Status::NOT_READY {
log::debug!("Stall function correctly returned NOT_READY (no metronome protocol)");
} else {
log::warn!("Stall function returned unexpected status: {status:#x?}");
}
let status = (st.boot_services().get().stall)(0);
if status == efi::Status::NOT_READY {
log::debug!("Zero stall correctly returned NOT_READY");
} else {
log::warn!("Zero stall returned unexpected status: {status:#x?}");
}
let status = (st.boot_services().get().stall)(usize::MAX);
if status == efi::Status::NOT_READY {
log::debug!("Maximum stall correctly returned NOT_READY");
} else {
log::warn!("Maximum stall returned unexpected status: {status:#x?}");
}
static WAIT_FOR_TICK_CALLED: Once<()> = Once::new();
extern "efiapi" fn wait_for_tick(
_this: *const patina::pi::protocols::metronome::Protocol,
_tick: u32,
) -> efi::Status {
WAIT_FOR_TICK_CALLED.call_once(|| {
log::debug!("Mock wait_for_tick called.");
});
efi::Status::SUCCESS
}
let metronome = protocols::metronome::Protocol {
tick_period: 10000, wait_for_tick,
};
unsafe {
METRONOME_ARCH_PTR.init(&metronome as *const _ as *mut c_void);
}
let status = (st.boot_services().get().stall)(10000);
if status == efi::Status::SUCCESS {
log::debug!("Stall function correctly returned SUCCESS (metronome protocol available)");
assert!(WAIT_FOR_TICK_CALLED.is_completed(), "wait_for_tick was not called during stall.");
} else {
log::warn!("Stall function returned unexpected status: {status:#x?}");
}
});
}
#[test]
fn test_misc_exit_boot_services() {
with_locked_state(|st| {
let valid_map_key: usize = 0x2000;
init_misc_boot_services_support(st);
let handle: efi::Handle = 0x1000 as efi::Handle; let _status = (st.boot_services().get().exit_boot_services)(handle, valid_map_key);
});
}
}