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 //! GblOps trait that defines GBL callbacks. 16 //! 17 #[cfg(feature = "alloc")] 18 extern crate alloc; 19 20 use crate::error::{Error, Result as GblResult}; 21 #[cfg(feature = "alloc")] 22 use alloc::ffi::CString; 23 use core::{ 24 fmt::{Debug, Write}, 25 result::Result, 26 }; 27 use gbl_storage::{ 28 required_scratch_size, AsBlockDevice, AsMultiBlockDevices, BlockDevice, BlockIo, 29 }; 30 use safemath::SafeNum; 31 32 use super::slots; 33 34 /// `AndroidBootImages` contains references to loaded images for booting Android. 35 pub struct AndroidBootImages<'a> { 36 pub kernel: &'a mut [u8], 37 pub ramdisk: &'a mut [u8], 38 pub fdt: &'a mut [u8], 39 } 40 41 /// `FuchsiaBootImages` contains references to loaded images for booting Zircon. 42 pub struct FuchsiaBootImages<'a> { 43 pub zbi_kernel: &'a mut [u8], 44 pub zbi_items: &'a mut [u8], 45 } 46 47 /// `BootImages` contains images for booting Android/Zircon kernel. 48 pub enum BootImages<'a> { 49 Android(AndroidBootImages<'a>), 50 Fuchsia(FuchsiaBootImages<'a>), 51 } 52 53 /// `GblOpsError` is the error type returned by required methods in `GblOps`. 54 #[derive(Default, Debug, PartialEq, Eq)] 55 pub struct GblOpsError(Option<&'static str>); 56 57 // https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust 58 // should we use traits for this? or optional/box FnMut? 59 // 60 /* TODO: b/312612203 - needed callbacks: 61 missing: 62 - validate_public_key_for_partition: None, 63 - key management => atx extension in callback => atx_ops: ptr::null_mut(), // support optional ATX. 64 */ 65 /// Trait that defines callbacks that can be provided to Gbl. 66 pub trait GblOps { 67 /// Iterates block devices on the platform. 68 /// 69 /// For each block device, implementation should call `f` with its 1) `BlockIo` trait 70 /// implementation, 2) a unique u64 ID and 3) maximum number of gpt entries. If the maximum 71 /// entries is 0, it is considered that the block should not use GPT. 72 /// 73 /// The list of block devices and visit order should remain the same for the life time of the 74 /// object that implements this trait. If this can not be met due to media change, error should 75 /// be returned. Dynamic media change is not supported for now. visit_block_devices( &mut self, f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64), ) -> Result<(), GblOpsError>76 fn visit_block_devices( 77 &mut self, 78 f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64), 79 ) -> Result<(), GblOpsError>; 80 81 /// Prints a ASCII character to the platform console. console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError>82 fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError>; 83 84 /// This method can be used to implement platform specific mechanism for deciding whether boot 85 /// should abort and enter Fastboot mode. should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError>86 fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError>; 87 88 /// Platform specific kernel boot implementation. 89 /// 90 /// Implementation is not expected to return on success. boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError>91 fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError>; 92 93 // TODO(b/334962570): figure out how to plumb ops-provided hash implementations into 94 // libavb. The tricky part is that libavb hashing APIs are global with no way to directly 95 // correlate the implementation to a particular [GblOps] object, so we'll probably have to 96 // create a [Context] ahead of time and store it globally for the hashing APIs to access. 97 // However this would mean that [Context] must be a standalone object and cannot hold a 98 // reference to [GblOps], which may restrict implementations. 99 // fn new_digest(&self) -> Option<Self::Context>; 100 101 /// Callback for when fastboot mode is requested. 102 // Nevertype could be used here when it is stable https://github.com/serde-rs/serde/issues/812 do_fastboot<B: gbl_storage::AsBlockDevice>( &self, cursor: &mut slots::Cursor<B, impl slots::Manager>, ) -> GblResult<()>103 fn do_fastboot<B: gbl_storage::AsBlockDevice>( 104 &self, 105 cursor: &mut slots::Cursor<B, impl slots::Manager>, 106 ) -> GblResult<()> { 107 Err(Error::NotImplemented.into()) 108 } 109 110 /// TODO: b/312607649 - placeholder interface for Gbl specific callbacks that uses alloc. 111 #[cfg(feature = "alloc")] gbl_alloc_extra_action(&mut self, s: &str) -> GblResult<()>112 fn gbl_alloc_extra_action(&mut self, s: &str) -> GblResult<()> { 113 let _c_string = CString::new(s); 114 Err(Error::NotImplemented.into()) 115 } 116 117 /// Load and initialize a slot manager and return a cursor over the manager on success. load_slot_interface<'b, B: gbl_storage::AsBlockDevice, M: slots::Manager>( &mut self, block_device: &'b mut B, boot_token: slots::BootToken, ) -> GblResult<slots::Cursor<'b, B, M>>118 fn load_slot_interface<'b, B: gbl_storage::AsBlockDevice, M: slots::Manager>( 119 &mut self, 120 block_device: &'b mut B, 121 boot_token: slots::BootToken, 122 ) -> GblResult<slots::Cursor<'b, B, M>> { 123 Err(Error::OperationProhibited.into()) 124 } 125 126 /// Computes the sum of required scratch size for all block devices. required_scratch_size(&mut self) -> GblResult<usize>127 fn required_scratch_size(&mut self) -> GblResult<usize> { 128 let mut total = SafeNum::ZERO; 129 let mut res = Ok(()); 130 self.visit_block_devices(&mut |io, id, max_gpt_entries| { 131 res = (|| { 132 total += required_scratch_size(io, max_gpt_entries).unwrap(); 133 Ok(()) 134 })(); 135 })?; 136 137 let total = usize::try_from(total).map_err(|e| e.into()); 138 res.and(total) 139 } 140 } 141 142 /// `GblUtils` takes a reference to `GblOps` and implements various traits. 143 pub(crate) struct GblUtils<'a, 'b, T: GblOps> { 144 ops: &'a mut T, 145 scratch: &'b mut [u8], 146 } 147 148 impl<'a, 'b, T: GblOps> GblUtils<'a, 'b, T> { 149 /// Create a new instance with user provided scratch buffer. 150 /// 151 /// # Args 152 /// 153 /// * `ops`: A reference to a `GblOps`, 154 /// * `scratch`: A scratch buffer. 155 /// 156 /// # Returns 157 /// 158 /// Returns a new instance and the trailing unused part of the input scratch buffer. new(ops: &'a mut T, scratch: &'b mut [u8]) -> GblResult<(Self, &'b mut [u8])>159 pub fn new(ops: &'a mut T, scratch: &'b mut [u8]) -> GblResult<(Self, &'b mut [u8])> { 160 let total_scratch_size = ops.required_scratch_size()?; 161 let (scratch, remaining) = scratch.split_at_mut(total_scratch_size); 162 Ok((Self { ops: ops, scratch: scratch }, remaining)) 163 } 164 } 165 166 impl<T: GblOps> AsMultiBlockDevices for GblUtils<'_, '_, T> { for_each( &mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64), ) -> core::result::Result<(), Option<&'static str>>167 fn for_each( 168 &mut self, 169 f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64), 170 ) -> core::result::Result<(), Option<&'static str>> { 171 let mut scratch_offset = SafeNum::ZERO; 172 self.ops 173 .visit_block_devices(&mut |io, id, max_gpt_entries| { 174 // Not expected to fail as `Self::new()` should have checked any overflow. 175 let scratch_size: usize = required_scratch_size(io, max_gpt_entries).unwrap(); 176 let scratch = 177 &mut self.scratch[scratch_offset.try_into().unwrap()..][..scratch_size]; 178 scratch_offset += scratch_size; 179 f(&mut BlockDevice::new(io, scratch, max_gpt_entries), id); 180 }) 181 .map_err(|v| v.0) 182 } 183 } 184 185 impl<T: GblOps> Write for GblUtils<'_, '_, T> { write_str(&mut self, s: &str) -> core::fmt::Result186 fn write_str(&mut self, s: &str) -> core::fmt::Result { 187 for ch in s.as_bytes() { 188 self.ops.console_put_char(*ch).map_err(|_| core::fmt::Error {})?; 189 } 190 Ok(()) 191 } 192 } 193 194 /// Default [GblOps] implementation that returns errors and does nothing. 195 #[derive(Debug)] 196 pub struct DefaultGblOps {} 197 198 impl GblOps for DefaultGblOps { visit_block_devices( &mut self, f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64), ) -> Result<(), GblOpsError>199 fn visit_block_devices( 200 &mut self, 201 f: &mut dyn FnMut(&mut dyn BlockIo, u64, u64), 202 ) -> Result<(), GblOpsError> { 203 Err(GblOpsError(Some("unimplemented"))) 204 } 205 console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError>206 fn console_put_char(&mut self, ch: u8) -> Result<(), GblOpsError> { 207 Err(GblOpsError(Some("unimplemented"))) 208 } 209 should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError>210 fn should_stop_in_fastboot(&mut self) -> Result<bool, GblOpsError> { 211 Err(GblOpsError(Some("unimplemented"))) 212 } 213 boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError>214 fn boot(&mut self, boot_images: BootImages) -> Result<(), GblOpsError> { 215 Err(GblOpsError(Some("unimplemented"))) 216 } 217 } 218