blob: 1c1f52605eaa9d90807c31267c6f71ff2a10510a [file] [log] [blame]
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Notes:
# (1) Usage: This script gets used as a "library" from other parts of the
# build scripts. Specifically, the board_specific_setup.sh script sources
# this script as part of the board_make_image_bootable() function. The
# "cros_make_image_bootable" script calls the board_make_image_bootable()
# function.
# (2) In order to install GRUB2 on GPT-partitioned disks, there must be a
# designated "BIOS boot partition".
#
# On MBR-only disks, the disk sectors immediately following the MBR are
# used for storing "stage 2" of GRUB2. On GPT disks, the sectors
# immediately after MBR are used up to hold the actual partition table.
# In such case, a well-known GUID can be used to mark a partition as the
# BIOS boot partition, which the grub-install command uses to store the
# second stage of the bootloader. For more details on BIOS boot partition,
# see https://en.wikipedia.org/wiki/BIOS_boot_partition.
#
# The disk layout of Lakitu is inherited from the ChromeOS disk layout,
# which has a partition 11 for firmware and a partition 12 for legacy
# bootloader configurations. Since Lakitu doesn't use a custom firmware,
# we can reuse partition 11 as the BIOS boot partition without having to
# change the disk layout. We continue to use partition 12 for storing
# bootloader configurations.
IMAGE=
ESP_DIR=""
LOOP_DEV=
# Only let dm-verity block if rootfs verification is configured.
# Also, set which device mapper correspondes to verity
dev_wait=0
ROOTDEV=/dev/dm-0
if [[ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} ]]; then
dev_wait=1
if [[ ${FLAGS_enable_bootcache} -eq ${FLAGS_TRUE} ]]; then
ROOTDEV=/dev/dm-1
fi
fi
part_index_to_uuid() {
local image="$1"
local index="$2"
sudo cgpt show -i "$index" -u "$image"
}
cleanup() {
if [[ -d "${ESP_DIR}" ]]; then
if mountpoint -q "${ESP_DIR}"; then
sudo umount "${ESP_DIR}"
fi
# ESP_DIR should be empty after the umount, so rmdir should work.
rmdir "${ESP_DIR}"
fi
if [[ -b "${LOOP_DEV}" ]]; then
sudo losetup --detach "${LOOP_DEV}"
fi
}
# Installs bootloaders on the given disk image.
#
# Args:
# $1: absolute path to the disk image.
bootloader_install() {
trap cleanup EXIT
IMAGE="$1"
info "Installing bootloaders on ${IMAGE}"
LOOP_DEV="$(sudo losetup --find --show --partscan "${IMAGE}")"
if ! sudo udevadm settle --timeout=10; then
warn "Error running 'udevadm settle'"
fi
local -r esp_node=${LOOP_DEV}p12
# Wait till udevd finishes the work.
for i in {1..10}; do
info "Probing ${esp_node}"
if [[ -b "${esp_node}" ]]; then
break
fi
sleep 1
sudo blockdev --rereadpt "${LOOP_DEV}"
done
if [[ ! -b "${esp_node}" ]]; then
error "EFI partition ${esp_node} not available"
return 1
fi
ESP_DIR="$(mktemp --directory)"
if ! sudo mount -t vfat "${esp_node}" "${ESP_DIR}"; then
error "Could not mount EFI partition"
return 1
fi
if [[ "${ARCH}" == "amd64" ]]; then
install_grub2_amd64
elif [[ "${ARCH}" == "arm64" ]]; then
install_efi_bootloaders_arm64
else
error "Could not install bootloaders on unsupported architecture: ${ARCH}"
return 1
fi
cleanup
trap - EXIT
info "Successfully installed bootloaders on ${IMAGE}"
}
install_grub2_amd64() {
local -r grub_dir="boot/grub"
local -r grub_target="i386-pc"
local -r grub_modules="part_gpt gptpriority test fat ext2 normal boot chain
configfile linux search search_fs_uuid search_label
terminal memdisk echo biosdisk serial"
# Use partition #11 (originally meant for firmware RW) as BIOS Boot Partition.
local -r bios_part_num="11"
# See https://www.gnu.org/software/grub/manual/html_node/BIOS-installation.html
# for the UUID.
local -r bios_part_uuid="21686148-6449-6E6F-744E-656564454649"
info "Installing amd64 GRUB2 ${grub_target}"
if ! sudo mkdir -p "${ESP_DIR}/${grub_dir}"; then
error "Could not create dir for grub config"
return 1
fi
if [[ ! -f ${ESP_DIR}/efi/boot/grub.cfg ]]; then
error "Could not find grub.cfg from EFI installation."
return 1
fi
if ! sudo sed -i -e 's|/sbin/init|/usr/lib/systemd/systemd|' \
-e '/^set timeout=/s|=.*|=0|' \
"${ESP_DIR}/efi/boot/grub.cfg"; then
error "Failed to update grub configuration file."
return 1
fi
# Create a minimal grub.cfg that sets up environment variables and loads the
# configuration from EFI installation.
cat <<EOF | sudo dd of="${ESP_DIR}/${grub_dir}/grub.cfg" 2>/dev/null
# The configuration relies on |grubdisk| environment variable to find the GPT
# disk. When booting with EFI, the efidisk module exports the env var. In case
# of BIOS, we need to hard-code it and export.
set grubdisk=hd0
export grubdisk
configfile /efi/boot/grub.cfg
EOF
# Mark the firmware partition as the BIOS boot partition.
# Note that this must be done prior to running grub-install, or else the
# grub-install command will fail.
info "Setting BIOS boot partition ..."
if ! sudo cgpt add -i "${bios_part_num}" \
-t "${bios_part_uuid}" "${LOOP_DEV}"; then
error "Error running cgpt"
return 1
fi
info "Running grub-install ..."
if ! sudo grub-install --target="${grub_target}" \
--boot-directory="${ESP_DIR}/boot" \
--modules="${grub_modules}" \
"${LOOP_DEV}"; then
error "Error running grub-install"
return 1
fi
info "Successfully installed amd64 GRUB2 ${grub_target}"
}
ROOT_FS_DIR_ARM64=""
STATEFUL_FS_DIR_ARM64=""
KERNEL_CMDLINE_FILE_ARM64=""
install_efi_bootloaders_arm64_cleanup() {
if [[ -d "${ROOT_FS_DIR_ARM64}" ]]; then
if mountpoint -q "${ROOT_FS_DIR_ARM64}"; then
unmount_image
fi
rmdir "${ROOT_FS_DIR_ARM64}"
fi
if [[ -d "${STATEFUL_FS_DIR_ARM64}" ]]; then
if mountpoint -q "${STATEFUL_FS_DIR_ARM64}"; then
unmount_image
fi
rmdir "${STATEFUL_FS_DIR_ARM64}"
fi
rm -f "${KERNEL_CMDLINE_FILE_ARM64}"
}
install_efi_bootloaders_arm64() {
info "Installing arm64 EFI Shim and GRUB2"
trap install_efi_bootloaders_arm64_cleanup EXIT
# The script in src/scripts/update_bootloaders.sh copies the EFI bootloader
# files to the EFI system partition, only for x86 and amd64. Hence, we should
# copy them here for lakitu-arm64.
ROOT_FS_DIR_ARM64="$(mktemp --directory)"
STATEFUL_FS_DIR_ARM64="$(mktemp --directory)"
mount_image "${IMAGE}" "${ROOT_FS_DIR_ARM64}" "${STATEFUL_FS_DIR_ARM64}" "" \
"--read_only"
sudo mkdir -p "${ESP_DIR}"/efi/boot
sudo cp "${ROOT_FS_DIR_ARM64}"/boot/efi/boot/*.efi "${ESP_DIR}"/efi/boot/
unmount_image
# For x86 and amd64, create_base_image() in
# src/scripts/build_library/base_image_util.sh runs
# src/scripts/build_library/create_legacy_bootloader_templates.sh . The script
# in create_legacy_bootloader_templates.sh generates grub.cfg in the EFI
# system partition, but that's only for x86 and amd64. Hence, we should
# generate one here for lakitu for arm64.
KERNEL_CMDLINE_FILE_ARM64="$(mktemp legacy_config_XXXXXXXXXX.txt)"
cat <<EOF > "${KERNEL_CMDLINE_FILE_ARM64}"
init=/usr/lib/systemd/systemd
boot=local
rootwait
ro
noresume
noswap
loglevel=7
noinitrd
console=ttyAMA0
EOF
local -r script_root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")"
. "${script_root}/build_kernel_image.sh" || return 1
modify_kernel_command_line "${KERNEL_CMDLINE_FILE_ARM64}"
# Read back the config_file; translate newlines to space
common_args="$(tr "\n" " " < "${KERNEL_CMDLINE_FILE_ARM64}")"
install_efi_bootloaders_arm64_cleanup
trap - EXIT
# Common verified boot command-line args
verity_common="dm_verity.error_behavior=${FLAGS_verity_error_behavior}"
verity_common="${verity_common} dm_verity.max_bios=${FLAGS_verity_max_ios}"
verity_common="${verity_common} dm_verity.dev_wait=${dev_wait}"
# Discover last known partition numbers.
part_num_kern_a="$(get_layout_partition_number \
"${FLAGS_image_type}" KERN-A)"
part_num_kern_b="$(get_layout_partition_number \
"${FLAGS_image_type}" KERN-B)"
part_num_root_a="$(get_layout_partition_number \
"${FLAGS_image_type}" ROOT-A)"
part_num_root_b="$(get_layout_partition_number \
"${FLAGS_image_type}" ROOT-B)"
root_a_uuid="PARTUUID=$(part_index_to_uuid "${LOOP_DEV}" ${part_num_root_a})"
root_b_uuid="PARTUUID=$(part_index_to_uuid "${LOOP_DEV}" ${part_num_root_b})"
# Make sure we got the kernel partition
if [ -z "${part_num_kern_a}" ]; then
error "failed to get a partition number for KERN-A"
return 1
fi
dm_table=
# get cmdline from any kernel partition
kernel_cmdline=$(sudo dump_kernel_config "${LOOP_DEV}p${part_num_kern_a}")
if echo "$kernel_cmdline" | grep -q 'dm="'; then
dm_table=$(echo "$kernel_cmdline" | sed -s 's/.*dm="\([^"]*\)".*/\1/')
fi
old_root='PARTUUID=%U/PARTNROFF=1'
grub_dm_table_a=${dm_table//${old_root}/${root_a_uuid}}
grub_dm_table_b=${dm_table//${old_root}/${root_b_uuid}}
cat <<EOF | sudo dd of="${ESP_DIR}/efi/boot/grub.cfg"
defaultA=0
defaultB=1
gptpriority \$grubdisk ${part_num_kern_a} prioA
gptpriority \$grubdisk ${part_num_kern_b} prioB
if [ \$prioA -lt \$prioB ]; then
set default=\$defaultB
else
set default=\$defaultA
fi
set timeout=2
# NOTE: These magic grub variables are a Chrome OS hack. They are not portable.
menuentry "local image A" {
linux /syslinux/vmlinuz.A ${common_args} cros_efi \
root=${root_a_uuid}
}
menuentry "local image B" {
linux /syslinux/vmlinuz.B ${common_args} cros_efi \
root=${root_b_uuid}
}
menuentry "verified image A" {
linux /syslinux/vmlinuz.A ${common_args} ${verity_common} \
cros_efi root=${ROOTDEV} dm="${grub_dm_table_a}"
}
menuentry "verified image B" {
linux /syslinux/vmlinuz.B ${common_args} ${verity_common} \
cros_efi root=${ROOTDEV} dm="${grub_dm_table_b}"
}
EOF
if [[ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} ]]; then
sudo sed -i \
-e '/^defaultA=/s:=.*:=2:' \
-e '/^defaultB=/s:=.*:=3:' \
"${ESP_DIR}/efi/boot/grub.cfg"
fi
info "Successfully installed arm64 EFI Shim and GRUB2"
info "Installing arm64 kernels"
# The function create_base_image() in
# src/scripts/build_library/base_image_util.sh places the Linux kernel images
# in ${IMAGE_DIR}/boot_images/ . Then create_base_image() runs
# src/scripts/bin/cros_make_image_bootable, which runs
# src/update_bootloaders.sh . The script in update_bootloaders.sh copies the
# Linux kernel files into the EFI system partition for some architectures, but
# not for arm64. Hence, we should copy them here for lakitu for arm64.
sudo mkdir -p "${ESP_DIR}"/syslinux
# sign_official_image.sh uses wildcard *.cfg to do some editing
# and if there is no cfg file it fails. Add dummy one so wildcard
# is properly expanded
sudo touch "${ESP_DIR}"/syslinux/dummy.cfg
# TODO: Install vmlinuz.B as well, once we reduce the kernel image size.
sudo cp "${IMAGE_DIR}"/boot_images/vmlinuz-* "${ESP_DIR}"/syslinux/vmlinuz.A
info "Successfully installed arm64 kernels"
}