xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/protocol/device_path.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Rust wrapper for `EFI_DEVICE_PATH_PROTOCOL`.
16 
17 use crate::protocol::{Protocol, ProtocolInfo};
18 use crate::EfiEntry;
19 use core::fmt::Display;
20 use efi_types::{EfiDevicePathProtocol, EfiDevicePathToTextProtocol, EfiGuid};
21 use liberror::{Error, Result};
22 
23 /// `EFI_DEVICE_PATH_PROTOCOL`
24 pub struct DevicePathProtocol;
25 
26 impl ProtocolInfo for DevicePathProtocol {
27     type InterfaceType = EfiDevicePathProtocol;
28 
29     const GUID: EfiGuid =
30         EfiGuid::new(0x09576e91, 0x6d3f, 0x11d2, [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b]);
31 }
32 
33 /// `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL`
34 pub struct DevicePathToTextProtocol;
35 
36 impl ProtocolInfo for DevicePathToTextProtocol {
37     type InterfaceType = EfiDevicePathToTextProtocol;
38 
39     const GUID: EfiGuid =
40         EfiGuid::new(0x8b843e20, 0x8132, 0x4852, [0x90, 0xcc, 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c]);
41 }
42 
43 impl<'a> Protocol<'a, DevicePathToTextProtocol> {
44     /// Wrapper of `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText()`
convert_device_path_to_text( &self, device_path: &Protocol<DevicePathProtocol>, display_only: bool, allow_shortcuts: bool, ) -> Result<DevicePathText<'a>>45     pub fn convert_device_path_to_text(
46         &self,
47         device_path: &Protocol<DevicePathProtocol>,
48         display_only: bool,
49         allow_shortcuts: bool,
50     ) -> Result<DevicePathText<'a>> {
51         let f = self.interface()?.convert_device_path_to_text.as_ref().ok_or(Error::NotFound)?;
52         // SAFETY:
53         // `self.interface()?` guarantees `self.interface` is non-null and points to a valid object
54         // established by `Protocol::new()`.
55         // `self.interface` is input parameter and will not be retained. It outlives the call.
56         let res = unsafe { f(device_path.interface_ptr(), display_only, allow_shortcuts) };
57         Ok(DevicePathText::new(res, self.efi_entry))
58     }
59 }
60 
61 /// `DevicePathText` is a wrapper for the return type of
62 /// EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText().
63 pub struct DevicePathText<'a> {
64     text: Option<&'a [u16]>,
65     efi_entry: &'a EfiEntry,
66 }
67 
68 impl<'a> DevicePathText<'a> {
new(text: *mut u16, efi_entry: &'a EfiEntry) -> Self69     pub(crate) fn new(text: *mut u16, efi_entry: &'a EfiEntry) -> Self {
70         if text.is_null() {
71             return Self { text: None, efi_entry: efi_entry };
72         }
73 
74         let mut len: usize = 0;
75         // SAFETY: UEFI text is NULL terminated.
76         while unsafe { *text.add(len) } != 0 {
77             len += 1;
78         }
79         Self {
80             // SAFETY: Pointer is confirmed non-null with known length at this point.
81             text: Some(unsafe { core::slice::from_raw_parts(text, len) }),
82             efi_entry: efi_entry,
83         }
84     }
85 
86     /// Get the text as a u16 slice.
text(&self) -> Option<&[u16]>87     pub fn text(&self) -> Option<&[u16]> {
88         self.text
89     }
90 }
91 
92 impl Display for DevicePathText<'_> {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result93     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94         if let Some(text) = self.text {
95             for c in char::decode_utf16(text.into_iter().map(|v| *v)) {
96                 match c.unwrap_or(char::REPLACEMENT_CHARACTER) {
97                     '\0' => break,
98                     ch => write!(f, "{}", ch)?,
99                 };
100             }
101         }
102         Ok(())
103     }
104 }
105 
106 impl Drop for DevicePathText<'_> {
drop(&mut self)107     fn drop(&mut self) {
108         if let Some(text) = self.text {
109             self.efi_entry
110                 .system_table()
111                 .boot_services()
112                 .free_pool(text.as_ptr() as *mut _)
113                 .unwrap();
114         }
115     }
116 }
117 
118 #[cfg(test)]
119 mod test {
120     use super::*;
121     use crate::test::*;
122     use core::ptr::null_mut;
123 
124     #[test]
test_device_path_text_drop()125     fn test_device_path_text_drop() {
126         run_test(|image_handle, systab_ptr| {
127             let efi_entry = EfiEntry { image_handle, systab_ptr };
128             let mut data: [u16; 4] = [1, 2, 3, 0];
129             {
130                 let path = DevicePathText::new(data.as_mut_ptr(), &efi_entry);
131                 assert_eq!(path.text().unwrap().to_vec(), vec![1, 2, 3]);
132             }
133             efi_call_traces().with(|traces| {
134                 assert_eq!(
135                     traces.borrow_mut().free_pool_trace.inputs,
136                     [data.as_mut_ptr() as *mut _]
137                 );
138             });
139         })
140     }
141 
142     #[test]
test_device_path_text_null()143     fn test_device_path_text_null() {
144         run_test(|image_handle, systab_ptr| {
145             let efi_entry = EfiEntry { image_handle, systab_ptr };
146             {
147                 assert_eq!(DevicePathText::new(null_mut(), &efi_entry).text(), None);
148             }
149             efi_call_traces().with(|traces| {
150                 assert_eq!(traces.borrow_mut().free_pool_trace.inputs.len(), 0);
151             });
152         })
153     }
154 }
155