| // Copyright 2024, The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! Android boot support. |
| |
| use crate::{avb_ops::GblAvbOps, gbl_print, gbl_println, GblOps, IntegrationError, Result}; |
| use arrayvec::ArrayVec; |
| use avb::{slot_verify, HashtreeErrorMode, Ops as _, SlotVerifyFlags}; |
| use bootimg::{BootImage, VendorImageHeader}; |
| use bootparams::{bootconfig::BootConfigBuilder, commandline::CommandlineBuilder}; |
| use core::{ffi::CStr, fmt::Write}; |
| use fdt::Fdt; |
| use liberror::Error; |
| use libutils::aligned_subslice; |
| use misc::{AndroidBootMode, BootloaderMessage}; |
| use safemath::SafeNum; |
| use zerocopy::{AsBytes, ByteSlice}; |
| |
| #[cfg(target_arch = "aarch64")] |
| use crate::decompress::decompress_kernel; |
| |
| /// Device tree bootargs property to store kernel command line. |
| pub const BOOTARGS_PROP: &CStr = c"bootargs"; |
| /// Linux kernel requires 2MB alignment. |
| const KERNEL_ALIGNMENT: usize = 2 * 1024 * 1024; |
| /// libfdt requires FDT buffer to be 8-byte aligned. |
| const FDT_ALIGNMENT: usize = 8; |
| |
| /// A helper to convert a bytes slice containing a null-terminated string to `str` |
| fn cstr_bytes_to_str(data: &[u8]) -> core::result::Result<&str, Error> { |
| Ok(CStr::from_bytes_until_nul(data)?.to_str()?) |
| } |
| |
| /// Helper function for performing libavb verification. |
| /// |
| /// Currently this requires the caller to preload all relevant images from disk; in its final |
| /// state `ops` will provide the necessary callbacks for where the images should go in RAM and |
| /// which ones are preloaded. |
| /// |
| /// # Arguments |
| /// * `ops`: [GblOps] providing device-specific backend. |
| /// * `kernel`: buffer containing the `boot` image loaded from disk. |
| /// * `vendor_boot`: buffer containing the `vendor_boot` image loaded from disk. |
| /// * `init_boot`: buffer containing the `init_boot` image loaded from disk. |
| /// * `dtbo`: buffer containing the `dtbo` image loaded from disk, if it exists. |
| /// * `bootconfig_builder`: object to write the bootconfig data into. |
| /// |
| /// # Returns |
| /// `()` on success, error if the images fail to verify or we fail to update the bootconfig. |
| fn avb_verify_slot<'a>( |
| ops: &mut impl GblOps<'a>, |
| kernel: &[u8], |
| vendor_boot: &[u8], |
| init_boot: &[u8], |
| dtbo: Option<&[u8]>, |
| bootconfig_builder: &mut BootConfigBuilder, |
| ) -> Result<()> { |
| // We need the list of partition names to verify with libavb, and a corresponding list of |
| // (name, image) tuples to register as [GblAvbOps] preloaded data. |
| let mut partitions = ArrayVec::<_, 4>::new(); |
| let mut preloaded = ArrayVec::<_, 4>::new(); |
| for (c_name, image) in [ |
| (c"boot", Some(kernel)), |
| (c"vendor_boot", Some(vendor_boot)), |
| (c"init_boot", Some(init_boot)), |
| (c"dtbo", dtbo), |
| ] { |
| if let Some(image) = image { |
| partitions.push(c_name); |
| preloaded.push((c_name.to_str().unwrap(), image)); |
| } |
| } |
| |
| let mut avb_ops = GblAvbOps::new(ops, &preloaded[..], false); |
| let avb_state = match avb_ops.read_is_device_unlocked()? { |
| true => "orange", |
| _ => "green", |
| }; |
| |
| let res = slot_verify( |
| &mut avb_ops, |
| &partitions, |
| Some(c"_a"), |
| SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, |
| // For demo, we use the same setting as Cuttlefish u-boot. |
| HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, |
| ) |
| .map_err(|e| IntegrationError::from(e.without_verify_data()))?; |
| |
| // Append avb generated bootconfig. |
| for cmdline_arg in res.cmdline().to_str().unwrap().split(' ') { |
| write!(bootconfig_builder, "{}\n", cmdline_arg).or(Err(Error::BufferTooSmall(None)))?; |
| } |
| |
| // Append "androidboot.verifiedbootstate=" |
| write!(bootconfig_builder, "androidboot.verifiedbootstate={}\n", avb_state) |
| .or(Err(Error::BufferTooSmall(None)))?; |
| Ok(()) |
| } |
| |
| /// Helper function to parse common fields from boot image headers. |
| /// |
| /// # Returns |
| /// |
| /// Returns a tuple of 6 slices corresponding to: |
| /// (kernel_size, cmdline, page_size, ramdisk_size, second_size, dtb_size) |
| fn boot_header_elements<B: ByteSlice + PartialEq>( |
| hdr: &BootImage<B>, |
| ) -> Result<(usize, &str, usize, usize, usize, usize)> { |
| const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096; |
| Ok(match hdr { |
| BootImage::V2(ref hdr) => ( |
| hdr._base._base.kernel_size as usize, |
| cstr_bytes_to_str(&hdr._base._base.cmdline[..])?, |
| hdr._base._base.page_size as usize, |
| hdr._base._base.ramdisk_size as usize, |
| hdr._base._base.second_size as usize, |
| hdr.dtb_size as usize, |
| ), |
| BootImage::V3(ref hdr) => ( |
| hdr.kernel_size as usize, |
| cstr_bytes_to_str(&hdr.cmdline[..])?, |
| PAGE_SIZE, |
| hdr.ramdisk_size as usize, |
| 0, |
| 0, |
| ), |
| BootImage::V4(ref hdr) => ( |
| hdr._base.kernel_size as usize, |
| cstr_bytes_to_str(&hdr._base.cmdline[..])?, |
| PAGE_SIZE, |
| hdr._base.ramdisk_size as usize, |
| 0, |
| 0, |
| ), |
| _ => { |
| return Err(Error::UnsupportedVersion.into()); |
| } |
| }) |
| } |
| |
| /// Helper function to parse common fields from vendor image headers. |
| /// |
| /// # Returns |
| /// |
| /// Returns a tuple of 5 slices corresponding to: |
| /// (vendor_ramdisk_size, hdr_size, cmdline, page_size, dtb_size, vendor_bootconfig_size, vendor_ramdisk_table_size) |
| fn vendor_header_elements<B: ByteSlice + PartialEq>( |
| hdr: &VendorImageHeader<B>, |
| ) -> Result<(usize, usize, &str, usize, usize, usize, usize)> { |
| Ok(match hdr { |
| VendorImageHeader::V3(ref hdr) => ( |
| hdr.vendor_ramdisk_size as usize, |
| SafeNum::from(hdr.bytes().len()) |
| .round_up(hdr.page_size) |
| .try_into() |
| .map_err(Error::from)?, |
| cstr_bytes_to_str(&hdr.cmdline.as_bytes())?, |
| hdr.page_size as usize, |
| hdr.dtb_size as usize, |
| 0, |
| 0, |
| ), |
| VendorImageHeader::V4(ref hdr) => ( |
| hdr._base.vendor_ramdisk_size as usize, |
| SafeNum::from(hdr.bytes().len()) |
| .round_up(hdr._base.page_size) |
| .try_into() |
| .map_err(Error::from)?, |
| cstr_bytes_to_str(&hdr._base.cmdline.as_bytes())?, |
| hdr._base.page_size as usize, |
| hdr._base.dtb_size as usize, |
| hdr.bootconfig_size as usize, |
| hdr.vendor_ramdisk_table_size as usize, |
| ), |
| }) |
| } |
| |
| /// Loads Android images from disk and fixes up bootconfig, commandline, and FDT. |
| /// |
| /// A number of simplifications are made: |
| /// |
| /// * No A/B slot switching is performed. It always boot from *_a slot. |
| /// * No dynamic partitions. |
| /// * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition) |
| /// * Only support booting recovery from boot image |
| /// |
| /// # Arguments |
| /// * `ops`: the [GblOps] object providing platform-specific backends. |
| /// * `load`: the combined buffer to load all images into. |
| /// |
| /// # Returns |
| /// Returns a tuple of 4 slices corresponding to: |
| /// (ramdisk load buffer, FDT load buffer, kernel load buffer, unused buffer). |
| pub fn load_android_simple<'a, 'b>( |
| ops: &mut impl GblOps<'b>, |
| load: &'a mut [u8], |
| ) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])> { |
| const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096; |
| |
| let (bcb_buffer, load) = load.split_at_mut(BootloaderMessage::SIZE_BYTES); |
| ops.read_from_partition_sync("misc", 0, bcb_buffer)?; |
| let bcb = BootloaderMessage::from_bytes_ref(bcb_buffer)?; |
| let boot_mode = bcb.boot_mode()?; |
| gbl_println!(ops, "boot mode from BCB: {}", boot_mode); |
| |
| // Parse boot header. |
| let (boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE); |
| ops.read_from_partition_sync("boot_a", 0, boot_header_buffer)?; |
| let boot_header = BootImage::parse(boot_header_buffer).map_err(Error::from)?; |
| let ( |
| kernel_size, |
| boot_cmdline, |
| kernel_hdr_size, |
| boot_ramdisk_size, |
| boot_second_size, |
| boot_dtb_size, |
| ) = boot_header_elements(&boot_header)?; |
| gbl_println!(ops, "boot image size: {}", kernel_size); |
| gbl_println!(ops, "boot image cmdline: \"{}\"", boot_cmdline); |
| gbl_println!(ops, "boot ramdisk size: {}", boot_ramdisk_size); |
| gbl_println!(ops, "boot dtb size: {}", boot_dtb_size); |
| |
| // Parse vendor boot header. |
| let (vendor_boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE); |
| let vendor_boot_header; |
| let ( |
| vendor_ramdisk_size, |
| vendor_hdr_size, |
| vendor_cmdline, |
| vendor_page_size, |
| vendor_dtb_size, |
| vendor_bootconfig_size, |
| vendor_ramdisk_table_size, |
| ) = match ops.partition_size("vendor_boot_a") { |
| Ok(Some(_sz)) => { |
| ops.read_from_partition_sync("vendor_boot_a", 0, vendor_boot_header_buffer)?; |
| vendor_boot_header = |
| VendorImageHeader::parse(vendor_boot_header_buffer).map_err(Error::from)?; |
| vendor_header_elements(&vendor_boot_header)? |
| } |
| _ => (0 as usize, 0 as usize, "", 0 as usize, 0 as usize, 0 as usize, 0), |
| }; |
| |
| gbl_println!(ops, "vendor ramdisk size: {}", vendor_ramdisk_size); |
| gbl_println!(ops, "vendor cmdline: \"{}\"", vendor_cmdline); |
| gbl_println!(ops, "vendor dtb size: {}", vendor_dtb_size); |
| |
| let (dtbo_buffer, load) = match ops.partition_size("dtbo_a") { |
| Ok(Some(sz)) => { |
| let (dtbo_buffer, load) = |
| aligned_subslice(load, FDT_ALIGNMENT)?.split_at_mut(sz.try_into().unwrap()); |
| ops.read_from_partition_sync("dtbo_a", 0, dtbo_buffer)?; |
| (Some(dtbo_buffer), load) |
| } |
| _ => (None, load), |
| }; |
| |
| // Parse init_boot header |
| let init_boot_header_buffer = &mut load[..PAGE_SIZE]; |
| let (generic_ramdisk_size, init_boot_hdr_size) = match ops.partition_size("init_boot_a") { |
| Ok(Some(_sz)) => { |
| ops.read_from_partition_sync("init_boot_a", 0, init_boot_header_buffer)?; |
| let init_boot_header = |
| BootImage::parse(init_boot_header_buffer).map_err(Error::from)?; |
| match init_boot_header { |
| BootImage::V3(ref hdr) => (hdr.ramdisk_size as usize, PAGE_SIZE), |
| BootImage::V4(ref hdr) => (hdr._base.ramdisk_size as usize, PAGE_SIZE), |
| _ => { |
| gbl_println!(ops, "V0/V1/V2 images are not supported"); |
| return Err(Error::UnsupportedVersion.into()); |
| } |
| } |
| } |
| _ => (0, 0), |
| }; |
| gbl_println!(ops, "init_boot image size: {}", generic_ramdisk_size); |
| |
| // Load and prepare various images. |
| let images_buffer = aligned_subslice(load, KERNEL_ALIGNMENT)?; |
| let load = &mut images_buffer[..]; |
| |
| // Load kernel |
| // Kernel may need to reserve additional memory after itself. To avoid the risk of this |
| // memory overlapping with ramdisk. We place kernel after ramdisk. We first load it to the tail |
| // of the buffer and move it forward as much as possible after ramdisk and fdt are loaded, |
| // fixed-up and finalized. |
| let boot_img_load_offset: usize = { |
| let off = SafeNum::from(load.len()) - kernel_size - boot_ramdisk_size; |
| let off_idx: usize = off.try_into().map_err(Error::from)?; |
| let aligned_off = off - (&load[off_idx] as *const _ as usize % KERNEL_ALIGNMENT); |
| aligned_off.try_into().map_err(Error::from)? |
| }; |
| let (load, boot_img_buffer) = load.split_at_mut(boot_img_load_offset); |
| ops.read_from_partition_sync( |
| "boot_a", |
| kernel_hdr_size.try_into().unwrap(), |
| &mut boot_img_buffer[..kernel_size + boot_ramdisk_size], |
| )?; |
| |
| // Load vendor ramdisk |
| let mut ramdisk_load_curr = SafeNum::ZERO; |
| if vendor_ramdisk_size > 0 { |
| ops.read_from_partition_sync( |
| "vendor_boot_a", |
| u64::try_from(vendor_hdr_size).map_err(Error::from)?, |
| &mut load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..vendor_ramdisk_size], |
| )?; |
| } |
| ramdisk_load_curr += vendor_ramdisk_size; |
| |
| // Load generic ramdisk |
| if generic_ramdisk_size > 0 { |
| ops.read_from_partition_sync( |
| "init_boot_a", |
| init_boot_hdr_size.try_into().unwrap(), |
| &mut load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..generic_ramdisk_size], |
| )?; |
| ramdisk_load_curr += generic_ramdisk_size; |
| } |
| |
| // Load ramdisk from boot image |
| if boot_ramdisk_size > 0 { |
| load[ramdisk_load_curr.try_into().map_err(Error::from)?..][..boot_ramdisk_size] |
| .copy_from_slice(&boot_img_buffer[kernel_size..][..boot_ramdisk_size]); |
| ramdisk_load_curr += boot_ramdisk_size; |
| } |
| |
| // Prepare partition data for avb verification |
| let (vendor_boot_load_buffer, remains) = load.split_at_mut(vendor_ramdisk_size); |
| let (init_boot_load_buffer, remains) = remains.split_at_mut(generic_ramdisk_size); |
| let (_boot_ramdisk_load_buffer, remains) = remains.split_at_mut(boot_ramdisk_size); |
| // Prepare a BootConfigBuilder to add avb generated bootconfig. |
| let mut bootconfig_builder = BootConfigBuilder::new(remains)?; |
| // Perform avb verification. |
| avb_verify_slot( |
| ops, |
| boot_img_buffer, |
| vendor_boot_load_buffer, |
| init_boot_load_buffer, |
| dtbo_buffer.as_deref(), |
| &mut bootconfig_builder, |
| )?; |
| |
| // Move kernel to end of the boot image buffer |
| let (_boot_img_buffer, kernel_tail_buffer) = { |
| let off = SafeNum::from(boot_img_buffer.len()) - kernel_size; |
| let off_idx: usize = off.try_into().map_err(Error::from)?; |
| let aligned_off = off - (&boot_img_buffer[off_idx] as *const _ as usize % KERNEL_ALIGNMENT); |
| let aligned_off_idx = aligned_off.try_into().map_err(Error::from)?; |
| boot_img_buffer.copy_within(0..kernel_size, aligned_off_idx); |
| boot_img_buffer.split_at_mut(aligned_off_idx) |
| }; |
| |
| // Add slot index |
| bootconfig_builder.add("androidboot.slot_suffix=_a\n")?; |
| |
| match boot_mode { |
| // TODO(b/329716686): Support bootloader mode |
| AndroidBootMode::Normal | AndroidBootMode::BootloaderBootOnce => { |
| bootconfig_builder.add("androidboot.force_normal_boot=1\n")? |
| } |
| _ => { |
| // Do nothing |
| } |
| } |
| |
| // V4 image has vendor bootconfig. |
| if vendor_bootconfig_size > 0 { |
| let mut bootconfig_offset = SafeNum::from(vendor_hdr_size); |
| for image_size in [vendor_ramdisk_size, vendor_dtb_size, vendor_ramdisk_table_size] { |
| bootconfig_offset += SafeNum::from(image_size).round_up(vendor_page_size); |
| } |
| bootconfig_builder.add_with(|_, out| { |
| ops.read_from_partition_sync( |
| "vendor_boot_a", |
| bootconfig_offset.try_into()?, |
| &mut out[..vendor_bootconfig_size as usize], |
| )?; |
| Ok(vendor_bootconfig_size as usize) |
| })?; |
| } |
| // Check if there is a device specific bootconfig partition. |
| // TODO(b/353272981): Implement cuttlefish-specific u-boot os_configuration implementation |
| // to handle this. |
| match ops.partition_size("bootconfig") { |
| Ok(Some(sz)) => { |
| bootconfig_builder.add_with(|_, out| { |
| // For proof-of-concept only, we just load as much as possible and figure out the |
| // actual bootconfig string length after. This however, can introduce large amount |
| // of unnecessary disk access. In real implementation, we might want to either read |
| // page by page or find way to know the actual length first. |
| let max_size = core::cmp::min(sz.try_into().unwrap(), out.len()); |
| ops.read_from_partition_sync("bootconfig", 0, &mut out[..max_size])?; |
| // Compute the actual config string size. The config is a null-terminated string. |
| Ok(CStr::from_bytes_until_nul(&out[..]) |
| .or(Err(Error::InvalidInput))? |
| .to_bytes() |
| .len()) |
| })?; |
| } |
| _ => {} |
| } |
| |
| // TODO(b/353272981): Handle buffer too small |
| bootconfig_builder.add_with(|bytes, out| { |
| // TODO(b/353272981): Verify provided bootconfig and fail here |
| Ok(ops.fixup_bootconfig(&bytes, out)?.map(|slice| slice.len()).unwrap_or(0)) |
| })?; |
| gbl_println!(ops, "final bootconfig: \"{}\"", bootconfig_builder); |
| |
| ramdisk_load_curr += bootconfig_builder.config_bytes().len(); |
| |
| // On ARM, we may need to decompress the kernel and re-split the buffer to the new kernel size. |
| #[cfg(target_arch = "aarch64")] |
| let (load, kernel_size, kernel_tail_buffer) = { |
| let kernel_size = kernel_tail_buffer.len(); |
| let compressed_kernel_offset = images_buffer.len() - kernel_size; |
| let decompressed_kernel_offset = |
| decompress_kernel(ops, images_buffer, compressed_kernel_offset)?; |
| let (load, kernel_tail_buffer) = images_buffer.split_at_mut(decompressed_kernel_offset); |
| (load, kernel_tail_buffer.len(), kernel_tail_buffer) |
| }; |
| |
| // Use the remaining load buffer for the FDT. |
| let (ramdisk_load_buffer, load) = |
| load.split_at_mut(ramdisk_load_curr.try_into().map_err(Error::from)?); |
| let fdt_buffer = aligned_subslice(load, FDT_ALIGNMENT)?; |
| |
| // TODO(b/353272981): for now we have a hardcoded priority order of DT sources to check, but |
| // the goal is to provide all DTBs and DTBOs to [GblOps] and let it select which ones to use. |
| match ops.get_custom_device_tree() { |
| // First: check for custom FDT (Cuttlefish). |
| Some(custom_fdt) => { |
| gbl_println!(ops, "Using custom FDT"); |
| fdt_buffer[..custom_fdt.len()].copy_from_slice(custom_fdt); |
| } |
| None => { |
| // Second: "vendor_boot" FDT. |
| let (part, offset, size) = if vendor_dtb_size > 0 { |
| // DTB is located after the header and ramdisk (aligned). |
| let offset = (SafeNum::from(vendor_hdr_size) + SafeNum::from(vendor_ramdisk_size)) |
| .round_up(vendor_page_size) |
| .try_into() |
| .map_err(Error::from)?; |
| ("vendor_boot_a", offset, vendor_dtb_size) |
| // Third: "boot" FDT. |
| } else if boot_dtb_size > 0 { |
| // DTB is located after the header, kernel, ramdisk, and second images (aligned). |
| let mut offset = SafeNum::from(kernel_hdr_size); |
| for image_size in [kernel_size, boot_ramdisk_size, boot_second_size] { |
| offset += SafeNum::from(image_size).round_up(kernel_hdr_size); |
| } |
| ("boot_a", offset.try_into().map_err(Error::from)?, boot_dtb_size) |
| } else { |
| return Err(Error::NoFdt.into()); |
| }; |
| |
| gbl_println!(ops, "Using FDT from {} (offset = {}, size = {})", part, offset, size); |
| ops.read_from_partition_sync(part, offset, &mut fdt_buffer[..size])?; |
| } |
| }; |
| let mut fdt = Fdt::new(fdt_buffer)?; |
| |
| // Add ramdisk range to FDT |
| let ramdisk_addr: u64 = |
| (ramdisk_load_buffer.as_ptr() as usize).try_into().map_err(Error::from)?; |
| let ramdisk_end: u64 = |
| ramdisk_addr + u64::try_from(ramdisk_load_buffer.len()).map_err(Error::from)?; |
| fdt.set_property("chosen", c"linux,initrd-start", &ramdisk_addr.to_be_bytes())?; |
| fdt.set_property("chosen", c"linux,initrd-end", &ramdisk_end.to_be_bytes())?; |
| gbl_println!(ops, "linux,initrd-start: {:#x}", ramdisk_addr); |
| gbl_println!(ops, "linux,initrd-end: {:#x}", ramdisk_end); |
| |
| // Update the FDT commandline. |
| let device_tree_commandline_length = |
| CStr::from_bytes_until_nul(fdt.get_property("chosen", BOOTARGS_PROP)?) |
| .map_err(Error::from)? |
| .to_bytes() |
| .len(); |
| |
| // Reserve 1024 bytes for separators and fixup. |
| let final_commandline_len = |
| device_tree_commandline_length + boot_cmdline.len() + vendor_cmdline.len() + 1024; |
| let final_commandline_buffer = |
| fdt.set_property_placeholder("chosen", BOOTARGS_PROP, final_commandline_len)?; |
| |
| let mut commandline_builder = |
| CommandlineBuilder::new_from_prefix(&mut final_commandline_buffer[..])?; |
| commandline_builder.add(boot_cmdline)?; |
| commandline_builder.add(vendor_cmdline)?; |
| |
| // TODO(b/353272981): Handle buffer too small |
| commandline_builder.add_with(|current, out| { |
| // TODO(b/353272981): Verify provided command line and fail here. |
| Ok(ops.fixup_os_commandline(current, out)?.map(|fixup| fixup.len()).unwrap_or(0)) |
| })?; |
| gbl_println!(ops, "final cmdline: \"{}\"", commandline_builder.as_str()); |
| |
| // Finalize FDT to actual used size. |
| fdt.shrink_to_fit()?; |
| |
| // Move the kernel backward as much as possible to preserve more space after it. This is |
| // necessary in case the input buffer is at the end of address space. |
| let kernel_tail_buffer_size = kernel_tail_buffer.len(); |
| let ramdisk_load_buffer_size = ramdisk_load_buffer.len(); |
| let fdt_len = fdt.header_ref()?.actual_size(); |
| // Split out the ramdisk. |
| let (ramdisk, remains) = images_buffer.split_at_mut(ramdisk_load_buffer_size); |
| // Split out the fdt. |
| let (fdt, kernel) = aligned_subslice(remains, FDT_ALIGNMENT)?.split_at_mut(fdt_len); |
| // Move the kernel backward as much as possible. |
| let kernel = aligned_subslice(kernel, KERNEL_ALIGNMENT)?; |
| let kernel_start = kernel.len().checked_sub(kernel_tail_buffer_size).unwrap(); |
| kernel.copy_within(kernel_start..kernel_start.checked_add(kernel_size).unwrap(), 0); |
| // Split out the remaining buffer. |
| let (kernel, remains) = kernel.split_at_mut(kernel_size); |
| |
| Ok((ramdisk, fdt, kernel, remains)) |
| } |