use core::mem;
use alloc::boxed::Box;
use log::{debug, error, trace, warn};
use uefi_raw::protocol::device_path::DevicePathProtocol;
use uefi_raw::protocol::driver::DriverBindingProtocol;
use crate::mem::memory_map::MemoryType;
use crate::proto::device_path::DevicePath;
use crate::proto::loaded_image::LoadedImage;
use crate::{Handle, Result, ResultExt, Status, boot};
pub trait Driver {
fn supported(
&mut self,
agent: Handle,
controller: Handle,
remaining: Option<&DevicePath>,
) -> Result;
fn start(
&mut self,
agent: Handle,
controller: Handle,
remaining: Option<&DevicePath>,
) -> Result;
fn stop(&mut self, agent: Handle, controller: Handle) -> Result;
}
struct DriverBindingInterface<T> {
protocol: DriverBindingProtocol,
driver: T,
}
impl<T> DriverBindingInterface<T> {
unsafe fn from_proto_ptr_mut<'a>(ptr: *mut DriverBindingProtocol) -> &'a mut Self {
let ptr = ptr
.cast::<u8>()
.wrapping_sub(mem::offset_of!(Self, protocol))
.cast::<Self>();
unsafe { &mut *ptr }
}
}
unsafe extern "efiapi" fn driver_supported<T: Driver>(
this: *const DriverBindingProtocol,
controller: uefi_raw::Handle,
remaining: *const DevicePathProtocol,
) -> Status {
if this.is_null() || controller.is_null() {
return Status::INVALID_PARAMETER;
}
let this = unsafe { DriverBindingInterface::<T>::from_proto_ptr_mut(this.cast_mut()) };
let agent = unsafe { Handle::from_ptr(this.protocol.driver_binding_handle).unwrap() };
let controller = unsafe { Handle::from_ptr(controller).unwrap() };
let remaining = if remaining.is_null() {
None
} else {
Some(unsafe { DevicePath::from_ffi_ptr(remaining.cast()) })
};
this.driver.supported(agent, controller, remaining).status()
}
unsafe extern "efiapi" fn driver_start<T: Driver>(
this: *const DriverBindingProtocol,
controller: uefi_raw::Handle,
remaining: *const DevicePathProtocol,
) -> Status {
trace!("this: {this:p}, controller: {controller:p}, remaining: {remaining:p}");
if this.is_null() || controller.is_null() {
return Status::INVALID_PARAMETER;
}
let this = unsafe { DriverBindingInterface::<T>::from_proto_ptr_mut(this.cast_mut()) };
let agent = unsafe { Handle::from_ptr(this.protocol.driver_binding_handle).unwrap() };
let controller = unsafe { Handle::from_ptr(controller).unwrap() };
let remaining = if remaining.is_null() {
None
} else {
Some(unsafe { DevicePath::from_ffi_ptr(remaining.cast()) })
};
this.driver.start(agent, controller, remaining).status()
}
unsafe extern "efiapi" fn driver_stop<T: Driver>(
this: *const DriverBindingProtocol,
controller: uefi_raw::Handle,
number_of_children: usize,
child_handle_buffer: *const uefi_raw::Handle,
) -> Status {
trace!(
"this: {this:p}, controller: {controller:p}, number_of_children: {number_of_children}, child_handle_buffer: {child_handle_buffer:p}"
);
if this.is_null() || controller.is_null() {
return Status::INVALID_PARAMETER;
}
let this = unsafe { DriverBindingInterface::<T>::from_proto_ptr_mut(this.cast_mut()) };
let agent = unsafe { Handle::from_ptr(this.protocol.driver_binding_handle).unwrap() };
let controller = unsafe { Handle::from_ptr(controller).unwrap() };
if number_of_children == 0 {
this.driver.stop(agent, controller).status()
} else {
warn!("stop with children not currently supported");
Status::UNSUPPORTED
}
}
pub fn install<T: Driver>(driver: T, handle: Option<Handle>) -> Result {
trace!("handle: {handle:?}");
let image_handle = boot::image_handle();
let target_handle = handle.unwrap_or(image_handle);
{
let loaded_image = boot::open_protocol_exclusive::<LoadedImage>(image_handle)?;
if loaded_image.code_type() != MemoryType::BOOT_SERVICES_CODE
|| loaded_image.data_type() != MemoryType::BOOT_SERVICES_DATA
{
error!("current image was not loaded as an EFI boot service driver");
return Err(Status::UNSUPPORTED.into());
}
}
let mut ctx = Box::new(DriverBindingInterface {
protocol: DriverBindingProtocol {
supported: driver_supported::<T>,
start: driver_start::<T>,
stop: driver_stop::<T>,
version: 1,
image_handle: image_handle.as_ptr(),
driver_binding_handle: target_handle.as_ptr(),
},
driver,
});
let proto_ptr = &raw mut ctx.protocol;
trace!("proto_ptr: {proto_ptr:p}");
debug!("installing driver binding protocol");
unsafe {
boot::install_protocol_interface(
Some(target_handle),
&DriverBindingProtocol::GUID,
proto_ptr.cast(),
)?;
}
Box::leak(ctx);
Ok(())
}