blob: 0422846e6651765c54650b897f0572a0da08443b [file]
// 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.
//! EFI protocol wrappers to provide Rust-safe APIs for usage.
use core::ptr::null_mut;
use crate::{DeviceHandle, EfiEntry};
use efi_types::*;
pub mod block_io;
pub mod block_io2;
pub mod device_path;
pub mod gbl_efi_ab_slot;
pub mod gbl_efi_fastboot;
pub mod gbl_efi_fastboot_usb;
pub mod gbl_efi_image_loading;
pub mod gbl_efi_os_configuration;
pub mod loaded_image;
pub mod riscv;
pub mod simple_network;
pub mod simple_text_input;
pub mod simple_text_output;
use liberror::{Error, Result};
/// ProtocolInfo provides GUID info and the EFI data structure type for a protocol.
pub trait ProtocolInfo {
/// Data structure type of the interface.
type InterfaceType;
/// GUID of the protocol.
const GUID: EfiGuid;
}
/// A generic type for representing an EFI protcol.
pub struct Protocol<'a, T: ProtocolInfo> {
// The handle to the device offering the protocol. It's needed for closing the protocol.
device: DeviceHandle,
// The interface protocol itself.
interface: *mut T::InterfaceType,
// The `EfiEntry` data
efi_entry: &'a EfiEntry,
}
/// A base implementation for Protocol<T>.
/// Protocol<T> will have additional implementation based on type `T`.
impl<'a, T: ProtocolInfo> Protocol<'a, T> {
/// Create a new instance with the given device handle, interface pointer and `EfiEntry` data.
///
/// # Safety
///
/// Caller needs to ensure that
///
/// * `interface` points to a valid object of type T::InterfaceType.
///
/// * Object pointed to by `interface` must live as long as the create `Protocol` or 'a.
pub(crate) unsafe fn new(
device: DeviceHandle,
interface: *mut T::InterfaceType,
efi_entry: &'a EfiEntry,
) -> Self {
Self { device, interface, efi_entry }
}
/// Returns the EFI data structure for the protocol interface.
pub fn interface(&self) -> Result<&T::InterfaceType> {
// SAFETY: EFI protocol interface data structure.
unsafe { self.interface.as_ref() }.ok_or(Error::InvalidInput)
}
/// Returns the reference to EFI entry.
pub fn efi_entry(&self) -> &'a EfiEntry {
self.efi_entry
}
/// Returns the mutable pointer of the interface. Invisible from outside. Application should
/// not have any need to alter the content of interface data.
pub(crate) fn interface_ptr(&self) -> *mut T::InterfaceType {
self.interface
}
}
impl<T: ProtocolInfo> Drop for Protocol<'_, T> {
fn drop(&mut self) {
// If the device handle is not specified when creating the Protocol<T>, treat the
// handle as a static permanent reference and don't close it. An example is
// `EFI_SYSTEM_TABLE.ConOut`.
if self.device.0 != null_mut() {
// Currently we open all protocols using flags BY_HANDLE_PROTOCOL. The flag allows a
// protocol to be opened for multiple copies, which is needed if a UEFI protocol
// implementation also require access for other protocols. But if any one of them is
// closed, all other opened copies will be affected. Therefore for now we don't close
// the protocol on drop. In the future when we start using other flags such as
// EXCLUSIVE, we should perform protocol close based on the open flags.
// self.efi_entry.system_table().boot_services().close_protocol::<T>(self.device).unwrap();
}
}
}
/// Macro to perform an EFI protocol function call.
///
/// In the first variant, the first argument is the function pointer,
/// and the following arguments are passed through as protocol args.
///
/// With our [Protocol] struct, usage generally looks something like:
///
/// ```
/// efi_call!(
/// self.interface()?.protocol_function_name,
/// self.interface,
/// arg1,
/// arg2,
/// ...
/// )
/// ```
/// Most efi_call! invocations should use the first variant.
///
/// With the second variant, the first argument is an expression that references
/// a buffer in-out size parameter.
/// This is part of a pattern used by some protocol methods
/// that take an output buffer and an in-out buffer size:
/// if the method returns EFI_STATUS_BUFFER_TOO_SMALL,
/// the size is mutated to contain the minimum required buffer size.
/// The caller can then allocate a larger buffer and reattempt the method call.
///
/// Usage generally looks something like:
/// ```
/// efi_call!(
/// @bufsize arg2,
/// self.interface()?.protocol_function_name,
/// self.interface,
/// arg1,
/// &mut arg2,
/// ...
/// )
/// ```
#[macro_export]
macro_rules! efi_call {
( $method:expr, $($x:expr),*$(,)? ) => {
{
use liberror::{Error, Result, efi_status_to_result};
let res: Result<()> = match $method {
None => Err(Error::NotFound),
Some(f) => efi_status_to_result(f($($x,)*)),
};
res
}
};
( @bufsize $size:expr, $method:expr, $($x:expr),*$(,)? ) => {
{
use liberror::{Error, Result, efi_status_to_result};
use efi_types::EFI_STATUS_BUFFER_TOO_SMALL;
let res: Result<()> = match $method {
None => Err(Error::NotFound),
Some(f) => {
match f($($x,)*) {
EFI_STATUS_BUFFER_TOO_SMALL => Err(Error::BufferTooSmall(Some($size))),
r => efi_status_to_result(r),
}
},
};
res
}
};
}
// Following are protocol specific implementations for Protocol<T>.
// TODO(300168989): Consdier splitting each protocol into separate file as we add more protocols.
#[cfg(test)]
mod test {
use super::*;
use crate::test::*;
#[test]
fn test_dont_close_protocol_without_device_handle() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
let mut block_io: EfiBlockIoProtocol = Default::default();
// SAFETY: `block_io` is a EfiBlockIoProtocol and out lives the created Protocol.
unsafe {
Protocol::<block_io::BlockIoProtocol>::new(
DeviceHandle(null_mut()),
&mut block_io as *mut _,
&efi_entry,
);
}
efi_call_traces().with(|traces| {
assert_eq!(traces.borrow_mut().close_protocol_trace.inputs.len(), 0);
});
})
}
}