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 use crate::defs::{
16     EfiDevicePathProtocol, EfiDevicePathToTextProtocol, EfiGuid, EFI_STATUS_NOT_FOUND,
17 };
18 use crate::protocol::{Protocol, ProtocolInfo};
19 use crate::{EfiEntry, EfiError, EfiResult};
20 use core::fmt::Display;
21 
22 /// `EFI_DEVICE_PATH_PROTOCOL`
23 pub struct DevicePathProtocol;
24 
25 impl ProtocolInfo for DevicePathProtocol {
26     type InterfaceType = EfiDevicePathProtocol;
27 
28     const GUID: EfiGuid =
29         EfiGuid::new(0x09576e91, 0x6d3f, 0x11d2, [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b]);
30 }
31 
32 /// `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL`
33 pub struct DevicePathToTextProtocol;
34 
35 impl ProtocolInfo for DevicePathToTextProtocol {
36     type InterfaceType = EfiDevicePathToTextProtocol;
37 
38     const GUID: EfiGuid =
39         EfiGuid::new(0x8b843e20, 0x8132, 0x4852, [0x90, 0xcc, 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c]);
40 }
41 
42 impl<'a> Protocol<'a, DevicePathToTextProtocol> {
43     /// 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, ) -> EfiResult<DevicePathText<'a>>44     pub fn convert_device_path_to_text(
45         &self,
46         device_path: &Protocol<DevicePathProtocol>,
47         display_only: bool,
48         allow_shortcuts: bool,
49     ) -> EfiResult<DevicePathText<'a>> {
50         let f = self
51             .interface()?
52             .convert_device_path_to_text
53             .as_ref()
54             .ok_or_else::<EfiError, _>(|| EFI_STATUS_NOT_FOUND.into())?;
55         // SAFETY:
56         // `self.interface()?` guarantees `self.interface` is non-null and points to a valid object
57         // established by `Protocol::new()`.
58         // `self.interface` is input parameter and will not be retained. It outlives the call.
59         let res = unsafe { f(device_path.interface_ptr(), display_only, allow_shortcuts) };
60         Ok(DevicePathText::new(res, self.efi_entry))
61     }
62 }
63 
64 // `DevicePathText` is a wrapper for the return type of
65 // EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText().
66 pub struct DevicePathText<'a> {
67     text: Option<&'a [u16]>,
68     efi_entry: &'a EfiEntry,
69 }
70 
71 impl<'a> DevicePathText<'a> {
new(text: *mut u16, efi_entry: &'a EfiEntry) -> Self72     pub(crate) fn new(text: *mut u16, efi_entry: &'a EfiEntry) -> Self {
73         if text.is_null() {
74             return Self { text: None, efi_entry: efi_entry };
75         }
76 
77         let mut len: usize = 0;
78         // SAFETY: UEFI text is NULL terminated.
79         while unsafe { *text.add(len) } != 0 {
80             len += 1;
81         }
82         Self {
83             // SAFETY: Pointer is confirmed non-null with known length at this point.
84             text: Some(unsafe { core::slice::from_raw_parts(text, len) }),
85             efi_entry: efi_entry,
86         }
87     }
88 
89     /// Get the text as a u16 slice.
text(&self) -> Option<&[u16]>90     pub fn text(&self) -> Option<&[u16]> {
91         self.text
92     }
93 }
94 
95 impl Display for DevicePathText<'_> {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result96     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
97         if let Some(text) = self.text {
98             for c in char::decode_utf16(text.into_iter().map(|v| *v)) {
99                 match c.unwrap_or(char::REPLACEMENT_CHARACTER) {
100                     '\0' => break,
101                     ch => write!(f, "{}", ch)?,
102                 };
103             }
104         }
105         Ok(())
106     }
107 }
108 
109 impl Drop for DevicePathText<'_> {
drop(&mut self)110     fn drop(&mut self) {
111         if let Some(text) = self.text {
112             self.efi_entry
113                 .system_table()
114                 .boot_services()
115                 .free_pool(text.as_ptr() as *mut _)
116                 .unwrap();
117         }
118     }
119 }
120 
121 #[cfg(test)]
122 mod test {
123     use super::*;
124     use crate::test::*;
125     use core::ptr::null_mut;
126 
127     #[test]
test_device_path_text_drop()128     fn test_device_path_text_drop() {
129         run_test(|image_handle, systab_ptr| {
130             let efi_entry = EfiEntry { image_handle, systab_ptr };
131             let mut data: [u16; 4] = [1, 2, 3, 0];
132             {
133                 let path = DevicePathText::new(data.as_mut_ptr(), &efi_entry);
134                 assert_eq!(path.text().unwrap().to_vec(), vec![1, 2, 3]);
135             }
136             efi_call_traces().with(|traces| {
137                 assert_eq!(
138                     traces.borrow_mut().free_pool_trace.inputs,
139                     [data.as_mut_ptr() as *mut _]
140                 );
141             });
142         })
143     }
144 
145     #[test]
test_device_path_text_null()146     fn test_device_path_text_null() {
147         run_test(|image_handle, systab_ptr| {
148             let efi_entry = EfiEntry { image_handle, systab_ptr };
149             {
150                 assert_eq!(DevicePathText::new(null_mut(), &efi_entry).text(), None);
151             }
152             efi_call_traces().with(|traces| {
153                 assert_eq!(traces.borrow_mut().free_pool_trace.inputs.len(), 0);
154             });
155         })
156     }
157 }
158