xref: /aosp_15_r20/bootable/libbootloader/gbl/libgbl/src/android_boot/mod.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 //! Android boot support.
16 
17 use crate::{
18     device_tree::{DeviceTreeComponentSource, DeviceTreeComponentsRegistry, FDT_ALIGNMENT},
19     gbl_avb::{
20         ops::GblAvbOps,
21         state::{BootStateColor, KeyValidationStatus},
22     },
23     gbl_print, gbl_println, GblOps, IntegrationError, Result,
24 };
25 use arrayvec::ArrayVec;
26 use avb::{slot_verify, HashtreeErrorMode, Ops as _, SlotVerifyFlags};
27 use bootimg::{BootImage, VendorImageHeader};
28 use bootparams::{bootconfig::BootConfigBuilder, commandline::CommandlineBuilder};
29 use core::{ffi::CStr, fmt::Write};
30 use dttable::DtTableImage;
31 use fdt::Fdt;
32 use liberror::Error;
33 use libutils::aligned_subslice;
34 use misc::{AndroidBootMode, BootloaderMessage};
35 use safemath::SafeNum;
36 use zerocopy::{AsBytes, ByteSlice};
37 
38 #[cfg(target_arch = "aarch64")]
39 use crate::decompress::decompress_kernel;
40 
41 /// Device tree bootargs property to store kernel command line.
42 pub const BOOTARGS_PROP: &CStr = c"bootargs";
43 /// Linux kernel requires 2MB alignment.
44 const KERNEL_ALIGNMENT: usize = 2 * 1024 * 1024;
45 
46 /// A helper to convert a bytes slice containing a null-terminated string to `str`
cstr_bytes_to_str(data: &[u8]) -> core::result::Result<&str, Error>47 fn cstr_bytes_to_str(data: &[u8]) -> core::result::Result<&str, Error> {
48     Ok(CStr::from_bytes_until_nul(data)?.to_str()?)
49 }
50 
51 /// Helper function for performing libavb verification.
52 ///
53 /// Currently this requires the caller to preload all relevant images from disk; in its final
54 /// state `ops` will provide the necessary callbacks for where the images should go in RAM and
55 /// which ones are preloaded.
56 ///
57 /// # Arguments
58 /// * `ops`: [GblOps] providing device-specific backend.
59 /// * `kernel`: buffer containing the `boot` image loaded from disk.
60 /// * `vendor_boot`: buffer containing the `vendor_boot` image loaded from disk.
61 /// * `init_boot`: buffer containing the `init_boot` image loaded from disk.
62 /// * `dtbo`: buffer containing the `dtbo` image loaded from disk, if it exists.
63 /// * `bootconfig_builder`: object to write the bootconfig data into.
64 ///
65 /// # Returns
66 /// `()` on success, error if the images fail to verify or we fail to update the bootconfig.
avb_verify_slot<'a, 'b>( ops: &mut impl GblOps<'a, 'b>, kernel: &[u8], vendor_boot: &[u8], init_boot: &[u8], dtbo: Option<&[u8]>, bootconfig_builder: &mut BootConfigBuilder, ) -> Result<()>67 fn avb_verify_slot<'a, 'b>(
68     ops: &mut impl GblOps<'a, 'b>,
69     kernel: &[u8],
70     vendor_boot: &[u8],
71     init_boot: &[u8],
72     dtbo: Option<&[u8]>,
73     bootconfig_builder: &mut BootConfigBuilder,
74 ) -> Result<()> {
75     // We need the list of partition names to verify with libavb, and a corresponding list of
76     // (name, image) tuples to register as [GblAvbOps] preloaded data.
77     let mut partitions = ArrayVec::<_, 4>::new();
78     let mut preloaded = ArrayVec::<_, 4>::new();
79     for (c_name, image) in [
80         (c"boot", Some(kernel)),
81         (c"vendor_boot", Some(vendor_boot)),
82         (c"init_boot", Some(init_boot)),
83         (c"dtbo", dtbo),
84     ] {
85         if let Some(image) = image {
86             partitions.push(c_name);
87             preloaded.push((c_name.to_str().unwrap(), image));
88         }
89     }
90 
91     // TODO(b/337846185): Pass AVB_SLOT_VERIFY_FLAGS_RESTART_CAUSED_BY_HASHTREE_CORRUPTION in
92     // case verity corruption is detected by HLOS.
93     let mut avb_ops = GblAvbOps::new(ops, &preloaded[..], false);
94     let res = slot_verify(
95         &mut avb_ops,
96         &partitions,
97         Some(c"_a"),
98         SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
99         // TODO(b/337846185): For demo, we use the same setting as Cuttlefish u-boot.
100         // Pass AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIO and handle EIO.
101         HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
102     )
103     .map_err(|e| IntegrationError::from(e.without_verify_data()))?;
104 
105     // TODO(b/337846185): Handle RED and RED_EIO (AVB_HASHTREE_ERROR_MODE_EIO).
106     let color = match avb_ops.read_is_device_unlocked()? {
107         false if avb_ops.key_validation_status()? == KeyValidationStatus::ValidCustomKey => {
108             BootStateColor::Yellow
109         }
110         false => BootStateColor::Green,
111         true => BootStateColor::Orange,
112     };
113     avb_ops.handle_verification_result(&res, color)?;
114 
115     // Append avb generated bootconfig.
116     for cmdline_arg in res.cmdline().to_str().unwrap().split(' ') {
117         write!(bootconfig_builder, "{}\n", cmdline_arg).or(Err(Error::BufferTooSmall(None)))?;
118     }
119 
120     // Append "androidboot.verifiedbootstate="
121     write!(bootconfig_builder, "androidboot.verifiedbootstate={}\n", color)
122         .or(Err(Error::BufferTooSmall(None)))?;
123     Ok(())
124 }
125 
126 /// Helper function to parse common fields from boot image headers.
127 ///
128 /// # Returns
129 ///
130 /// Returns a tuple of 6 slices corresponding to:
131 /// (kernel_size, cmdline, page_size, ramdisk_size, second_size, dtb_size)
boot_header_elements<B: ByteSlice + PartialEq>( hdr: &BootImage<B>, ) -> Result<(usize, &str, usize, usize, usize, usize)>132 fn boot_header_elements<B: ByteSlice + PartialEq>(
133     hdr: &BootImage<B>,
134 ) -> Result<(usize, &str, usize, usize, usize, usize)> {
135     const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096;
136     Ok(match hdr {
137         BootImage::V2(ref hdr) => (
138             hdr._base._base.kernel_size as usize,
139             cstr_bytes_to_str(&hdr._base._base.cmdline[..])?,
140             hdr._base._base.page_size as usize,
141             hdr._base._base.ramdisk_size as usize,
142             hdr._base._base.second_size as usize,
143             hdr.dtb_size as usize,
144         ),
145         BootImage::V3(ref hdr) => (
146             hdr.kernel_size as usize,
147             cstr_bytes_to_str(&hdr.cmdline[..])?,
148             PAGE_SIZE,
149             hdr.ramdisk_size as usize,
150             0,
151             0,
152         ),
153         BootImage::V4(ref hdr) => (
154             hdr._base.kernel_size as usize,
155             cstr_bytes_to_str(&hdr._base.cmdline[..])?,
156             PAGE_SIZE,
157             hdr._base.ramdisk_size as usize,
158             0,
159             0,
160         ),
161         _ => {
162             return Err(Error::UnsupportedVersion.into());
163         }
164     })
165 }
166 
167 /// Helper function to parse common fields from vendor image headers.
168 ///
169 /// # Returns
170 ///
171 /// Returns a tuple of 5 slices corresponding to:
172 /// (vendor_ramdisk_size, hdr_size, cmdline, page_size, dtb_size, vendor_bootconfig_size, vendor_ramdisk_table_size)
vendor_header_elements<B: ByteSlice + PartialEq>( hdr: &VendorImageHeader<B>, ) -> Result<(usize, usize, &str, usize, usize, usize, usize)>173 fn vendor_header_elements<B: ByteSlice + PartialEq>(
174     hdr: &VendorImageHeader<B>,
175 ) -> Result<(usize, usize, &str, usize, usize, usize, usize)> {
176     Ok(match hdr {
177         VendorImageHeader::V3(ref hdr) => (
178             hdr.vendor_ramdisk_size as usize,
179             SafeNum::from(hdr.bytes().len())
180                 .round_up(hdr.page_size)
181                 .try_into()
182                 .map_err(Error::from)?,
183             cstr_bytes_to_str(&hdr.cmdline.as_bytes())?,
184             hdr.page_size as usize,
185             hdr.dtb_size as usize,
186             0,
187             0,
188         ),
189         VendorImageHeader::V4(ref hdr) => (
190             hdr._base.vendor_ramdisk_size as usize,
191             SafeNum::from(hdr.bytes().len())
192                 .round_up(hdr._base.page_size)
193                 .try_into()
194                 .map_err(Error::from)?,
195             cstr_bytes_to_str(&hdr._base.cmdline.as_bytes())?,
196             hdr._base.page_size as usize,
197             hdr._base.dtb_size as usize,
198             hdr.bootconfig_size as usize,
199             hdr.vendor_ramdisk_table_size as usize,
200         ),
201     })
202 }
203 
204 /// Loads Android images from disk and fixes up bootconfig, commandline, and FDT.
205 ///
206 /// A number of simplifications are made:
207 ///
208 ///   * No A/B slot switching is performed. It always boot from *_a slot.
209 ///   * No dynamic partitions.
210 ///   * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition)
211 ///   * Only support booting recovery from boot image
212 ///
213 /// # Arguments
214 /// * `ops`: the [GblOps] object providing platform-specific backends.
215 /// * `load`: the combined buffer to load all images into.
216 ///
217 /// # Returns
218 /// Returns a tuple of 4 slices corresponding to:
219 ///   (ramdisk load buffer, FDT load buffer, kernel load buffer, unused buffer).
load_android_simple<'a, 'b, 'c>( ops: &mut impl GblOps<'b, 'c>, load: &'a mut [u8], ) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])>220 pub fn load_android_simple<'a, 'b, 'c>(
221     ops: &mut impl GblOps<'b, 'c>,
222     load: &'a mut [u8],
223 ) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])> {
224     const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096;
225 
226     let (bcb_buffer, load) = load.split_at_mut(BootloaderMessage::SIZE_BYTES);
227     ops.read_from_partition_sync("misc", 0, bcb_buffer)?;
228     let bcb = BootloaderMessage::from_bytes_ref(bcb_buffer)?;
229     let boot_mode = bcb.boot_mode()?;
230     gbl_println!(ops, "boot mode from BCB: {}", boot_mode);
231 
232     // TODO(b/370317273): use high level abstraction over boot to avoid working
233     // with offsets on application level.
234     // Parse boot header.
235     let (boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
236     ops.read_from_partition_sync("boot_a", 0, boot_header_buffer)?;
237     let boot_header = BootImage::parse(boot_header_buffer).map_err(Error::from)?;
238     let (
239         kernel_size,
240         boot_cmdline,
241         kernel_hdr_size,
242         boot_ramdisk_size,
243         boot_second_size,
244         boot_dtb_size,
245     ) = boot_header_elements(&boot_header)?;
246     gbl_println!(ops, "boot image size: {}", kernel_size);
247     gbl_println!(ops, "boot image cmdline: \"{}\"", boot_cmdline);
248     gbl_println!(ops, "boot ramdisk size: {}", boot_ramdisk_size);
249     gbl_println!(ops, "boot dtb size: {}", boot_dtb_size);
250 
251     // TODO(b/370317273): use high level abstraction over vendor_boot to avoid working
252     // with offsets on application level.
253     // Parse vendor boot header.
254     let (vendor_boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
255     let vendor_boot_header;
256     let (
257         vendor_ramdisk_size,
258         vendor_hdr_size,
259         vendor_cmdline,
260         vendor_page_size,
261         vendor_dtb_size,
262         vendor_bootconfig_size,
263         vendor_ramdisk_table_size,
264     ) = match ops.partition_size("vendor_boot_a") {
265         Ok(Some(_sz)) => {
266             ops.read_from_partition_sync("vendor_boot_a", 0, vendor_boot_header_buffer)?;
267             vendor_boot_header =
268                 VendorImageHeader::parse(vendor_boot_header_buffer).map_err(Error::from)?;
269             vendor_header_elements(&vendor_boot_header)?
270         }
271         _ => (0 as usize, 0 as usize, "", 0 as usize, 0 as usize, 0 as usize, 0),
272     };
273 
274     gbl_println!(ops, "vendor ramdisk size: {}", vendor_ramdisk_size);
275     gbl_println!(ops, "vendor cmdline: \"{}\"", vendor_cmdline);
276     gbl_println!(ops, "vendor dtb size: {}", vendor_dtb_size);
277 
278     let (dtbo_buffer, load) = match ops.partition_size("dtbo_a") {
279         Ok(Some(sz)) => {
280             let (dtbo_buffer, load) = load.split_at_mut(sz.try_into().unwrap());
281             ops.read_from_partition_sync("dtbo_a", 0, dtbo_buffer)?;
282             (Some(dtbo_buffer), load)
283         }
284         _ => (None, load),
285     };
286 
287     let mut components: DeviceTreeComponentsRegistry<'a> = DeviceTreeComponentsRegistry::new();
288     let load = match dtbo_buffer {
289         Some(ref dtbo_buffer) => {
290             let dtbo_table = DtTableImage::from_bytes(dtbo_buffer)?;
291             components.append_from_dtbo(&dtbo_table, load)?
292         }
293         _ => load,
294     };
295 
296     // First: check for custom FDT (Cuttlefish).
297     let load = if ops.get_custom_device_tree().is_none() {
298         // Second: "vendor_boot" FDT.
299         let (source, part, offset, size) = if vendor_dtb_size > 0 {
300             // DTB is located after the header and ramdisk (aligned).
301             let offset = (SafeNum::from(vendor_hdr_size) + SafeNum::from(vendor_ramdisk_size))
302                 .round_up(vendor_page_size)
303                 .try_into()
304                 .map_err(Error::from)?;
305             (DeviceTreeComponentSource::VendorBoot, "vendor_boot_a", offset, vendor_dtb_size)
306         // Third: "boot" FDT.
307         } else if boot_dtb_size > 0 {
308             // DTB is located after the header, kernel, ramdisk, and second images (aligned).
309             let mut offset = SafeNum::from(kernel_hdr_size);
310             for image_size in [kernel_size, boot_ramdisk_size, boot_second_size] {
311                 offset += SafeNum::from(image_size).round_up(kernel_hdr_size);
312             }
313             (
314                 DeviceTreeComponentSource::Boot,
315                 "boot_a",
316                 offset.try_into().map_err(Error::from)?,
317                 boot_dtb_size,
318             )
319         } else {
320             return Err(Error::NoFdt.into());
321         };
322 
323         let (fdt_buffer, load) = aligned_subslice(load, FDT_ALIGNMENT)?.split_at_mut(size);
324         ops.read_from_partition_sync(part, offset, fdt_buffer)?;
325         components.append(ops, source, fdt_buffer, load)?
326     } else {
327         load
328     };
329 
330     // Parse init_boot header
331     let init_boot_header_buffer = &mut load[..PAGE_SIZE];
332     let (generic_ramdisk_size, init_boot_hdr_size) = match ops.partition_size("init_boot_a") {
333         Ok(Some(_sz)) => {
334             ops.read_from_partition_sync("init_boot_a", 0, init_boot_header_buffer)?;
335             let init_boot_header =
336                 BootImage::parse(init_boot_header_buffer).map_err(Error::from)?;
337             match init_boot_header {
338                 BootImage::V3(ref hdr) => (hdr.ramdisk_size as usize, PAGE_SIZE),
339                 BootImage::V4(ref hdr) => (hdr._base.ramdisk_size as usize, PAGE_SIZE),
340                 _ => {
341                     gbl_println!(ops, "V0/V1/V2 images are not supported");
342                     return Err(Error::UnsupportedVersion.into());
343                 }
344             }
345         }
346         _ => (0, 0),
347     };
348     gbl_println!(ops, "init_boot image size: {}", generic_ramdisk_size);
349 
350     // Load and prepare various images.
351     let images_buffer = aligned_subslice(load, KERNEL_ALIGNMENT)?;
352     let load = &mut images_buffer[..];
353 
354     // Load kernel
355     // Kernel may need to reserve additional memory after itself. To avoid the risk of this
356     // memory overlapping with ramdisk. We place kernel after ramdisk. We first load it to the tail
357     // of the buffer and move it forward as much as possible after ramdisk and fdt are loaded,
358     // fixed-up and finalized.
359     let boot_img_load_offset: usize = {
360         let off = SafeNum::from(load.len()) - kernel_size - boot_ramdisk_size;
361         let off_idx: usize = off.try_into().map_err(Error::from)?;
362         let aligned_off = off - (&load[off_idx] as *const _ as usize % KERNEL_ALIGNMENT);
363         aligned_off.try_into().map_err(Error::from)?
364     };
365     let (load, boot_img_buffer) = load.split_at_mut(boot_img_load_offset);
366     ops.read_from_partition_sync(
367         "boot_a",
368         kernel_hdr_size.try_into().unwrap(),
369         &mut boot_img_buffer[..kernel_size + boot_ramdisk_size],
370     )?;
371 
372     // Load vendor ramdisk
373     let mut ramdisk_load_curr = SafeNum::ZERO;
374     if vendor_ramdisk_size > 0 {
375         ops.read_from_partition_sync(
376             "vendor_boot_a",
377             u64::try_from(vendor_hdr_size).map_err(Error::from)?,
378             &mut load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..vendor_ramdisk_size],
379         )?;
380     }
381     ramdisk_load_curr += vendor_ramdisk_size;
382 
383     // Load generic ramdisk
384     if generic_ramdisk_size > 0 {
385         ops.read_from_partition_sync(
386             "init_boot_a",
387             init_boot_hdr_size.try_into().unwrap(),
388             &mut load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..generic_ramdisk_size],
389         )?;
390         ramdisk_load_curr += generic_ramdisk_size;
391     }
392 
393     // Load ramdisk from boot image
394     if boot_ramdisk_size > 0 {
395         load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..boot_ramdisk_size]
396             .copy_from_slice(&boot_img_buffer[kernel_size..][..boot_ramdisk_size]);
397         ramdisk_load_curr += boot_ramdisk_size;
398     }
399 
400     // Prepare partition data for avb verification
401     let (vendor_boot_load_buffer, remains) = load.split_at_mut(vendor_ramdisk_size);
402     let (init_boot_load_buffer, remains) = remains.split_at_mut(generic_ramdisk_size);
403     let (_boot_ramdisk_load_buffer, remains) = remains.split_at_mut(boot_ramdisk_size);
404     // Prepare a BootConfigBuilder to add avb generated bootconfig.
405     let mut bootconfig_builder = BootConfigBuilder::new(remains)?;
406     // Perform avb verification.
407     avb_verify_slot(
408         ops,
409         boot_img_buffer,
410         vendor_boot_load_buffer,
411         init_boot_load_buffer,
412         dtbo_buffer.as_deref(),
413         &mut bootconfig_builder,
414     )?;
415 
416     // Move kernel to end of the boot image buffer
417     let (_boot_img_buffer, kernel_tail_buffer) = {
418         let off = SafeNum::from(boot_img_buffer.len()) - kernel_size;
419         let off_idx: usize = off.try_into().map_err(Error::from)?;
420         let aligned_off = off - (&boot_img_buffer[off_idx] as *const _ as usize % KERNEL_ALIGNMENT);
421         let aligned_off_idx = aligned_off.try_into().map_err(Error::from)?;
422         boot_img_buffer.copy_within(0..kernel_size, aligned_off_idx);
423         boot_img_buffer.split_at_mut(aligned_off_idx)
424     };
425 
426     // Add slot index
427     bootconfig_builder.add("androidboot.slot_suffix=_a\n")?;
428 
429     match boot_mode {
430         // TODO(b/329716686): Support bootloader mode
431         AndroidBootMode::Normal | AndroidBootMode::BootloaderBootOnce => {
432             bootconfig_builder.add("androidboot.force_normal_boot=1\n")?
433         }
434         _ => {
435             // Do nothing
436         }
437     }
438 
439     // V4 image has vendor bootconfig.
440     if vendor_bootconfig_size > 0 {
441         let mut bootconfig_offset = SafeNum::from(vendor_hdr_size);
442         for image_size in [vendor_ramdisk_size, vendor_dtb_size, vendor_ramdisk_table_size] {
443             bootconfig_offset += SafeNum::from(image_size).round_up(vendor_page_size);
444         }
445         bootconfig_builder.add_with(|_, out| {
446             ops.read_from_partition_sync(
447                 "vendor_boot_a",
448                 bootconfig_offset.try_into()?,
449                 &mut out[..vendor_bootconfig_size as usize],
450             )?;
451             Ok(vendor_bootconfig_size as usize)
452         })?;
453     }
454 
455     // TODO(b/353272981): Handle buffer too small
456     bootconfig_builder.add_with(|bytes, out| {
457         // TODO(b/353272981): Verify provided bootconfig and fail here
458         Ok(ops.fixup_bootconfig(&bytes, out)?.map(|slice| slice.len()).unwrap_or(0))
459     })?;
460     gbl_println!(ops, "final bootconfig: \"{}\"", bootconfig_builder);
461 
462     ramdisk_load_curr += bootconfig_builder.config_bytes().len();
463 
464     // On ARM, we may need to decompress the kernel and re-split the buffer to the new kernel size.
465     #[cfg(target_arch = "aarch64")]
466     let (load, kernel_size, kernel_tail_buffer) = {
467         let kernel_size = kernel_tail_buffer.len();
468         let compressed_kernel_offset = images_buffer.len() - kernel_size;
469         let decompressed_kernel_offset =
470             decompress_kernel(ops, images_buffer, compressed_kernel_offset)?;
471         let (load, kernel_tail_buffer) = images_buffer.split_at_mut(decompressed_kernel_offset);
472         (load, kernel_tail_buffer.len(), kernel_tail_buffer)
473     };
474 
475     // Use the remaining load buffer for the FDT.
476     let (ramdisk_load_buffer, load) =
477         load.split_at_mut(ramdisk_load_curr.try_into().map_err(Error::from)?);
478 
479     let (base, overlays): (&[u8], &[&[u8]]) = if let Some(custom_fdt) = ops.get_custom_device_tree()
480     {
481         (custom_fdt, &[])
482     } else {
483         ops.select_device_trees(&mut components)?;
484         components.selected()?
485     };
486 
487     let fdt_buffer = aligned_subslice(load, FDT_ALIGNMENT)?;
488     let mut fdt = Fdt::new_from_init(fdt_buffer, base)?;
489 
490     gbl_println!(ops, "Applying {} overlays", overlays.len());
491     fdt.multioverlay_apply(overlays)?;
492     gbl_println!(ops, "Overlays applied");
493 
494     // Add ramdisk range to FDT
495     let ramdisk_addr: u64 =
496         (ramdisk_load_buffer.as_ptr() as usize).try_into().map_err(Error::from)?;
497     let ramdisk_end: u64 =
498         ramdisk_addr + u64::try_from(ramdisk_load_buffer.len()).map_err(Error::from)?;
499     fdt.set_property("chosen", c"linux,initrd-start", &ramdisk_addr.to_be_bytes())?;
500     fdt.set_property("chosen", c"linux,initrd-end", &ramdisk_end.to_be_bytes())?;
501     gbl_println!(ops, "linux,initrd-start: {:#x}", ramdisk_addr);
502     gbl_println!(ops, "linux,initrd-end: {:#x}", ramdisk_end);
503 
504     // Update the FDT commandline.
505     let device_tree_commandline_length = match fdt.get_property("chosen", BOOTARGS_PROP) {
506         Ok(val) => CStr::from_bytes_until_nul(val).map_err(Error::from)?.to_bytes().len(),
507         Err(_) => 0,
508     };
509 
510     // Reserve 1024 bytes for separators and fixup.
511     let final_commandline_len =
512         device_tree_commandline_length + boot_cmdline.len() + vendor_cmdline.len() + 1024;
513     let final_commandline_buffer =
514         fdt.set_property_placeholder("chosen", BOOTARGS_PROP, final_commandline_len)?;
515 
516     let mut commandline_builder =
517         CommandlineBuilder::new_from_prefix(&mut final_commandline_buffer[..])?;
518     commandline_builder.add(boot_cmdline)?;
519     commandline_builder.add(vendor_cmdline)?;
520 
521     // TODO(b/353272981): Handle buffer too small
522     commandline_builder.add_with(|current, out| {
523         // TODO(b/353272981): Verify provided command line and fail here.
524         Ok(ops.fixup_os_commandline(current, out)?.map(|fixup| fixup.len()).unwrap_or(0))
525     })?;
526     gbl_println!(ops, "final cmdline: \"{}\"", commandline_builder.as_str());
527 
528     // Make sure we provide an actual device tree size, so FW can calculate amount of space
529     // available for fixup.
530     fdt.shrink_to_fit()?;
531     // TODO(b/353272981): Make a copy of current device tree and verify provided fixup.
532     // TODO(b/353272981): Handle buffer too small
533     ops.fixup_device_tree(fdt.as_mut())?;
534     fdt.shrink_to_fit()?;
535 
536     // Move the kernel backward as much as possible to preserve more space after it. This is
537     // necessary in case the input buffer is at the end of address space.
538     let kernel_tail_buffer_size = kernel_tail_buffer.len();
539     let ramdisk_load_buffer_size = ramdisk_load_buffer.len();
540     let fdt_len = fdt.header_ref()?.actual_size();
541     // Split out the ramdisk.
542     let (ramdisk, remains) = images_buffer.split_at_mut(ramdisk_load_buffer_size);
543     // Split out the fdt.
544     let (fdt, kernel) = aligned_subslice(remains, FDT_ALIGNMENT)?.split_at_mut(fdt_len);
545     // Move the kernel backward as much as possible.
546     let kernel = aligned_subslice(kernel, KERNEL_ALIGNMENT)?;
547     let kernel_start = kernel.len().checked_sub(kernel_tail_buffer_size).unwrap();
548     kernel.copy_within(kernel_start..kernel_start.checked_add(kernel_size).unwrap(), 0);
549     // Split out the remaining buffer.
550     let (kernel, remains) = kernel.split_at_mut(kernel_size);
551 
552     Ok((ramdisk, fdt, kernel, remains))
553 }
554