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