#![allow(unused)]
use crate::{dxe_services, protocols::PROTOCOL_DB};
use alloc::boxed::Box;
use core::ffi::c_void;
use patina::{
arch,
boot_services::{BootServices, StandardBootServices},
component::{
Storage, component,
service::{IntoService, Service},
},
error::{EfiError, Result},
uefi_protocol::ProtocolInterface,
};
use patina_internal_cpu::interrupts::{self, ExceptionType, HandlerType, InterruptManager, Interrupts};
use r_efi::efi;
use core::sync::atomic::{AtomicU64, Ordering};
use patina::pi::protocols::cpu_arch::{CpuFlushType, CpuInitType, InterruptHandler, PROTOCOL_GUID, Protocol};
static TIMER_PERIOD: AtomicU64 = AtomicU64::new(0);
#[derive(IntoService)]
#[service(dyn InterruptManager)]
pub(crate) struct DxeInterruptManager(pub(crate) Interrupts);
impl InterruptManager for DxeInterruptManager {
fn register_exception_handler(&self, exception_type: ExceptionType, handler: HandlerType) -> Result<()> {
self.0.register_exception_handler(exception_type, handler)
}
fn unregister_exception_handler(&self, exception_type: ExceptionType) -> Result<()> {
self.0.unregister_exception_handler(exception_type)
}
}
#[repr(C)]
struct EfiCpuArchProtocolImpl {
protocol: Protocol,
pub(crate) interrupt_manager: Service<dyn InterruptManager>,
}
unsafe impl ProtocolInterface for EfiCpuArchProtocolImpl {
const PROTOCOL_GUID: patina::BinaryGuid = PROTOCOL_GUID;
}
fn get_impl_ref<'a>(this: *const Protocol) -> Option<&'a EfiCpuArchProtocolImpl> {
if this.is_null() {
return None;
}
Some(unsafe { &*(this as *const EfiCpuArchProtocolImpl) })
}
fn get_impl_ref_mut<'a>(this: *mut Protocol) -> Option<&'a mut EfiCpuArchProtocolImpl> {
if this.is_null() {
return None;
}
Some(unsafe { &mut *(this as *mut EfiCpuArchProtocolImpl) })
}
#[cfg_attr(coverage, coverage(off))]
extern "efiapi" fn flush_data_cache(
this: *const Protocol,
start: efi::PhysicalAddress,
length: u64,
flush_type: CpuFlushType,
) -> efi::Status {
if this.is_null() {
return efi::Status::INVALID_PARAMETER;
}
patina::arch::flush_data_cache(start, length, flush_type)
.map(|_| efi::Status::SUCCESS)
.unwrap_or_else(|err| err.into())
}
extern "efiapi" fn enable_interrupt(this: *const Protocol) -> efi::Status {
arch::enable_interrupts();
efi::Status::SUCCESS
}
extern "efiapi" fn disable_interrupt(this: *const Protocol) -> efi::Status {
arch::disable_interrupts();
efi::Status::SUCCESS
}
extern "efiapi" fn get_interrupt_state(this: *const Protocol, state: *mut bool) -> efi::Status {
if state.is_null() {
return efi::Status::INVALID_PARAMETER;
}
unsafe {
state.write_unaligned(arch::interrupts_enabled());
}
efi::Status::SUCCESS
}
extern "efiapi" fn init(this: *const Protocol, init_type: CpuInitType) -> efi::Status {
if this.is_null() {
return efi::Status::INVALID_PARAMETER;
}
efi::Status::UNSUPPORTED
}
extern "efiapi" fn register_interrupt_handler(
this: *const Protocol,
interrupt_type: isize,
interrupt_handler: InterruptHandler,
) -> efi::Status {
let Some(impl_ref) = get_impl_ref(this) else {
return efi::Status::INVALID_PARAMETER;
};
let interrupt_manager = &impl_ref.interrupt_manager;
let const_fn_ptr = interrupt_handler as *const ();
let result = if const_fn_ptr.is_null() {
interrupt_manager.unregister_exception_handler(interrupt_type as ExceptionType)
} else {
interrupt_manager
.register_exception_handler(interrupt_type as ExceptionType, HandlerType::UefiRoutine(interrupt_handler))
};
match result {
Ok(()) => efi::Status::SUCCESS,
Err(err) => err.into(),
}
}
fn get_cpu_timer_value(timer_index: u32) -> Result<(u64, u64)> {
if timer_index != 0 {
return Err(EfiError::InvalidParameter);
}
let value = patina::arch::get_timer_value();
let cached = TIMER_PERIOD.load(Ordering::Relaxed);
if cached != 0 {
return Ok((value, cached));
}
let period = match patina::arch::get_timer_frequency() {
Some(frequency) => 10_000_000 / frequency.get(),
None => 0,
};
TIMER_PERIOD.store(period, Ordering::Relaxed);
Ok((value, period))
}
extern "efiapi" fn get_timer_value(
this: *const Protocol,
timer_index: u32,
timer_value: *mut u64,
timer_period: *mut u64,
) -> efi::Status {
if timer_value.is_null() || timer_period.is_null() {
return efi::Status::INVALID_PARAMETER;
}
if this.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let result = get_cpu_timer_value(timer_index);
match result {
Ok((value, period)) => {
unsafe {
timer_value.write_unaligned(value);
timer_period.write_unaligned(period);
}
efi::Status::SUCCESS
}
Err(err) => err.into(),
}
}
extern "efiapi" fn set_memory_attributes(
_this: *const Protocol,
base_address: efi::PhysicalAddress,
length: u64,
attributes: u64,
) -> efi::Status {
match dxe_services::core_set_memory_space_attributes(base_address, length, attributes) {
Ok(_) => efi::Status::SUCCESS,
Err(status) => status.into(),
}
}
impl EfiCpuArchProtocolImpl {
fn new(interrupt_manager: Service<dyn InterruptManager>) -> Self {
Self {
protocol: Protocol {
flush_data_cache,
enable_interrupt,
disable_interrupt,
get_interrupt_state,
init,
register_interrupt_handler,
get_timer_value,
set_memory_attributes,
number_of_timers: 0,
dma_buffer_alignment: patina::arch::cache_writeback_granule(),
},
interrupt_manager,
}
}
}
#[derive(Default)]
pub(crate) struct CpuArchProtocolInstaller;
#[component]
impl CpuArchProtocolInstaller {
fn entry_point(self, interrupt_manager: Service<dyn InterruptManager>, bs: StandardBootServices) -> Result<()> {
let protocol = EfiCpuArchProtocolImpl::new(interrupt_manager);
let interface = Box::leak(Box::new(protocol));
bs.install_protocol_interface(None, interface)
.inspect_err(|_| log::error!("Failed to install EFI_CPU_ARCH_PROTOCOL"))?;
log::info!("installed EFI_CPU_ARCH_PROTOCOL_GUID");
Ok(())
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use crate::test_support;
use super::*;
use mockall::{mock, predicate::*};
use patina::pi::protocols::cpu_arch::{EfiExceptionType, EfiSystemContext};
mock! {
InterruptManager {}
impl InterruptManager for InterruptManager {
fn register_exception_handler(
&self,
interrupt_type: ExceptionType,
handler: HandlerType,
) -> Result<()>;
fn unregister_exception_handler(&self, interrupt_type: ExceptionType) -> Result<()>;
}
}
fn with_locked_state<F: Fn() + std::panic::RefUnwindSafe>(f: F) {
crate::test_support::with_global_lock(|| {
test_support::init_test_logger();
f();
})
.unwrap();
}
#[test]
fn test_enable_interrupt() {
with_locked_state(|| {
let im: Service<dyn InterruptManager> = Service::mock(Box::new(MockInterruptManager::new()));
let protocol = EfiCpuArchProtocolImpl::new(im);
let status = enable_interrupt(&protocol.protocol);
assert_eq!(status, efi::Status::SUCCESS);
});
}
#[test]
fn test_disable_interrupt() {
with_locked_state(|| {
let im: Service<dyn InterruptManager> = Service::mock(Box::new(MockInterruptManager::new()));
let protocol = EfiCpuArchProtocolImpl::new(im);
let status = disable_interrupt(&protocol.protocol);
assert_eq!(status, efi::Status::SUCCESS);
});
}
#[test]
fn test_get_interrupt_state() {
with_locked_state(|| {
let im: Service<dyn InterruptManager> = Service::mock(Box::new(MockInterruptManager::new()));
let protocol = EfiCpuArchProtocolImpl::new(im);
let mut state = false;
let status = get_interrupt_state(&protocol.protocol, &mut state as *mut bool);
assert_eq!(status, efi::Status::SUCCESS);
});
}
extern "efiapi" fn mock_interrupt_handler(_type: EfiExceptionType, _context: EfiSystemContext) {}
#[test]
fn test_register_interrupt_handler() {
with_locked_state(|| {
let mut interrupt_manager = MockInterruptManager::new();
interrupt_manager
.expect_register_exception_handler()
.with(eq(ExceptionType::from(0_usize)), always())
.returning(|_, _| Ok(()));
let im: Service<dyn InterruptManager> = Service::mock(Box::new(interrupt_manager));
let protocol = EfiCpuArchProtocolImpl::new(im);
let status = register_interrupt_handler(&protocol.protocol, 0, mock_interrupt_handler);
assert_eq!(status, efi::Status::SUCCESS);
let status = register_interrupt_handler(core::ptr::null(), 0, mock_interrupt_handler);
assert_eq!(status, efi::Status::INVALID_PARAMETER);
});
}
#[test]
fn test_get_timer_value() {
with_locked_state(|| {
let im: Service<dyn InterruptManager> = Service::mock(Box::new(MockInterruptManager::new()));
let protocol = EfiCpuArchProtocolImpl::new(im);
let mut timer_value: u64 = 0;
let mut timer_period: u64 = 0;
let status =
get_timer_value(&protocol.protocol, 0, &mut timer_value as *mut _, &mut timer_period as *mut _);
assert_eq!(status, efi::Status::SUCCESS);
let status = get_timer_value(core::ptr::null(), 0, &mut timer_value as *mut _, &mut timer_period as *mut _);
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let status = get_timer_value(&protocol.protocol, 0, core::ptr::null_mut(), &mut timer_period as *mut _);
assert_eq!(status, efi::Status::INVALID_PARAMETER);
let status = get_timer_value(&protocol.protocol, 0, &mut timer_value as *mut _, core::ptr::null_mut());
assert_eq!(status, efi::Status::INVALID_PARAMETER);
});
}
#[test]
fn test_get_cpu_timer_value() {
with_locked_state(|| {
TIMER_PERIOD.store(0, Ordering::Relaxed);
assert!(matches!(get_cpu_timer_value(1), Err(EfiError::InvalidParameter)));
assert_eq!(get_cpu_timer_value(0).unwrap(), (0, 0));
TIMER_PERIOD.store(1234, Ordering::Relaxed);
assert_eq!(get_cpu_timer_value(0).unwrap(), (0, 1234));
TIMER_PERIOD.store(0, Ordering::Relaxed);
});
}
#[test]
fn test_dxe_interrupt_manager_register_then_unregister_delegates() {
with_locked_state(|| {
let dxe_interrupt_manager = DxeInterruptManager(Interrupts::default());
let result = dxe_interrupt_manager.register_exception_handler(
ExceptionType::from(0_usize),
HandlerType::UefiRoutine(mock_interrupt_handler),
);
assert!(result.is_ok());
let result = dxe_interrupt_manager.unregister_exception_handler(ExceptionType::from(0_usize));
assert!(result.is_ok());
});
}
#[test]
fn test_dxe_interrupt_manager_unregister_then_register_delegates() {
with_locked_state(|| {
let dxe_interrupt_manager = DxeInterruptManager(Interrupts::default());
let result = dxe_interrupt_manager.unregister_exception_handler(ExceptionType::from(0_usize));
assert!(result.is_err());
let result = dxe_interrupt_manager.register_exception_handler(
ExceptionType::from(0_usize),
HandlerType::UefiRoutine(mock_interrupt_handler),
);
assert!(result.is_ok());
let result = dxe_interrupt_manager.unregister_exception_handler(ExceptionType::from(0_usize));
assert!(result.is_ok());
});
}
#[test]
fn test_get_impl_ref_null_returns_none() {
assert!(get_impl_ref(core::ptr::null()).is_none());
}
#[test]
fn test_get_impl_ref_returns_some_for_valid_pointer() {
with_locked_state(|| {
let im: Service<dyn InterruptManager> = Service::mock(Box::new(MockInterruptManager::new()));
let protocol = EfiCpuArchProtocolImpl::new(im);
let this = &raw const protocol.protocol;
let impl_ref = get_impl_ref(this).expect("non-null pointer should yield Some");
assert_eq!(&raw const impl_ref.protocol, this);
});
}
#[test]
fn test_get_impl_ref_mut_null_returns_none() {
assert!(get_impl_ref_mut(core::ptr::null_mut()).is_none());
}
#[test]
fn test_get_impl_ref_mut_returns_some_for_valid_pointer() {
with_locked_state(|| {
let im: Service<dyn InterruptManager> = Service::mock(Box::new(MockInterruptManager::new()));
let mut protocol = EfiCpuArchProtocolImpl::new(im);
let this = &raw mut protocol.protocol;
let impl_ref = get_impl_ref_mut(this).expect("non-null pointer should yield Some");
assert_eq!(&raw const impl_ref.protocol, this.cast_const());
});
}
}