xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/protocol/block_io2.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_BLOCK_IO2_PROTOCOL`.
16 
17 use crate::{
18     efi_call,
19     protocol::{Protocol, ProtocolInfo},
20     Event, EventNotify, EventType, Tpl,
21 };
22 use efi_types::{
23     EfiBlockIo2Protocol, EfiBlockIo2Token, EfiBlockIoMedia, EfiGuid, EFI_STATUS_NOT_READY,
24 };
25 use gbl_async::{assert_return, yield_now};
26 use gbl_storage::SliceMaybeUninit;
27 use liberror::{efi_status_to_result, Error, Result};
28 
29 /// EFI_BLOCK_IO2_PROTOCOL
30 pub struct BlockIo2Protocol;
31 
32 impl ProtocolInfo for BlockIo2Protocol {
33     type InterfaceType = EfiBlockIo2Protocol;
34 
35     const GUID: EfiGuid =
36         EfiGuid::new(0xa77b2472, 0xe282, 0x4e9f, [0xa2, 0x45, 0xc2, 0xc0, 0xe2, 0x7b, 0xbc, 0xc1]);
37 }
38 
39 // Protocol interface wrappers.
40 impl Protocol<'_, BlockIo2Protocol> {
41     /// Syncs a non-blocking operation by waiting for the corresponding EFI event to be signaled.
wait_io_completion(&self, event: &Event<'_, '_>) -> Result<()>42     async fn wait_io_completion(&self, event: &Event<'_, '_>) -> Result<()> {
43         let bs = self.efi_entry().system_table().boot_services();
44         loop {
45             match bs.check_event(&event) {
46                 Err(e) => {
47                     // If we fail to check event/status, force reset the device to release any
48                     // retained user buffer. The reset cannot fail.
49                     self.reset(true).unwrap();
50                     return Err(e);
51                 }
52                 Ok(true) => return Ok(()),
53                 _ => yield_now().await,
54             }
55         }
56     }
57 
58     /// Wraps `EfiBlockIo2Protocol.read_blocks_ex`.
read_blocks_ex( &self, lba: u64, buffer: &mut (impl SliceMaybeUninit + ?Sized), ) -> Result<()>59     pub async fn read_blocks_ex(
60         &self,
61         lba: u64,
62         buffer: &mut (impl SliceMaybeUninit + ?Sized),
63     ) -> Result<()> {
64         let bs = self.efi_entry().system_table().boot_services();
65         // UEFI spec requires that NOTIFY_WAIT event be always created with a callback.
66         let mut notify_fn = &mut |_| ();
67         let mut notify = EventNotify::new(Tpl::Callback, &mut notify_fn);
68         // SAFETY: the notification callback never allocates, deallocates, or panics.
69         let event =
70             unsafe { bs.create_event_with_notification(EventType::NotifyWait, &mut notify) }?;
71         let mut token =
72             EfiBlockIo2Token { event: event.efi_event, transaction_status: EFI_STATUS_NOT_READY };
73         // SAFETY:
74         // * `self.interface()?` guarantees self.interface is non-null and points to a valid object
75         //    established by `Protocol::new()`.
76         // * `self.interface` is input parameter and will not be retained. It outlives the call.
77         // * `Self::wait_io_completion()` is called immediately after. It makes sure the IO is
78         //   either completed successfully or is reset if `check_event` fails. Thus it's
79         //   guaranteed that after `Self::wait_io_completion()` returns, `buffer` and `token` are
80         //   not being retained by the UEFI firmware anymore.
81         // * `assert_return` asserts that `wait_io_completion` returns eventually. Otherwise it
82         //   panics if the top level Future gets dropped before it returns.
83         unsafe {
84             efi_call!(
85                 self.interface()?.read_blocks_ex,
86                 self.interface,
87                 self.media()?.media_id,
88                 lba,
89                 &mut token,
90                 buffer.len(),
91                 buffer.as_mut().as_mut_ptr() as _
92             )?;
93         }
94         assert_return(self.wait_io_completion(&event)).await?;
95         efi_status_to_result(token.transaction_status)
96     }
97 
98     /// Wraps `EfiBlockIo2Protocol.write_blocks_ex`.
write_blocks_ex(&self, lba: u64, buffer: &mut [u8]) -> Result<()>99     pub async fn write_blocks_ex(&self, lba: u64, buffer: &mut [u8]) -> Result<()> {
100         let bs = self.efi_entry().system_table().boot_services();
101         let mut notify_fn = &mut |_| ();
102         let mut notify = EventNotify::new(Tpl::Callback, &mut notify_fn);
103         // SAFETY: the notification callback never allocates, deallocates, or panics.
104         let event =
105             unsafe { bs.create_event_with_notification(EventType::NotifyWait, &mut notify) }?;
106         let mut token =
107             EfiBlockIo2Token { event: event.efi_event, transaction_status: EFI_STATUS_NOT_READY };
108         // SAFETY: See safety comment for `Self::read_blocks_ex()`.
109         unsafe {
110             efi_call!(
111                 self.interface()?.write_blocks_ex,
112                 self.interface,
113                 self.media()?.media_id,
114                 lba,
115                 &mut token,
116                 buffer.len(),
117                 buffer.as_mut_ptr() as _
118             )?;
119         }
120         assert_return(self.wait_io_completion(&event)).await?;
121         efi_status_to_result(token.transaction_status)
122     }
123 
124     /// Wraps `EFI_BLOCK_IO2_PROTOCOL.flush_blocks_ex()`
flush_blocks_ex(&self) -> Result<()>125     pub async fn flush_blocks_ex(&self) -> Result<()> {
126         let bs = self.efi_entry().system_table().boot_services();
127         let mut notify_fn = &mut |_| ();
128         let mut notify = EventNotify::new(Tpl::Callback, &mut notify_fn);
129         // SAFETY: the notification callback never allocates, deallocates, or panics.
130         let event =
131             unsafe { bs.create_event_with_notification(EventType::NotifyWait, &mut notify) }?;
132         let mut token =
133             EfiBlockIo2Token { event: event.efi_event, transaction_status: EFI_STATUS_NOT_READY };
134         // SAFETY: See safety comment for `Self::read_blocks_ex()`.
135         unsafe { efi_call!(self.interface()?.flush_blocks_ex, self.interface, &mut token) }?;
136         assert_return(self.wait_io_completion(&event)).await?;
137         efi_status_to_result(token.transaction_status)
138     }
139 
140     /// Wraps `EFI_BLOCK_IO2_PROTOCOL.reset()`
reset(&self, extended_verification: bool) -> Result<()>141     pub fn reset(&self, extended_verification: bool) -> Result<()> {
142         // SAFETY:
143         // * See safety comment for `Self::read_blocks_ex()`.
144         // * The operation is synchronous, no need to call wait_io_completion().
145         unsafe { efi_call!(self.interface()?.reset, self.interface, extended_verification) }
146     }
147 
148     /// Gets a copy of the `EFI_BLOCK_IO2_PROTOCOL.Media` structure.
media(&self) -> Result<EfiBlockIoMedia>149     pub fn media(&self) -> Result<EfiBlockIoMedia> {
150         let ptr = self.interface()?.media;
151         // SAFETY: Pointers to EFI data structure.
152         Ok(*unsafe { ptr.as_ref() }.ok_or(Error::InvalidInput)?)
153     }
154 }
155