1 // Copyright 2023, 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 alloc::vec::Vec;
16 use core::ffi::CStr;
17
18 use crate::error::{EfiAppError, Result};
19 use efi::{
20 defs::{EfiGuid, EFI_TIMER_DELAY_TIMER_RELATIVE},
21 protocol::{
22 block_io::BlockIoProtocol,
23 device_path::{DevicePathProtocol, DevicePathText, DevicePathToTextProtocol},
24 loaded_image::LoadedImageProtocol,
25 simple_text_input::SimpleTextInputProtocol,
26 Protocol,
27 },
28 DeviceHandle, EfiEntry, EventType,
29 };
30 use fdt::FdtHeader;
31 use gbl_storage::{
32 required_scratch_size, AsBlockDevice, AsMultiBlockDevices, BlockInfo, BlockIo, BlockIoError,
33 };
34
35 pub const EFI_DTB_TABLE_GUID: EfiGuid =
36 EfiGuid::new(0xb1b621d5, 0xf19c, 0x41a5, [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0]);
37
38 /// Checks and converts an integer into usize
to_usize<T: TryInto<usize>>(val: T) -> Result<usize>39 pub fn to_usize<T: TryInto<usize>>(val: T) -> Result<usize> {
40 Ok(val.try_into().map_err(|_| EfiAppError::ArithmeticOverflow)?)
41 }
42
43 /// Rounds up a usize convertible number.
usize_roundup<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize>44 pub fn usize_roundup<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize> {
45 // (lhs + rhs - 1) / rhs * rhs
46 let lhs = to_usize(lhs)?;
47 let rhs = to_usize(rhs)?;
48 let compute = || lhs.checked_add(rhs.checked_sub(1)?)?.checked_div(rhs)?.checked_mul(rhs);
49 Ok(compute().ok_or_else(|| EfiAppError::ArithmeticOverflow)?)
50 }
51
52 /// Adds two usize convertible numbers and checks overflow.
usize_add<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize>53 pub fn usize_add<L: TryInto<usize>, R: TryInto<usize>>(lhs: L, rhs: R) -> Result<usize> {
54 Ok(to_usize(lhs)?.checked_add(to_usize(rhs)?).ok_or_else(|| EfiAppError::ArithmeticOverflow)?)
55 }
56
57 /// Gets a subslice of the given slice with aligned address according to `alignment`
aligned_subslice(bytes: &mut [u8], alignment: usize) -> Result<&mut [u8]>58 pub fn aligned_subslice(bytes: &mut [u8], alignment: usize) -> Result<&mut [u8]> {
59 let addr = bytes.as_ptr() as usize;
60 Ok(&mut bytes[usize_roundup(addr, alignment)? - addr..])
61 }
62
63 // Implement a block device on top of BlockIoProtocol
64 pub struct EfiBlockIo<'a>(pub Protocol<'a, BlockIoProtocol>);
65
66 impl BlockIo for EfiBlockIo<'_> {
info(&mut self) -> BlockInfo67 fn info(&mut self) -> BlockInfo {
68 BlockInfo {
69 block_size: self.0.media().unwrap().block_size as u64,
70 num_blocks: (self.0.media().unwrap().last_block + 1) as u64,
71 alignment: core::cmp::max(1, self.0.media().unwrap().io_align as u64),
72 }
73 }
74
read_blocks( &mut self, blk_offset: u64, out: &mut [u8], ) -> core::result::Result<(), BlockIoError>75 fn read_blocks(
76 &mut self,
77 blk_offset: u64,
78 out: &mut [u8],
79 ) -> core::result::Result<(), BlockIoError> {
80 self.0
81 .read_blocks(blk_offset, out)
82 .map_err(|_| BlockIoError::Others(Some("EFI BLOCK_IO protocol read error")))
83 }
84
write_blocks( &mut self, blk_offset: u64, data: &mut [u8], ) -> core::result::Result<(), BlockIoError>85 fn write_blocks(
86 &mut self,
87 blk_offset: u64,
88 data: &mut [u8],
89 ) -> core::result::Result<(), BlockIoError> {
90 self.0
91 .write_blocks(blk_offset, data)
92 .map_err(|_| BlockIoError::Others(Some("EFI BLOCK_IO protocol write error")))
93 }
94 }
95
96 /// `EfiGptDevice` wraps a `EfiBlockIo` and implements `AsBlockDevice` interface.
97 pub struct EfiGptDevice<'a> {
98 io: EfiBlockIo<'a>,
99 scratch: Vec<u8>,
100 }
101
102 const MAX_GPT_ENTRIES: u64 = 128;
103
104 impl<'a> EfiGptDevice<'a> {
105 /// Initialize from a `BlockIoProtocol` EFI protocol
new(protocol: Protocol<'a, BlockIoProtocol>) -> Result<Self>106 pub fn new(protocol: Protocol<'a, BlockIoProtocol>) -> Result<Self> {
107 let mut io = EfiBlockIo(protocol);
108 let scratch = vec![0u8; required_scratch_size(&mut io, MAX_GPT_ENTRIES)?];
109 Ok(Self { io, scratch })
110 }
111 }
112
113 impl AsBlockDevice for EfiGptDevice<'_> {
with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64))114 fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64)) {
115 f(&mut self.io, &mut self.scratch[..], MAX_GPT_ENTRIES)
116 }
117 }
118
119 pub struct EfiMultiBlockDevices<'a>(pub alloc::vec::Vec<EfiGptDevice<'a>>);
120
121 impl AsMultiBlockDevices for EfiMultiBlockDevices<'_> {
for_each( &mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64), ) -> core::result::Result<(), Option<&'static str>>122 fn for_each(
123 &mut self,
124 f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
125 ) -> core::result::Result<(), Option<&'static str>> {
126 for (idx, ele) in self.0.iter_mut().enumerate() {
127 f(ele, u64::try_from(idx).unwrap());
128 }
129 Ok(())
130 }
131 }
132
133 /// Finds and returns all block devices that have a valid GPT.
find_gpt_devices(efi_entry: &EfiEntry) -> Result<EfiMultiBlockDevices>134 pub fn find_gpt_devices(efi_entry: &EfiEntry) -> Result<EfiMultiBlockDevices> {
135 let bs = efi_entry.system_table().boot_services();
136 let block_dev_handles = bs.locate_handle_buffer_by_protocol::<BlockIoProtocol>()?;
137 let mut gpt_devices = Vec::<EfiGptDevice>::new();
138 for handle in block_dev_handles.handles() {
139 let mut gpt_dev = EfiGptDevice::new(bs.open_protocol::<BlockIoProtocol>(*handle)?)?;
140 match gpt_dev.sync_gpt() {
141 Ok(_) => {
142 gpt_devices.push(gpt_dev);
143 }
144 _ => {}
145 };
146 }
147 Ok(EfiMultiBlockDevices(gpt_devices))
148 }
149
150 /// Helper function to get the `DevicePathText` from a `DeviceHandle`.
get_device_path<'a>( entry: &'a EfiEntry, handle: DeviceHandle, ) -> Result<DevicePathText<'a>>151 pub fn get_device_path<'a>(
152 entry: &'a EfiEntry,
153 handle: DeviceHandle,
154 ) -> Result<DevicePathText<'a>> {
155 let bs = entry.system_table().boot_services();
156 let path = bs.open_protocol::<DevicePathProtocol>(handle)?;
157 let path_to_text = bs.find_first_and_open::<DevicePathToTextProtocol>()?;
158 Ok(path_to_text.convert_device_path_to_text(&path, false, false)?)
159 }
160
161 /// Helper function to get the loaded image path.
loaded_image_path(entry: &EfiEntry) -> Result<DevicePathText>162 pub fn loaded_image_path(entry: &EfiEntry) -> Result<DevicePathText> {
163 get_device_path(
164 entry,
165 entry
166 .system_table()
167 .boot_services()
168 .open_protocol::<LoadedImageProtocol>(entry.image_handle())?
169 .device_handle()?,
170 )
171 }
172
173 /// Find FDT from EFI configuration table.
get_efi_fdt<'a>(entry: &'a EfiEntry) -> Option<(&FdtHeader, &[u8])>174 pub fn get_efi_fdt<'a>(entry: &'a EfiEntry) -> Option<(&FdtHeader, &[u8])> {
175 if let Some(config_tables) = entry.system_table().configuration_table() {
176 for table in config_tables {
177 if table.vendor_guid == EFI_DTB_TABLE_GUID {
178 // SAFETY: Buffer provided by EFI configuration table.
179 return unsafe { FdtHeader::from_raw(table.vendor_table as *const _).ok() };
180 }
181 }
182 }
183 None
184 }
185
186 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
efi_to_e820_mem_type(efi_mem_type: u32) -> u32187 pub fn efi_to_e820_mem_type(efi_mem_type: u32) -> u32 {
188 match efi_mem_type {
189 efi::defs::EFI_MEMORY_TYPE_LOADER_CODE
190 | efi::defs::EFI_MEMORY_TYPE_LOADER_DATA
191 | efi::defs::EFI_MEMORY_TYPE_BOOT_SERVICES_CODE
192 | efi::defs::EFI_MEMORY_TYPE_BOOT_SERVICES_DATA
193 | efi::defs::EFI_MEMORY_TYPE_CONVENTIONAL_MEMORY => boot::x86::E820_ADDRESS_TYPE_RAM,
194 efi::defs::EFI_MEMORY_TYPE_RUNTIME_SERVICES_CODE
195 | efi::defs::EFI_MEMORY_TYPE_RUNTIME_SERVICES_DATA
196 | efi::defs::EFI_MEMORY_TYPE_MEMORY_MAPPED_IO
197 | efi::defs::EFI_MEMORY_TYPE_MEMORY_MAPPED_IOPORT_SPACE
198 | efi::defs::EFI_MEMORY_TYPE_PAL_CODE
199 | efi::defs::EFI_MEMORY_TYPE_RESERVED_MEMORY_TYPE => boot::x86::E820_ADDRESS_TYPE_RESERVED,
200 efi::defs::EFI_MEMORY_TYPE_UNUSABLE_MEMORY => boot::x86::E820_ADDRESS_TYPE_UNUSABLE,
201 efi::defs::EFI_MEMORY_TYPE_ACPIRECLAIM_MEMORY => boot::x86::E820_ADDRESS_TYPE_ACPI,
202 efi::defs::EFI_MEMORY_TYPE_ACPIMEMORY_NVS => boot::x86::E820_ADDRESS_TYPE_NVS,
203 efi::defs::EFI_MEMORY_TYPE_PERSISTENT_MEMORY => boot::x86::E820_ADDRESS_TYPE_PMEM,
204 v => panic!("Unmapped EFI memory type {v}"),
205 }
206 }
207
208 /// A helper to convert a bytes slice containing a null-terminated string to `str`
cstr_bytes_to_str(data: &[u8]) -> Result<&str>209 pub fn cstr_bytes_to_str(data: &[u8]) -> Result<&str> {
210 Ok(CStr::from_bytes_until_nul(data)
211 .map_err(|_| EfiAppError::InvalidString)?
212 .to_str()
213 .map_err(|_| EfiAppError::InvalidString)?)
214 }
215
216 /// Converts 1 ms to number of 100 nano seconds
ms_to_100ns(ms: u64) -> Result<u64>217 pub fn ms_to_100ns(ms: u64) -> Result<u64> {
218 Ok(ms.checked_mul(1000 * 10).ok_or(EfiAppError::ArithmeticOverflow)?)
219 }
220
221 /// Repetitively runs a closure until it signals completion or timeout.
222 ///
223 /// * If `f` returns `Ok(R)`, an `Ok(Some(R))` is returned immediately.
224 /// * If `f` has been repetitively called and returning `Err(false)` for `timeout_ms`, an
225 /// `Ok(None)` is returned. This is the time out case.
226 /// * If `f` returns `Err(true)` the timeout is reset.
loop_with_timeout<F, R>(efi_entry: &EfiEntry, timeout_ms: u64, mut f: F) -> Result<Option<R>> where F: FnMut() -> core::result::Result<R, bool>,227 pub fn loop_with_timeout<F, R>(efi_entry: &EfiEntry, timeout_ms: u64, mut f: F) -> Result<Option<R>>
228 where
229 F: FnMut() -> core::result::Result<R, bool>,
230 {
231 let bs = efi_entry.system_table().boot_services();
232 let timer = bs.create_event(EventType::Timer, None)?;
233 bs.set_timer(&timer, EFI_TIMER_DELAY_TIMER_RELATIVE, ms_to_100ns(timeout_ms)?)?;
234 while !bs.check_event(&timer)? {
235 match f() {
236 Ok(v) => {
237 return Ok(Some(v));
238 }
239 Err(true) => {
240 bs.set_timer(&timer, EFI_TIMER_DELAY_TIMER_RELATIVE, ms_to_100ns(timeout_ms)?)?;
241 }
242 _ => {}
243 }
244 }
245 Ok(None)
246 }
247
248 /// Waits for a key stroke value from simple text input.
249 ///
250 /// Returns `Ok(true)` if the expected key stroke is read, `Ok(false)` if timeout, `Err` otherwise.
wait_key_stroke(efi_entry: &EfiEntry, expected: char, timeout_ms: u64) -> Result<bool>251 pub fn wait_key_stroke(efi_entry: &EfiEntry, expected: char, timeout_ms: u64) -> Result<bool> {
252 let input = efi_entry
253 .system_table()
254 .boot_services()
255 .find_first_and_open::<SimpleTextInputProtocol>()?;
256 loop_with_timeout(efi_entry, timeout_ms, || -> core::result::Result<Result<bool>, bool> {
257 match input.read_key_stroke() {
258 Ok(Some(key)) => match char::decode_utf16([key.unicode_char]).next().unwrap() {
259 Ok(ch) if ch == expected => Ok(Ok(true)),
260 _ => Err(false),
261 },
262 Ok(None) => Err(false),
263 Err(e) => Ok(Err(e.into())),
264 }
265 })?
266 .unwrap_or(Ok(false))
267 }
268