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