#[cfg(any(test, feature = "mockall"))]
use mockall::automock;
use alloc::boxed::Box;
use core::{
mem::{self, ManuallyDrop},
ptr::NonNull,
};
use r_efi::{
efi,
protocols::{device_path::Protocol as EfiDevicePathProtocol, driver_binding::Protocol as EfiDriverBindingProtocol},
};
use crate::boot_services::{
BootServices,
c_ptr::{CPtr, PtrMetadata},
};
#[cfg_attr(any(test, feature = "mockall"), automock)]
pub trait DriverBinding {
fn driver_binding_supported<T: BootServices + 'static>(
&self,
boot_services: &'static T,
controller: efi::Handle,
remaining_device_path: Option<NonNull<EfiDevicePathProtocol>>,
) -> Result<bool, efi::Status>;
fn driver_binding_start<T: BootServices + 'static>(
&mut self,
boot_services: &'static T,
controller: efi::Handle,
remaining_device_path: Option<NonNull<EfiDevicePathProtocol>>,
) -> Result<(), efi::Status>;
fn driver_binding_stop<T: BootServices + 'static>(
&mut self,
boot_services: &'static T,
controller: efi::Handle,
number_of_children: usize,
child_handle_buffer: Option<NonNull<efi::Handle>>,
) -> Result<(), efi::Status>;
}
#[repr(C)]
pub struct _UefiDriverBinding<T, U>
where
T: DriverBinding + 'static,
U: BootServices + 'static,
{
driver_binding_protocol: EfiDriverBindingProtocol,
driver_binding: T,
boot_services: &'static U,
}
impl<T, U> _UefiDriverBinding<T, U>
where
T: DriverBinding + 'static,
U: BootServices + 'static,
{
const fn new(
image_handle: efi::Handle,
driver_binding_handle: efi::Handle,
driver_binding: T,
boot_services: &'static U,
) -> Self {
Self {
driver_binding_protocol: EfiDriverBindingProtocol {
supported: Self::efi_driver_binding_supported,
start: Self::efi_driver_binding_start,
stop: Self::efi_driver_binding_stop,
version: 1,
image_handle,
driver_binding_handle,
},
driver_binding,
boot_services,
}
}
extern "efiapi" fn efi_driver_binding_supported(
this: *mut EfiDriverBindingProtocol,
controller_handle: efi::Handle,
remaining_device_path: *mut EfiDevicePathProtocol,
) -> efi::Status {
let this = unsafe {
let Some(this) = (this as *mut _UefiDriverBinding<T, U>).as_mut() else {
return efi::Status::INVALID_PARAMETER;
};
this
};
match this.driver_binding.driver_binding_supported(
this.boot_services,
controller_handle,
NonNull::new(remaining_device_path),
) {
Ok(true) => efi::Status::SUCCESS,
Ok(false) => efi::Status::UNSUPPORTED,
Err(status) => status,
}
}
extern "efiapi" fn efi_driver_binding_start(
this: *mut EfiDriverBindingProtocol,
controller_handle: efi::Handle,
remaining_device_path: *mut EfiDevicePathProtocol,
) -> efi::Status {
let this = unsafe {
let Some(this) = (this as *mut _UefiDriverBinding<T, U>).as_mut() else {
return efi::Status::INVALID_PARAMETER;
};
this
};
match this.driver_binding.driver_binding_start(
this.boot_services,
controller_handle,
NonNull::new(remaining_device_path),
) {
Ok(()) => efi::Status::SUCCESS,
Err(status) => status,
}
}
extern "efiapi" fn efi_driver_binding_stop(
this: *mut EfiDriverBindingProtocol,
controller_handle: efi::Handle,
number_of_children: usize,
child_handle_buffer: *mut efi::Handle,
) -> efi::Status {
let this = unsafe {
let Some(this) = (this as *mut _UefiDriverBinding<T, U>).as_mut() else {
return efi::Status::INVALID_PARAMETER;
};
this
};
match this.driver_binding.driver_binding_stop(
this.boot_services,
controller_handle,
number_of_children,
NonNull::new(child_handle_buffer),
) {
Ok(()) => efi::Status::SUCCESS,
Err(status) => status,
}
}
}
pub enum UefiDriverBinding<T, U>
where
T: DriverBinding + 'static,
U: BootServices + 'static,
{
Uninstalled(Box<_UefiDriverBinding<T, U>>),
Installed(PtrMetadata<'static, Box<_UefiDriverBinding<T, U>>>),
}
impl<T: DriverBinding + 'static, U: BootServices + 'static> UefiDriverBinding<T, U> {
pub fn new(driver_binding: T, handle: efi::Handle, boot_services: &'static U) -> Self {
Self::new_with_driver_handle(driver_binding, handle, handle, boot_services)
}
pub fn new_with_driver_handle(
driver_binding: T,
image_handle: efi::Handle,
driver_binding_handle: efi::Handle,
boot_services: &'static U,
) -> Self {
Self::Uninstalled(Box::new(_UefiDriverBinding::new(
image_handle,
driver_binding_handle,
driver_binding,
boot_services,
)))
}
pub fn install(&mut self) -> Result<(), efi::Status> {
let Self::Uninstalled(uefi_driver_binding) = self else {
return Ok(());
};
unsafe {
uefi_driver_binding.boot_services.install_protocol_interface_unchecked(
Some(uefi_driver_binding.driver_binding_protocol.driver_binding_handle),
&efi::protocols::driver_binding::PROTOCOL_GUID,
<Box<_> as CPtr>::as_ptr(uefi_driver_binding) as *mut _,
)
}?;
let metadata = Box::metadata(uefi_driver_binding);
match mem::replace(self, Self::Installed(metadata)) {
UefiDriverBinding::Uninstalled(uefi_driver_binding) => _ = Box::leak(uefi_driver_binding),
UefiDriverBinding::Installed(_) => (),
}
Ok(())
}
pub fn uninstall(&mut self) -> Result<(), efi::Status> {
let Self::Installed(ptr_metadata) = self else {
return Ok(());
};
let uefi_driver_binding = unsafe { PtrMetadata::clone(ptr_metadata).try_into_original_ptr() }
.ok_or(efi::Status::INVALID_PARAMETER)
.map(ManuallyDrop::new)?;
unsafe {
uefi_driver_binding.boot_services.uninstall_protocol_interface_unchecked(
uefi_driver_binding.driver_binding_protocol.driver_binding_handle,
&efi::protocols::driver_binding::PROTOCOL_GUID,
uefi_driver_binding.as_ptr() as *mut _,
)?;
}
*self = Self::Uninstalled(ManuallyDrop::into_inner(uefi_driver_binding));
Ok(())
}
pub fn is_installed(&self) -> bool {
match self {
UefiDriverBinding::Uninstalled(_) => false,
UefiDriverBinding::Installed(_) => true,
}
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use core::{
mem::MaybeUninit,
ptr,
sync::atomic::{AtomicBool, Ordering},
};
use crate::boot_services::MockBootServices;
use super::*;
#[test]
fn test_install_driver_binding() {
const TEST_HANDLE: efi::Handle = 12345_usize as efi::Handle;
static mut BOOT_SERVICES_INIT: MaybeUninit<MockBootServices> = MaybeUninit::uninit();
unsafe {
let mut mock_boot_services = MockBootServices::new();
mock_boot_services
.expect_install_protocol_interface_unchecked()
.once()
.withf_st(|handle, protocol, interface| {
assert_eq!(&Some(TEST_HANDLE), handle);
assert_eq!(&efi::protocols::driver_binding::PROTOCOL_GUID, protocol);
let interface = (*interface as *const _UefiDriverBinding<MockDriverBinding, MockBootServices>)
.as_ref()
.unwrap();
assert_eq!(TEST_HANDLE, interface.driver_binding_protocol.image_handle);
assert_eq!(TEST_HANDLE, interface.driver_binding_protocol.driver_binding_handle);
assert_eq!(
BOOT_SERVICES as *const MockBootServices,
interface.boot_services as *const MockBootServices
);
true
})
.return_const_st(Ok(TEST_HANDLE));
ptr::write(BOOT_SERVICES_INIT.as_mut_ptr(), mock_boot_services);
}
static BOOT_SERVICES: &MockBootServices = unsafe { BOOT_SERVICES_INIT.assume_init_ref() };
let driver = MockDriverBinding::new();
let mut uefi_driver_binding = UefiDriverBinding::new(driver, TEST_HANDLE, BOOT_SERVICES);
assert!(!uefi_driver_binding.is_installed());
uefi_driver_binding.install().unwrap();
assert!(uefi_driver_binding.is_installed());
}
#[test]
fn test_install_driver_binding_with_driver_handle() {
const TEST_HANDLE: efi::Handle = 12345_usize as efi::Handle;
const TEST_DRIVER_HANDLE: efi::Handle = 54321_usize as efi::Handle;
static mut BOOT_SERVICES_INIT: MaybeUninit<MockBootServices> = MaybeUninit::uninit();
unsafe {
let mut mock_boot_services = MockBootServices::new();
mock_boot_services
.expect_install_protocol_interface_unchecked()
.once()
.withf_st(|handle, protocol, interface| {
assert_eq!(&Some(TEST_DRIVER_HANDLE), handle);
assert_eq!(&efi::protocols::driver_binding::PROTOCOL_GUID, protocol);
let interface = (*interface as *const _UefiDriverBinding<MockDriverBinding, MockBootServices>)
.as_ref()
.unwrap();
assert_eq!(TEST_HANDLE, interface.driver_binding_protocol.image_handle);
assert_eq!(TEST_DRIVER_HANDLE, interface.driver_binding_protocol.driver_binding_handle);
assert_eq!(
BOOT_SERVICES as *const MockBootServices,
interface.boot_services as *const MockBootServices
);
true
})
.return_const_st(Ok(TEST_DRIVER_HANDLE));
ptr::write(BOOT_SERVICES_INIT.as_mut_ptr(), mock_boot_services);
}
static BOOT_SERVICES: &MockBootServices = unsafe { BOOT_SERVICES_INIT.assume_init_ref() };
let driver = MockDriverBinding::new();
let mut uefi_driver_binding: UefiDriverBinding<MockDriverBinding, MockBootServices> =
UefiDriverBinding::new_with_driver_handle(driver, TEST_HANDLE, TEST_DRIVER_HANDLE, BOOT_SERVICES);
assert!(!uefi_driver_binding.is_installed());
uefi_driver_binding.install().unwrap();
assert!(uefi_driver_binding.is_installed());
}
#[test]
fn test_uninstall_driver_binding() {
const TEST_HANDLE: efi::Handle = 12345_usize as efi::Handle;
const TEST_DRIVER_HANDLE: efi::Handle = 54321_usize as efi::Handle;
static mut BOOT_SERVICES_INIT: MaybeUninit<MockBootServices> = MaybeUninit::uninit();
unsafe {
let mut mock_boot_services = MockBootServices::new();
mock_boot_services.expect_install_protocol_interface_unchecked().once().return_const_st(Ok(TEST_HANDLE));
mock_boot_services
.expect_uninstall_protocol_interface_unchecked()
.once()
.withf(|handle, protocol, interface| {
assert_eq!(&TEST_DRIVER_HANDLE, handle);
assert_eq!(&efi::protocols::driver_binding::PROTOCOL_GUID, protocol);
let interface = (*interface as *const _UefiDriverBinding<MockDriverBinding, MockBootServices>)
.as_ref()
.unwrap();
assert_eq!(TEST_HANDLE, interface.driver_binding_protocol.image_handle);
assert_eq!(TEST_DRIVER_HANDLE, interface.driver_binding_protocol.driver_binding_handle);
assert_eq!(
BOOT_SERVICES as *const MockBootServices,
interface.boot_services as *const MockBootServices
);
true
})
.return_const_st(Ok(()));
ptr::write(BOOT_SERVICES_INIT.as_mut_ptr(), mock_boot_services);
}
static BOOT_SERVICES: &MockBootServices = unsafe { BOOT_SERVICES_INIT.assume_init_ref() };
let driver = MockDriverBinding::new();
let mut uefi_driver_binding: UefiDriverBinding<MockDriverBinding, MockBootServices> =
UefiDriverBinding::new_with_driver_handle(driver, TEST_HANDLE, TEST_DRIVER_HANDLE, BOOT_SERVICES);
uefi_driver_binding.install().unwrap();
assert!(uefi_driver_binding.is_installed());
uefi_driver_binding.uninstall().unwrap();
assert!(!uefi_driver_binding.is_installed());
}
#[test]
fn test_driver_binding_lifetime() {
const TEST_HANDLE: efi::Handle = 12345_usize as efi::Handle;
static mut BOOT_SERVICES_INIT: MaybeUninit<MockBootServices> = MaybeUninit::uninit();
unsafe {
let mut mock_boot_services = MockBootServices::new();
mock_boot_services.expect_install_protocol_interface_unchecked().return_const_st(Ok(TEST_HANDLE));
mock_boot_services.expect_uninstall_protocol_interface_unchecked().return_const_st(Ok(()));
ptr::write(BOOT_SERVICES_INIT.as_mut_ptr(), mock_boot_services);
}
static BOOT_SERVICES: &MockBootServices = unsafe { BOOT_SERVICES_INIT.assume_init_ref() };
struct MyDriverBinding;
impl DriverBinding for MyDriverBinding {
fn driver_binding_supported<T: BootServices + 'static>(
&self,
_boot_services: &'static T,
_controller: efi::Handle,
_remaining_device_path: Option<NonNull<EfiDevicePathProtocol>>,
) -> Result<bool, efi::Status> {
Ok(true)
}
fn driver_binding_start<T: BootServices + 'static>(
&mut self,
_boot_services: &'static T,
_controller: efi::Handle,
_remaining_device_path: Option<NonNull<EfiDevicePathProtocol>>,
) -> Result<(), efi::Status> {
Ok(())
}
fn driver_binding_stop<T: BootServices + 'static>(
&mut self,
_boot_services: &'static T,
_controller: efi::Handle,
_number_of_children: usize,
_child_handle_buffer: Option<NonNull<efi::Handle>>,
) -> Result<(), efi::Status> {
Ok(())
}
}
impl Drop for MyDriverBinding {
fn drop(&mut self) {
MY_DRIVER_BINDING_DROPPED.store(true, Ordering::Relaxed);
}
}
static MY_DRIVER_BINDING_DROPPED: AtomicBool = AtomicBool::new(false);
{
let mut uefi_driver_binding = UefiDriverBinding::new(MyDriverBinding, TEST_HANDLE, BOOT_SERVICES);
uefi_driver_binding.install().unwrap();
}
assert!(!MY_DRIVER_BINDING_DROPPED.load(Ordering::Relaxed));
{
_ = UefiDriverBinding::new(MyDriverBinding, TEST_HANDLE, BOOT_SERVICES);
}
assert!(MY_DRIVER_BINDING_DROPPED.load(Ordering::Relaxed));
}
}