blob: 6ca5560e9e1e1384ce70caebe99b7a516b08c81a [file]
// Copyright 2024, 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.
//! Rust wrapper for `EFI_DEVICE_PATH_PROTOCOL`.
use crate::protocol::{Protocol, ProtocolInfo};
use crate::EfiEntry;
use core::fmt::Display;
use efi_types::{EfiDevicePathProtocol, EfiDevicePathToTextProtocol, EfiGuid};
use liberror::{Error, Result};
/// `EFI_DEVICE_PATH_PROTOCOL`
pub struct DevicePathProtocol;
impl ProtocolInfo for DevicePathProtocol {
type InterfaceType = EfiDevicePathProtocol;
const GUID: EfiGuid =
EfiGuid::new(0x09576e91, 0x6d3f, 0x11d2, [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b]);
}
/// `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL`
pub struct DevicePathToTextProtocol;
impl ProtocolInfo for DevicePathToTextProtocol {
type InterfaceType = EfiDevicePathToTextProtocol;
const GUID: EfiGuid =
EfiGuid::new(0x8b843e20, 0x8132, 0x4852, [0x90, 0xcc, 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c]);
}
impl<'a> Protocol<'a, DevicePathToTextProtocol> {
/// Wrapper of `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText()`
pub fn convert_device_path_to_text(
&self,
device_path: &Protocol<DevicePathProtocol>,
display_only: bool,
allow_shortcuts: bool,
) -> Result<DevicePathText<'a>> {
let f = self.interface()?.convert_device_path_to_text.as_ref().ok_or(Error::NotFound)?;
// SAFETY:
// `self.interface()?` guarantees `self.interface` is non-null and points to a valid object
// established by `Protocol::new()`.
// `self.interface` is input parameter and will not be retained. It outlives the call.
let res = unsafe { f(device_path.interface_ptr(), display_only, allow_shortcuts) };
Ok(DevicePathText::new(res, self.efi_entry))
}
}
/// `DevicePathText` is a wrapper for the return type of
/// EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText().
pub struct DevicePathText<'a> {
text: Option<&'a [u16]>,
efi_entry: &'a EfiEntry,
}
impl<'a> DevicePathText<'a> {
pub(crate) fn new(text: *mut u16, efi_entry: &'a EfiEntry) -> Self {
if text.is_null() {
return Self { text: None, efi_entry: efi_entry };
}
let mut len: usize = 0;
// SAFETY: UEFI text is NULL terminated.
while unsafe { *text.add(len) } != 0 {
len += 1;
}
Self {
// SAFETY: Pointer is confirmed non-null with known length at this point.
text: Some(unsafe { core::slice::from_raw_parts(text, len) }),
efi_entry: efi_entry,
}
}
/// Get the text as a u16 slice.
pub fn text(&self) -> Option<&[u16]> {
self.text
}
}
impl Display for DevicePathText<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Some(text) = self.text {
for c in char::decode_utf16(text.into_iter().map(|v| *v)) {
match c.unwrap_or(char::REPLACEMENT_CHARACTER) {
'\0' => break,
ch => write!(f, "{}", ch)?,
};
}
}
Ok(())
}
}
impl Drop for DevicePathText<'_> {
fn drop(&mut self) {
if let Some(text) = self.text {
self.efi_entry
.system_table()
.boot_services()
.free_pool(text.as_ptr() as *mut _)
.unwrap();
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::*;
use core::ptr::null_mut;
#[test]
fn test_device_path_text_drop() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
let mut data: [u16; 4] = [1, 2, 3, 0];
{
let path = DevicePathText::new(data.as_mut_ptr(), &efi_entry);
assert_eq!(path.text().unwrap().to_vec(), vec![1, 2, 3]);
}
efi_call_traces().with(|traces| {
assert_eq!(
traces.borrow_mut().free_pool_trace.inputs,
[data.as_mut_ptr() as *mut _]
);
});
})
}
#[test]
fn test_device_path_text_null() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
{
assert_eq!(DevicePathText::new(null_mut(), &efi_entry).text(), None);
}
efi_call_traces().with(|traces| {
assert_eq!(traces.borrow_mut().free_pool_trace.inputs.len(), 0);
});
})
}
}