use core::ffi::c_void;
use core::ptr::NonNull;
use thiserror::Error;
use uefi::boot::ScopedProtocol;
use uefi::{guid, prelude::*};
use crate::BootResult;
use crate::error::BootError;
use crate::system::fs::UefiFileSystem;
use crate::system::helper::{get_arch, locate_protocol, normalize_path, str_to_cstr};
use crate::system::protos::DevicetreeFixup;
const DTB_CONF_TABLE: uefi::Guid = guid!("b1b621d5-f19c-41a5-830b-d9152c69aae0");
const EFI_DT_APPLY_FIXUPS: u32 = 0x0000_0001;
const EFI_DT_RESERVE_MEMORY: u32 = 0x0000_0002;
#[derive(Error, Debug)]
pub enum DevicetreeError {
#[error("The DevicetreeGuard was already consumed")]
DevicetreeGuardConsumed,
}
struct Devicetree<'a> {
size: usize,
ptr: NonNull<u8>,
slice: &'a [u8],
}
#[must_use = "Will drop the inner Devicetree if immediately dropped"]
struct DevicetreeGuard<'a> {
devicetree: Option<Devicetree<'a>>,
}
impl Devicetree<'_> {
fn new(content: &[u8], size: Option<usize>) -> BootResult<Self> {
let size = size.unwrap_or(content.len()).min(content.len());
let mut ptr = boot::allocate_pool(boot::MemoryType::ACPI_RECLAIM, size)?;
let slice = unsafe { core::slice::from_raw_parts_mut(ptr.as_mut(), size) };
slice.copy_from_slice(content);
Ok(Self { size, ptr, slice })
}
fn fixup(&mut self, fixup: &mut ScopedProtocol<DevicetreeFixup>) -> BootResult<()> {
Ok(fixup
.fixup(
self.ptr,
&mut self.size,
EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY,
)
.to_result()?)
}
fn install(&self) -> BootResult<()> {
unsafe {
Ok(boot::install_configuration_table(
&DTB_CONF_TABLE,
self.ptr.as_ptr() as *const c_void,
)?)
}
}
}
impl Drop for Devicetree<'_> {
fn drop(&mut self) {
unsafe {
let _ = boot::free_pool(self.ptr);
}
}
}
impl DevicetreeGuard<'_> {
fn new(content: &[u8], size: Option<usize>) -> BootResult<Self> {
Ok(Self {
devicetree: Some(Devicetree::new(content, size)?),
})
}
fn fixup(&mut self, fixup: &mut ScopedProtocol<DevicetreeFixup>) -> BootResult<()> {
if let Some(devicetree) = &mut self.devicetree {
devicetree.fixup(fixup)?;
}
Ok(())
}
fn install(&mut self) -> BootResult<()> {
let devicetree = self.devicetree.take();
if let Some(devicetree) = devicetree {
devicetree.install()?;
core::mem::forget(devicetree); }
Ok(())
}
fn size(&self) -> Result<usize, DevicetreeError> {
Ok(self
.devicetree
.as_ref()
.ok_or(DevicetreeError::DevicetreeGuardConsumed)?
.size)
}
fn slice(&self) -> Result<&[u8], DevicetreeError> {
Ok(self
.devicetree
.as_ref()
.ok_or(DevicetreeError::DevicetreeGuardConsumed)?
.slice)
}
}
impl Drop for DevicetreeGuard<'_> {
fn drop(&mut self) {
self.devicetree.take();
}
}
fn fixup_devicetree(devicetree: &mut DevicetreeGuard) -> BootResult<()> {
let mut fixup = locate_protocol::<DevicetreeFixup>()?;
let slice = devicetree.slice()?.to_vec();
if let Err(BootError::Uefi(e)) = devicetree.fixup(&mut fixup)
&& e.status() == Status::BUFFER_TOO_SMALL
{
*devicetree = DevicetreeGuard::new(&slice, Some(devicetree.size()?))?;
devicetree.fixup(&mut fixup)?;
}
Ok(())
}
pub(super) fn install_devicetree(devicetree: &str, fs: &mut UefiFileSystem) -> BootResult<()> {
if matches!(
get_arch().as_deref().map(alloc::string::String::as_str),
Some("arm" | "aa64") ) {
let path = str_to_cstr(&normalize_path(devicetree))?;
let f = fs.read(&path)?;
let mut devicetree = DevicetreeGuard::new(&f, None)?;
fixup_devicetree(&mut devicetree)?;
devicetree.install()?;
}
Ok(())
}