xref: /aosp_15_r20/bootable/libbootloader/gbl/efi/src/efi_blocks.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 use alloc::vec::Vec;
16 use core::{cmp::max, fmt::Write};
17 use efi::{
18     efi_print, efi_println,
19     protocol::{block_io::BlockIoProtocol, block_io2::BlockIo2Protocol, Protocol},
20     EfiEntry,
21 };
22 use efi_types::EfiBlockIoMedia;
23 use gbl_async::block_on;
24 use gbl_storage::{gpt_buffer_size, BlockInfo, BlockIo, Disk, Gpt, SliceMaybeUninit};
25 use liberror::Error;
26 use libgbl::partition::GblDisk;
27 
28 /// `EfiBlockDeviceIo` wraps a EFI `BlockIoProtocol` or `BlockIo2Protocol` and implements the
29 /// `BlockIo` interface.
30 pub enum EfiBlockDeviceIo<'a> {
31     Sync(Protocol<'a, BlockIoProtocol>),
32     Async(Protocol<'a, BlockIo2Protocol>),
33 }
34 
35 impl<'a> EfiBlockDeviceIo<'a> {
media(&self) -> EfiBlockIoMedia36     fn media(&self) -> EfiBlockIoMedia {
37         match self {
38             EfiBlockDeviceIo::Sync(v) => v.media(),
39             EfiBlockDeviceIo::Async(v) => v.media(),
40         }
41         .unwrap()
42     }
43 
info(&mut self) -> BlockInfo44     fn info(&mut self) -> BlockInfo {
45         let media = self.media();
46         BlockInfo {
47             block_size: media.block_size as u64,
48             num_blocks: (media.last_block + 1) as u64,
49             alignment: max(1, media.io_align as u64),
50         }
51     }
52 }
53 
54 // SAFETY:
55 // `read_blocks()` usess EFI protocol that guarantees to read exact number of blocks that were
56 // requested, or return error.
57 // For async `read_blocks_ex()` blocking wait guarantees that read finishes.
58 unsafe impl BlockIo for EfiBlockDeviceIo<'_> {
info(&mut self) -> BlockInfo59     fn info(&mut self) -> BlockInfo {
60         (*self).info()
61     }
62 
read_blocks( &mut self, blk_offset: u64, out: &mut (impl SliceMaybeUninit + ?Sized), ) -> Result<(), Error>63     async fn read_blocks(
64         &mut self,
65         blk_offset: u64,
66         out: &mut (impl SliceMaybeUninit + ?Sized),
67     ) -> Result<(), Error> {
68         match self {
69             EfiBlockDeviceIo::Sync(v) => v.read_blocks(blk_offset, out),
70             EfiBlockDeviceIo::Async(v) => v.read_blocks_ex(blk_offset, out).await,
71         }
72         .or(Err(Error::BlockIoError))
73     }
74 
write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<(), Error>75     async fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<(), Error> {
76         match self {
77             EfiBlockDeviceIo::Sync(v) => v.write_blocks(blk_offset, data),
78             EfiBlockDeviceIo::Async(v) => v.write_blocks_ex(blk_offset, data).await,
79         }
80         .or(Err(Error::BlockIoError))
81     }
82 }
83 
84 const MAX_GPT_ENTRIES: usize = 128;
85 
86 /// The [GblDisk] type in the GBL EFI context.
87 pub type EfiGblDisk<'a> = GblDisk<Disk<EfiBlockDeviceIo<'a>, Vec<u8>>, Gpt<Vec<u8>>>;
88 
89 /// Finds and returns all EFI devices supporting either EFI_BLOCK_IO or EFI_BLOCK_IO2 protocol.
find_block_devices(efi_entry: &EfiEntry) -> Result<Vec<EfiGblDisk<'_>>, Error>90 pub fn find_block_devices(efi_entry: &EfiEntry) -> Result<Vec<EfiGblDisk<'_>>, Error> {
91     let bs = efi_entry.system_table().boot_services();
92     let block_dev_handles = bs.locate_handle_buffer_by_protocol::<BlockIoProtocol>()?;
93     let mut gbl_disks = vec![];
94     let gpt_buffer_size = gpt_buffer_size(MAX_GPT_ENTRIES)?;
95     for (idx, handle) in block_dev_handles.handles().iter().enumerate() {
96         // Prioritizes `BlockIo2Protocol`.
97         let blk_io = match bs.open_protocol::<BlockIo2Protocol>(*handle) {
98             Ok(v) => EfiBlockDeviceIo::Async(v),
99             _ => EfiBlockDeviceIo::Sync(bs.open_protocol::<BlockIoProtocol>(*handle)?),
100         };
101         if blk_io.media().logical_partition {
102             continue;
103         }
104         // TODO(b/357688291): Support raw partition based on device path info.
105         let disk = GblDisk::new_gpt(
106             Disk::new_alloc_scratch(blk_io).unwrap(),
107             Gpt::new(vec![0u8; gpt_buffer_size]).unwrap(),
108         );
109         match block_on(disk.sync_gpt()) {
110             Ok(Some(v)) => efi_println!(efi_entry, "Block #{idx} GPT sync result: {v}"),
111             Err(e) => efi_println!(efi_entry, "Block #{idx} error while syncing GPT: {e}"),
112             _ => {}
113         };
114         gbl_disks.push(disk);
115     }
116     Ok(gbl_disks)
117 }
118