blob: 5685879b98c590e28098ceff8abf213f9d83b137 [file]
// Copyright 2021 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use super::efi_ffi::{cpp_phys_handoff_efi_memory_attributes, cpp_phys_handoff_memory};
use crate::kernel::thread;
use crate::kernel::types::PAddr;
use crate::vm::arch_vm_aspace::{
ARCH_MMU_FLAG_CACHE_MASK, ARCH_MMU_FLAG_CACHED, ARCH_MMU_FLAG_PERM_EXECUTE,
ARCH_MMU_FLAG_PERM_READ, ARCH_MMU_FLAG_PERM_WRITE, ARCH_MMU_FLAG_UNCACHED_DEVICE, ArchMmuFlags,
};
use crate::vm::vm_address_region::flag as vmar_flag;
use crate::vm::vm_aspace::{Type, VmAspace};
use crate::vm::vm_object_physical::VmObjectPhysical;
use crate::vm::vmm;
use core::mem::{align_of, size_of};
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use debug::dprintf;
use efi::{
EFI_INVALID_PARAMETER, EFI_MEMORY_MAPPED_IO, EFI_MEMORY_RO, EFI_MEMORY_RUNTIME, EFI_MEMORY_XP,
EFI_UNSUPPORTED, EfiGuid, EfiMemoryAttributesTableHeader, EfiMemoryDescriptor,
EfiRuntimeServices, EfiStatus, EfiSystemTable,
};
use fbl::RefPtr;
use handoff::PhysHandoff;
use kprint::kprint;
use memalloc::{NormalizedRangeIterator, NormalizedRangeUncheckedIterator, Range};
use zerocopy::FromBytes;
use zx_status::Status;
/// EFI system table physical address.
///
/// Written at most once, at the end of a successful `init_efi_services` and before `EFI_ASPACE` is
/// published with `Ordering::Release`. Readers only use it after an `Ordering::Acquire` load of a
/// non-null `EFI_ASPACE`, so `Ordering::Relaxed` suffices for all accesses.
static EFI_SYSTEM_TABLE: AtomicU64 = AtomicU64::new(0);
/// Whether `EFI_SYSTEM_TABLE` has been set.
///
/// Written at most once, alongside `EFI_SYSTEM_TABLE` at the end of a successful
/// `init_efi_services`, so `Ordering::Relaxed` suffices for the same reason. A failed
/// initialization leaves it unset.
static EFI_SYSTEM_TABLE_SET: AtomicBool = AtomicBool::new(false);
/// Address space with EFI services mapped in 1:1.
///
/// Published at most once at the end of `init_efi_services`, after all 1:1 runtime mappings are in
/// place, with `Ordering::Release`. `try_activate_efi_services` loads it with `Ordering::Acquire`,
/// so a reader that sees a non-null aspace also sees the mappings and `EFI_SYSTEM_TABLE`.
static EFI_ASPACE: AtomicPtr<VmAspace> = AtomicPtr::new(ptr::null_mut());
fn efi_init_hook(_level: init::LkInitLevel) {
// Attempt to initialize EFI.
if let Some(efi_system_table) = Option::from(PhysHandoff::get().efi_system_table) {
let status = init_efi_services(efi_system_table);
if let Err(status) = status {
dprintf!(INFO, "Unable to initialize EFI services: {}\n", status.into_raw());
} else {
kprint!("Init EFI OK\n");
}
} else {
dprintf!(INFO, "No EFI available on system.\n");
}
}
// Init EFI before INIT_LEVEL_PLATFORM in case the platform code wants to use the EFI crashlog.
init::lk_init_hook!(efi_init, efi_init_hook, init::LkInitLevel(init::LK_INIT_LEVEL_PLATFORM.0 - 1));
/// Helper function that maps the region with size `size` at `base` into the given aspace.
fn map_unaligned_region(
aspace: &VmAspace,
base: PAddr,
size: usize,
name: &str,
mut arch_mmu_flags: ArchMmuFlags,
) -> Result<(), Status> {
if size == 0 {
return Err(Status::INVALID_ARGS);
}
let aligned_base = PAddr(page::round_down(base.0));
let aligned_size = size
.checked_add(base.0 - aligned_base.0)
.and_then(|unrounded_size| unrounded_size.checked_next_multiple_of(page::SIZE))
.ok_or(Status::INVALID_ARGS)?;
let aligned_end = PAddr(aligned_base.0.checked_add(aligned_size).ok_or(Status::INVALID_ARGS)?);
// Check that the given region, rounded out to whole pages, does not intersect with any RAM.
{
let mut reserved = true;
let mut check_intersection_with_ram = |ram: &Range| -> bool {
// We need only check for intersection with the first RAM range ending after the
// beginning the region.
if ram.end() <= aligned_base.0 as u64 {
return true;
}
reserved = aligned_end.0 as u64 <= ram.addr();
false
};
let mut count = 0usize;
// SAFETY: `gPhysHandoff` stays valid until `EndHandoff` clears it, which is after the
// `LK_INIT_LEVEL_PLATFORM - 1` hook runs, and `count` is a live local that the callee
// fills in.
let memory =
unsafe { zr::slice_from_raw_parts(cpp_phys_handoff_memory(&mut count), count) };
// SAFETY: `PhysHandoff::memory` is documented as a normalized accounting of RAM: maximally
// contiguous ranges in sorted order (`phys/handoff/include/phys/handoff/handoff.h`).
let ram = unsafe { NormalizedRangeUncheckedIterator::new(memory.iter().copied()) };
for range in ram.filter_map_ram() {
if !check_intersection_with_ram(&range) {
break;
}
}
if !reserved {
kprint!(
"ERROR: Attempted to map EFI region [0x{:x}, 0x{:x}) ({:s}), which is not a \
reserved region.\n",
aligned_base.0,
aligned_end.0,
name
);
return Err(Status::INVALID_ARGS);
}
}
let vmar = aspace.root_vmar().expect("uefi aspace has no root VMAR");
let vmo = VmObjectPhysical::create(aligned_base, aligned_size)?;
// SAFETY: `vmo` was just created and has no mappings or children yet.
unsafe { vmo.set_mapping_cache_policy(arch_mmu_flags & ARCH_MMU_FLAG_CACHE_MASK)? };
arch_mmu_flags &= !ARCH_MMU_FLAG_CACHE_MASK;
let mut vmar_flags: u32 = vmar_flag::SPECIFIC_OVERWRITE;
if arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ != 0 {
vmar_flags |= vmar_flag::CAN_MAP_READ;
}
if arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE != 0 {
vmar_flags |= vmar_flag::CAN_MAP_WRITE;
}
if arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE != 0 {
vmar_flags |= vmar_flag::CAN_MAP_EXECUTE;
}
let mapping_result = vmar.create_vm_mapping(
aligned_base.0,
aligned_size,
page::SHIFT as u8,
vmar_flags,
VmObjectPhysical::into_vm_object(vmo),
0,
arch_mmu_flags,
name.as_bytes(),
)?;
mapping_result.mapping.map_range(0, aligned_size, true, false)?;
Ok(())
}
/// Loops over a `&[u8]` that may or may not be a valid efi_memory_attributes_table.
/// Will return early if `callback` does not return `Ok(())`.
/// Returns `Status::INVALID_ARGS` if `table` is invalid.
pub(super) fn for_each_memory_attribute_entry_safe(
table: &[u8],
mut callback: impl FnMut(&EfiMemoryDescriptor) -> Result<(), Status>,
) -> Result<(), Status> {
if table.len() < size_of::<EfiMemoryAttributesTableHeader>() {
return Err(Status::INVALID_ARGS);
}
let header_bytes = &table[..size_of::<EfiMemoryAttributesTableHeader>()];
let header = EfiMemoryAttributesTableHeader::read_from_bytes(header_bytes).unwrap();
let mut entries = &table[size_of::<EfiMemoryAttributesTableHeader>()..];
if (header.descriptor_size as usize) < size_of::<EfiMemoryDescriptor>() {
dprintf!(
CRITICAL,
"EFI memory attributes header reports a descriptor size of 0x{:x}, which is smaller \
than ours (0x{:x})\n",
header.descriptor_size,
size_of::<EfiMemoryDescriptor>()
);
return Err(Status::INVALID_ARGS);
}
let desc_size = header.descriptor_size as usize;
for _ in 0..header.number_of_entries {
if entries.len() < desc_size {
return Err(Status::INVALID_ARGS);
}
let entry = &entries[..size_of::<EfiMemoryDescriptor>()];
let desc = EfiMemoryDescriptor::read_from_bytes(entry).unwrap();
callback(&desc)?;
entries = &entries[desc_size..];
}
Ok(())
}
/// Initialize data structures for EFI services.
#[sanitize(address = "off")]
fn init_efi_services(efi_system_table: u64) -> Result<(), Status> {
assert!(!EFI_SYSTEM_TABLE_SET.load(Ordering::Relaxed));
if efi_system_table == 0
|| !(efi_system_table as usize).is_multiple_of(align_of::<EfiSystemTable>())
{
return Err(Status::INVALID_ARGS);
}
// gPhysHandoff currently points into physical pages that are part of the ZBI VMO.
// This is safe for now, because we call the efi_init_hook at LK_INIT_LEVEL_PLATFORM, which is
// before userboot runs.
// There are plans to change this in the future, at which point we may need to revisit this.
let mut size = 0usize;
// SAFETY: `gPhysHandoff` stays valid until `EndHandoff` clears it, which is after the
// `LK_INIT_LEVEL_PLATFORM - 1` hook runs, and `size` is a live local that the callee fills in.
let efi_memory_attributes = unsafe { cpp_phys_handoff_efi_memory_attributes(&mut size) };
if size == 0 {
dprintf!(CRITICAL, "EFI did not provide memory table, cannot map runtime services.\n");
return Err(Status::NOT_SUPPORTED);
}
// Map in the system table.
let efi_memory_table = efi_memory_attributes;
if efi_memory_table.is_null() {
dprintf!(CRITICAL, "EFI did not provide memory table, cannot map runtime services.\n");
return Err(Status::NOT_SUPPORTED);
}
// Create a new address space.
let Some(efi_aspace) = VmAspace::create(Type::LowKernel, b"uefi") else {
return Err(Status::NO_RESOURCES);
};
let aspace_for_cleanup = efi_aspace.clone();
let mut cleanup = zr::defer(move || {
let _ = aspace_for_cleanup.destroy();
});
// SAFETY: `efi_memory_attributes` is non-null and points to `size` bytes of hand-off memory
// that stay mapped and unwritten for the duration of this call.
let efi_memory_attributes = unsafe { zr::slice_from_raw_parts(efi_memory_attributes, size) };
for_each_memory_attribute_entry_safe(efi_memory_attributes, |desc| {
if desc.Attribute & EFI_MEMORY_RUNTIME == 0 {
return Ok(());
}
// UEFI v2.9, section 4.6, "EFI_MEMORY_ATTRIBUTES_TABLE" says that only RUNTIME, RO and XP
// are allowed to be set.
//
// We assume double-negatives apply sensibly: "not read-only" implies writable and "not
// execute-protected" implies executable (except for MMIO, which is never executable).
let mut arch_mmu_flags: ArchMmuFlags = ARCH_MMU_FLAG_PERM_READ;
if (desc.Attribute & EFI_MEMORY_RO) == 0 {
arch_mmu_flags |= ARCH_MMU_FLAG_PERM_WRITE;
}
if desc.Type == EFI_MEMORY_MAPPED_IO {
arch_mmu_flags |= ARCH_MMU_FLAG_UNCACHED_DEVICE;
} else {
arch_mmu_flags |= ARCH_MMU_FLAG_CACHED;
if (desc.Attribute & EFI_MEMORY_XP) == 0 {
arch_mmu_flags |= ARCH_MMU_FLAG_PERM_EXECUTE;
}
}
let Some(region_size) = desc.NumberOfPages.checked_mul(page::SIZE as u64) else {
dprintf!(
CRITICAL,
"EFI region base=0x{:x} has an invalid page count 0x{:x}\n",
desc.PhysicalStart,
desc.NumberOfPages
);
return Err(Status::INVALID_ARGS);
};
let result = map_unaligned_region(
&efi_aspace,
PAddr(desc.PhysicalStart as usize),
region_size as usize,
"efi_runtime",
arch_mmu_flags,
);
if let Err(result) = result {
dprintf!(
CRITICAL,
"Failed to map EFI region base=0x{:x} size=0x{:x}: {}\n",
desc.PhysicalStart,
region_size,
result.into_raw()
);
return Err(result);
}
Ok(())
})?;
cleanup.cancel();
EFI_SYSTEM_TABLE_SET.store(true, Ordering::Relaxed);
EFI_SYSTEM_TABLE.store(efi_system_table, Ordering::Relaxed);
EFI_ASPACE.store(RefPtr::into_raw(efi_aspace).cast_mut(), Ordering::Release);
Ok(())
}
/// Activate EFI services.
///
/// Calling this function will attempt to activate the address space containing EFI services, and
/// return a scoped [`EfiServicesActivation`] guard providing access to runtime services. When the
/// guard is dropped, the previous address space will be restored.
///
/// Returns a guard for which [`EfiServicesActivation::valid`] is `false` if no EFI services are
/// available.
///
/// WARNING: Callers accessing EFI runtime services while this guard is active must be tagged with
/// `#[sanitize(address = "off")]` (the Rust equivalent of `NO_ASAN`) to avoid crashes when running
/// under KASAN.
///
/// # Safety
///
/// - Any [`EfiServicesActivation`] guards active on the current thread must be reset or dropped in
/// strict LIFO (stack) order without switching or destroying the thread's saved previous
/// `VmAspace` while the guard is active; otherwise, resetting or dropping an outer guard early
/// restores the previous address space while an inner guard still reports `valid() == true` and
/// would dereference unmapped 1:1 physical EFI pointers.
/// - Callers accessing EFI runtime services while the returned guard is active must disable KASAN
/// instrumentation (`#[sanitize(address = "off")]`).
#[sanitize(address = "off")]
pub unsafe fn try_activate_efi_services() -> EfiServicesActivation {
// Ensure we have EFI services available and it has been initialised.
let efi_aspace = EFI_ASPACE.load(Ordering::Acquire);
if efi_aspace.is_null() {
return EfiServicesActivation::default();
}
debug_assert!(EFI_SYSTEM_TABLE_SET.load(Ordering::Relaxed));
// Switch into the address space where EFI services have been mapped.
// SAFETY: The reference is immediately converted to a pointer and stored; the caller keeps
// that aspace valid until the guard restores it.
let old_aspace = unsafe { thread::current_active_aspace() }.map(NonNull::from);
// SAFETY: `efi_aspace` is published once into `EFI_ASPACE` at the end of `init_efi_services`
// (and loaded with `Acquire` above), is never destroyed, and the caller restores the previous
// aspace through the guard.
unsafe { vmm::set_active_aspace(Some(&*efi_aspace)) };
// Return the services.
let sys = ptr::with_exposed_provenance::<EfiSystemTable>(
EFI_SYSTEM_TABLE.load(Ordering::Relaxed) as usize,
);
// SAFETY: `sys` is the firmware's system table, which `init_efi_services` mapped 1:1 into the
// aspace that is now active, and this function is `#[sanitize(address = "off")]`.
let runtime_services = unsafe { (*sys).RuntimeServices };
let Some(services) = NonNull::new(runtime_services) else {
// SAFETY: `old_aspace` was the thread's active aspace above, and the caller keeps it valid.
unsafe { vmm::set_active_aspace(old_aspace.map(|p| p.as_ref())) };
return EfiServicesActivation::default();
};
EfiServicesActivation { previous_aspace: Some(old_aspace), services: Some(services) }
}
/// Manages access to [`EfiRuntimeServices`] and restoration of the previous address space on drop.
#[derive(Default)]
pub struct EfiServicesActivation {
/// `None` when inactive or already reset. Otherwise, the thread's active aspace when the guard
/// was created, which is `Some(None)` for a thread with no active aspace.
previous_aspace: Option<Option<NonNull<VmAspace>>>,
services: Option<NonNull<EfiRuntimeServices>>,
}
impl EfiServicesActivation {
/// Returns true if there is a valid EFI services pointer.
#[sanitize(address = "off")]
pub fn valid(&self) -> bool {
self.services.is_some()
}
/// Destroy this object, and restore the previous address space.
///
/// # Safety
///
/// Any inner [`EfiServicesActivation`] guards on the current thread must already have been
/// reset or dropped (strict LIFO order), and the caller must not dereference a pointer
/// previously returned by [`Self::get`] after calling `reset`.
#[sanitize(address = "off")]
pub(super) unsafe fn reset(&mut self) {
self.services = None;
let Some(previous_aspace) = self.previous_aspace.take() else {
return;
};
// Restore the previous address space.
// SAFETY: `previous_aspace` was the thread's active aspace when this guard was created,
// and the caller of `try_activate_efi_services` keeps it valid until the guard is reset.
unsafe { vmm::set_active_aspace(previous_aspace.map(|p| p.as_ref())) };
}
/// Get pointer to EFI services.
#[sanitize(address = "off")]
pub(super) fn get(&self) -> Option<NonNull<EfiRuntimeServices>> {
self.services
}
/// Sets the value of an EFI variable via the `SetVariable` runtime service.
#[sanitize(address = "off", cfi = "off")]
pub fn set_variable(
&self,
var_name: &[u16],
vendor_guid: &EfiGuid,
attributes: u32,
data: &[u8],
) -> EfiStatus {
if !self.valid() || var_name.last() != Some(&0) {
return EFI_INVALID_PARAMETER;
}
let services = self.get().unwrap();
// SAFETY: `services` is the firmware's runtime services table, mapped 1:1 in the aspace
// this guard activated, and this function is `#[sanitize(address = "off")]`.
let Some(set_variable) = (unsafe { (*services.as_ptr()).SetVariable }) else {
return EFI_UNSUPPORTED;
};
let data_ptr = if data.is_empty() { ptr::null() } else { data.as_ptr().cast() };
// SAFETY: `set_variable` is the firmware's `SetVariable`. `var_name` is NUL-terminated, and
// `SetVariable` only reads it, `vendor_guid` and `data` while it runs.
unsafe {
set_variable(
var_name.as_ptr().cast_mut(),
ptr::from_ref(vendor_guid).cast_mut(),
attributes,
data.len(),
data_ptr,
)
}
}
}
impl Drop for EfiServicesActivation {
#[sanitize(address = "off")]
fn drop(&mut self) {
// SAFETY: `try_activate_efi_services`'s caller upholds strict LIFO drop order, and `self`
// is being destroyed so `set_variable` and `get` cannot be used afterward.
unsafe { self.reset() };
}
}