// Copyright 2023, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::defs::{EFI_MEMORY_TYPE_LOADER_DATA, EFI_STATUS_ALREADY_STARTED}; use crate::{EfiEntry, EfiResult}; use core::alloc::{GlobalAlloc, Layout}; use core::ptr::null_mut; /// Implement a global allocator using `EFI_BOOT_SERVICES.AllocatePool()/FreePool()` pub enum EfiAllocator { Uninitialized, Initialized(EfiEntry), Exited, } #[global_allocator] static mut EFI_GLOBAL_ALLOCATOR: EfiAllocator = EfiAllocator::Uninitialized; /// An internal API to obtain library internal global EfiEntry. pub(crate) fn internal_efi_entry() -> Option<&'static EfiEntry> { // SAFETY: // For now, `EfiAllocator` is only modified in `init_efi_global_alloc()` when `EfiAllocator` is // being initialized or in `exit_efi_global_alloc` after `EFI_BOOT_SERVICES. // ExitBootServices()` is called, where there should be no event/notification function that can // be triggered. Therefore, it should be safe from race condition. unsafe { EFI_GLOBAL_ALLOCATOR.get_efi_entry() } } /// Initializes global allocator. /// /// # Safety /// /// This function modifies global variable `EFI_GLOBAL_ALLOCATOR`. It should only be called when /// there is no event/notification function that can be triggered or modify it. Otherwise there /// is a risk of race condition. pub(crate) unsafe fn init_efi_global_alloc(efi_entry: EfiEntry) -> EfiResult<()> { // SAFETY: See SAFETY of `internal_efi_entry()` unsafe { match EFI_GLOBAL_ALLOCATOR { EfiAllocator::Uninitialized => { EFI_GLOBAL_ALLOCATOR = EfiAllocator::Initialized(efi_entry); Ok(()) } _ => Err(EFI_STATUS_ALREADY_STARTED.into()), } } } /// Internal API to invalidate global allocator after ExitBootService(). /// /// # Safety /// /// This function modifies global variable `EFI_GLOBAL_ALLOCATOR`. It should only be called when /// there is no event/notification function that can be triggered or modify it. Otherwise there /// is a risk of race condition. pub(crate) unsafe fn exit_efi_global_alloc() { // SAFETY: See SAFETY of `internal_efi_entry()` unsafe { EFI_GLOBAL_ALLOCATOR = EfiAllocator::Exited; } } impl EfiAllocator { /// Returns a reference to the EfiEntry. fn get_efi_entry(&self) -> Option<&EfiEntry> { match self { EfiAllocator::Initialized(ref entry) => Some(entry), _ => None, } } /// Allocate memory via EFI_BOOT_SERVICES. fn allocate(&self, size: usize) -> *mut u8 { match self .get_efi_entry() .unwrap() .system_table() .boot_services() .allocate_pool(EFI_MEMORY_TYPE_LOADER_DATA, size) { Ok(p) => p as *mut _, _ => null_mut(), } } /// Deallocate memory previously allocated by `Self::allocate()`. Passing invalid pointer will /// cause the method to panic. fn deallocate(&self, ptr: *mut u8) { match self.get_efi_entry() { Some(ref entry) => { entry.system_table().boot_services().free_pool(ptr as *mut _).unwrap(); } // After EFI_BOOT_SERVICES.ExitBootServices(), all allocated memory is considered // leaked and under full ownership of subsequent OS loader code. _ => {} } } } unsafe impl GlobalAlloc for EfiAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { let size = layout.size(); let align = layout.align(); // TODO(300168989): `EFI_BOOT_SERVICES.AllocatePool()` is only 8-byte aligned. Add support // for arbitrary alignment. // `AllocatePool()` can be slow for allocating large buffers. In this case, // `AllocatePages()` is recommended. assert_eq!(8usize.checked_rem(align).unwrap(), 0); self.allocate(size) } unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { self.deallocate(ptr); } } /// API for allocating raw memory via EFI_BOOT_SERVICES pub fn efi_malloc(size: usize) -> *mut u8 { // SAFETY: See SAFETY of `internal_efi_entry()`. unsafe { EFI_GLOBAL_ALLOCATOR.allocate(size) } } /// API for deallocating raw memory previously allocated by `efi_malloc()`. Passing invalid /// pointer will cause the function to panic. pub fn efi_free(ptr: *mut u8) { // SAFETY: See SAFETY of `internal_efi_entry()`. unsafe { EFI_GLOBAL_ALLOCATOR.deallocate(ptr) } }