blob: 7699a1b45bd5066cd994fc94e5320fd5594d1118 [file] [log] [blame]
#!/bin/sh
#
# Copyright (c) 2012 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.
#
# For factory and auto update, after shell-ball self-extracts, this script is
# called to update BIOS and EC firmware as per how many files are extracted.
# To simply design, THIS SCRIPT MUST BE EXECUTED IN A R/W EXCLUSIVE TEMP FOLDER.
# AND ALL FILENAMES FOR INPUT AND OUTPUT MUST NOT CONTAIN SPACE.
#
# Allowed commands are:
# - standard shell commands
# - flashrom
# - gbb_utility
# - crossystem
# All other special commands should be defined by a function in crosutil.sh.
#
# Temporary files should be named as "_*" to prevent confliction
# Updater for firmware v4 (two-stop main, chromeos-ec).
# This is designed for x86/arm platform, using chromeos-ec (software sync).
# Assume SLOT_A and SLOT_B has exactly same contents (and same keyblock).
SCRIPT_BASE="$(dirname "$0")"
. "$SCRIPT_BASE/common.sh"
# Use bundled tools with highest priority, to prevent dependency when updating
cros_setup_path
# ----------------------------------------------------------------------------
# Customization Section
# Customization script file name - do not change this.
# You have to create a file with this name to put your customization.
CUSTOMIZATION_SCRIPT="updater_custom.sh"
# Customization script main entry - do not change this.
# You have to define a function with this name to run your customization.
CUSTOMIZATION_MAIN="updater_custom_main"
# Customization script "RW compatible check" function.
# Overrides this with any function names to test if RW firmware in current
# system is compatible with this updater. The function must returns $FLAGS_FALSE
# if RW is not compatible (i.e., need incompatible_update mode)
CUSTOMIZATION_RW_COMPATIBLE_CHECK=""
# ----------------------------------------------------------------------------
# Constants
# Slot names defined by ChromeOS Firmware Specification
SLOT_A="RW_SECTION_A"
SLOT_B="RW_SECTION_B"
SLOT_RO="RO_SECTION"
SLOT_RW_SHARED="RW_SHARED"
SLOT_EC_RO="EC_RO"
SLOT_EC_RW="EC_RW"
SLOT_PD_RO="EC_RO"
SLOT_PD_RW="EC_RW"
TYPE_MAIN="main"
TYPE_EC="ec"
TYPE_PD="pd"
# ----------------------------------------------------------------------------
# Global Variables
# Current system identifiers (may be empty if running on non-ChromeOS systems)
HWID="$(crossystem hwid 2>/dev/null)" || HWID=""
ECINFO="$(mosys -k ec info 2>/dev/null)" || ECINFO=""
PDINFO="$(mosys -k pd info 2>/dev/null)" || PDINFO=""
# Compare following values with TARGET_*
# (should be passed by wrapper as environment variables)
FWID="$(crossystem fwid 2>/dev/null)" || FWID=""
RO_FWID="$(crossystem ro_fwid 2>/dev/null)" || RO_FWID=""
ECID="$(eval "$ECINFO"; echo "$fw_version")"
PDID="$(eval "$PDINFO"; echo "$fw_version")"
PLATFORM="${FWID%%.*}"
# ----------------------------------------------------------------------------
# Helper functions
# Verifies if current system is installed with compatible rootkeys
check_compatible_keys() {
local current_image="$DIR_CURRENT/$IMAGE_MAIN"
local target_image="$DIR_TARGET/$IMAGE_MAIN"
if [ "${FLAGS_check_keys}" = ${FLAGS_FALSE} ]; then
debug_msg "check_compatible_keys: ignored."
return $FLAGS_TRUE
fi
if ! cros_check_same_root_keys "$current_image" "$target_image"; then
alert_incompatible_rootkey
die "Incompatible Rootkey."
fi
# Get RW firmware information
local fw_info
fw_info="$(cros_get_rw_firmware_info "$DIR_TARGET/$TYPE_MAIN/VBLOCK_A" \
"$DIR_TARGET/$TYPE_MAIN/FW_MAIN_A" \
"$target_image")" || fw_info=""
[ -n "$fw_info" ] || die "Failed to get RW firmware information"
# Check TPM
if ! cros_check_tpm_key_version "$fw_info"; then
alert_incompatible_tpmkey
die "Incompatible TPM Key."
fi
# Warn for RO-normal updates
local flag_ro_normal_boot=1
local current_flags="$(cros_get_firmware_preamble_flags "$fw_info")"
if [ "$((current_flags & flag_ro_normal_boot))" = "$flag_ro_normal_boot" ]
then
alert "
WARNING: FIRMWARE IMAGE TO BE UPDATED IS SIGNED WITH 'RO-NORMAL' FLAG.
THIS IS A KEY-BLOCK-ONLY UPDATE WITHOUT FIRMWARE CODE CHANGE.
YOUR FWID (ACTIVE FIRMWARE ID) WON'T CHANGE AFTER APPLYING THIS UPDATE.
"
fi
}
need_update_main_vblock() {
# Check if VBLOCK (key version and firmware signature) is different.
prepare_main_image
prepare_main_current_image
# Compare VBLOCK from current A slot and target B slot (normal firmware).
! crosfw_is_equal_slot "$TYPE_MAIN" "VBLOCK_A" "VBLOCK_B"
}
need_update_ec() {
prepare_ec_image
prepare_ec_current_image
if ! is_ecfw_write_protected &&
! crosfw_is_equal_slot "$TYPE_EC" "$SLOT_EC_RO"; then
debug_msg "EC RO needs update."
return $FLAGS_TRUE
fi
if ! crosfw_is_equal_slot "$TYPE_EC" "$SLOT_EC_RW"; then
debug_msg "EC RW needs update."
return $FLAGS_TRUE
fi
return $FLAGS_FALSE
}
need_update_pd() {
prepare_pd_image
prepare_pd_current_image
if ! is_pdfw_write_protected &&
! crosfw_is_equal_slot "$TYPE_PD" "$SLOT_PD_RO"; then
debug_msg "PD RO needs update."
return $FLAGS_TRUE
fi
if ! crosfw_is_equal_slot "$TYPE_PD" "$SLOT_PD_RW"; then
debug_msg "PD RW needs update."
return $FLAGS_TRUE
fi
return $FLAGS_FALSE
}
prepare_main_image() {
crosfw_unpack_image "$TYPE_MAIN" "$IMAGE_MAIN" "$TARGET_OPT_MAIN"
}
prepare_ec_image() {
crosfw_unpack_image "$TYPE_EC" "$IMAGE_EC" "$TARGET_OPT_EC"
}
prepare_pd_image() {
crosfw_unpack_image "$TYPE_PD" "$IMAGE_PD" "$TARGET_OPT_PD"
}
prepare_main_current_image() {
crosfw_unpack_current_image "$TYPE_MAIN" "$IMAGE_MAIN" "$TARGET_OPT_MAIN" "$@"
}
prepare_ec_current_image() {
crosfw_unpack_current_image "$TYPE_EC" "$IMAGE_EC" "$TARGET_OPT_EC" "$@"
}
prepare_pd_current_image() {
crosfw_unpack_current_image "$TYPE_PD" "$IMAGE_PD" "$TARGET_OPT_PD" "$@"
}
is_write_protection_disabled() {
if [ "${FLAGS_update_main}" = ${FLAGS_TRUE} ]; then
is_mainfw_write_protected && return $FLAGS_FALSE || true
fi
if [ "${FLAGS_update_ec}" = ${FLAGS_TRUE} ]; then
is_ecfw_write_protected && return $FLAGS_FALSE || true
fi
if [ "${FLAGS_update_pd}" = ${FLAGS_TRUE} ]; then
is_pdfw_write_protected && return $FLAGS_FALSE || true
fi
return $FLAGS_TRUE
}
clear_update_cookies() {
# Always success because the system may not have crossystem ready yet if we're
# trying to recover a broken firmware or after transition from legacy firmware
( cros_set_fwb_tries 0
cros_set_startup_update_tries 0
cros_set_prop recovery_request=0 ) >/dev/null 2>&1 ||
debug_msg "clear_update_cookies: there were some errors, ignored."
}
silent_sh() {
# Calls given commands and ignores any error (mostly for factory
# installations, when the firmware is still non-Chrome).
( "$@" ) >/dev/null 2>&1 ||
debug_msg "Failed calling: $@"
}
enable_dev_boot() {
cros_set_prop dev_boot_usb=1 dev_boot_signed_only=0
}
disable_dev_boot() {
# The firmware will decide and reset default values of dev_boot_usb and
# dev_boot_signed_only on reoot when user turned off developer switch (i.e.,
# normal mode). It's safe to set dev_boot_usb to zero here, but
# dev_boot_signed_only may expect different default values, so we leave it
# untouched and let firmware decide.
cros_set_prop dev_boot_usb=0
}
load_keyset() {
if ! [ -d "$KEYSET_DIR" ]; then
debug_msg "No keysets folder."
return
fi
local keyid="$FLAGS_customization_id"
if [ -z "$keyid" ]; then
debug_msg "No customization ID from command line. Try to read from VPD."
keyid="$(vpd -g "customization_id" || echo "")"
fi
if [ -z "$keyid" ]; then
verbose_msg "No customization_id in VPD/command line. Using default keys."
return
fi
# "customization_id" in format LOEMID[-SERIES]. We only want LOEMID for now.
keyid="${keyid%%-*}"
debug_msg "Keysets detected, using [$keyid]"
local rootkey="$KEYSET_DIR/rootkey.$keyid"
local vblock_a="$KEYSET_DIR/vblock_A.$keyid"
local vblock_b="$KEYSET_DIR/vblock_B.$keyid"
if ! [ -s "$rootkey" ]; then
die "Failed to load keysets for customization ID [$keyid]."
fi
# Override keys
gbb_utility -s --rootkey="$rootkey" "$IMAGE_MAIN" ||
die "Failed to update rootkey from keyset [$keyid]."
local size_input="$(cros_get_file_size "$IMAGE_MAIN")"
local param="dummy:emulate=VARIABLE_SIZE,image=$IMAGE_MAIN,size=$size_input"
local write_list="-i VBLOCK_A:$vblock_a -i VBLOCK_B:$vblock_b"
silent_invoke "flashrom -p $param -w $IMAGE_MAIN $write_list" ||
die "Failed to update VBLOCK from keyset [$keyid]."
# TODO(hungte) Verify key correctness after building new image files.
verbose_msg "Firmware keys changed to set [$keyid]."
}
is_vboot2() {
crossystem 'fw_vboot2?1'
}
# ----------------------------------------------------------------------------
# Core logic in different modes
# Startup
mode_startup() {
# ChromeOS-EC do not need to be updated at startup time.
cros_set_startup_update_tries 0
}
# Update Engine - Current Boot Successful (chromeos_setgoodkernel)
mode_bootok() {
if is_vboot2; then
cros_set_prop fw_result=success fw_try_count=0
else
local mainfw_act="$(cros_get_prop mainfw_act)"
# Copy main firmware to the spare slot.
if [ "$mainfw_act" = "A" ]; then
crosfw_dup2_mainfw "$SLOT_A" "$SLOT_B"
elif [ "$mainfw_act" = "B" ]; then
crosfw_dup2_mainfw "$SLOT_B" "$SLOT_A"
else
# Recovery mode, or non-chrome.
die "bootok: abnormal active firmware ($mainfw_act)..."
fi
cros_set_fwb_tries 0
fi
# EC firmware is managed by software sync (main firmware).
}
# Update Engine - Received Update
mode_autoupdate() {
# Quick check if we need to update
local need_update=0
if [ "${FLAGS_force}" = ${FLAGS_TRUE} ]; then
need_update=1
else
# Check main firmware
if [ "${FLAGS_update_main}" = ${FLAGS_TRUE} ]; then
if [ "$TARGET_FWID" != "$FWID" ] || need_update_main_vblock; then
need_update=1
else
FLAGS_update_main=$FLAGS_FALSE
fi
fi
# Check EC firmware
if [ "${FLAGS_update_ec}" = ${FLAGS_TRUE} ]; then
if [ "$TARGET_ECID" != "$ECID" ]; then
need_update=1
else
FLAGS_update_ec=$FLAGS_FALSE
fi
fi
# Check PD firmware
if [ "${FLAGS_update_pd}" = ${FLAGS_TRUE} ]; then
if [ "$TARGET_PDID" != "$PDID" ]; then
need_update=1
else
FLAGS_update_pd=$FLAGS_FALSE
fi
fi
fi
if [ "$need_update" -eq 0 ]; then
verbose_msg "Latest firmware already installed. No need to update."
return
fi
if [ "${FLAGS_update_main}" = "${FLAGS_TRUE}" ]; then
local mainfw_act="$(cros_get_prop mainfw_act)"
local update_slot="${SLOT_B}"
local prop_name=fwb_tries
local try_next=
if is_vboot2; then
if [ "$mainfw_act" = "A" ]; then
update_slot="$SLOT_B"
try_next=B
elif [ "$mainfw_act" = "B" ]; then
update_slot="$SLOT_A"
try_next=A
else
die "autoupdate: unexpected active firmware ($_mainfw_act)..."
fi
prop_name=fw_try_count
else # vboot1
if [ "$mainfw_act" = "B" ]; then
# mainfw_act is only updated at system reboot; if two updates (both with
# firmware updates) are pushed in a row, next update will be executed
# while mainfw_act is still B. Since we don't use RW BIOS directly when
# updater is running, it should be safe to update in this case.
debug_msg "mainfw_act=B, checking if we can still update FW B..."
prepare_main_current_image
cros_compare_file "$DIR_CURRENT/$TYPE_MAIN/$SLOT_A" \
"$DIR_CURRENT/$TYPE_MAIN/$SLOT_B" &&
alert "Installing updates while mainfw_act is B (should be safe)." ||
die_need_reboot "Done (retry update next boot)"
elif [ "$mainfw_act" != "A" ]; then
die "autoupdate: unexpected active firmware ($mainfw_act)..."
fi
fi
prepare_main_image
prepare_main_current_image
check_compatible_keys
crosfw_update_main "$update_slot"
if [ -n "$try_next" ]; then
cros_set_prop "fw_try_next=$try_next"
fi
# Try to determine EC software sync by checking $ECID. We can't rely on
# $TARGET_ECID, $FLAGS_update_ec or $IMAGE_EC because in future there may be
# no EC binary blobs in updater (integrated inside main firmware image).
# Note since $ECID uses mosys, devices not using ChromeOS EC may still
# report an ID (but they should use updater v3 instead).
if [ -n "$ECID" -a "$ECID" != "$TARGET_ECID" ]; then
# EC software sync may need extra reboots.
cros_set_prop "${prop_name}=8"
verbose_msg "On reboot EC update may occur."
else
cros_set_prop "${prop_name}=6"
fi
fi
# EC updates will be handled by EC software sync.
}
# Transition to Developer Mode
mode_todev() {
enable_dev_boot
echo "
Booting any self-signed kernel from SSD/USB/SDCard slot is enabled.
Insert bootable media into USB / SDCard slot and press Ctrl-U in developer
screen to boot your own image.
"
clear_update_cookies
}
# Transition to Normal Mode
mode_tonormal() {
# This is optional because whenever you turn off developer switch, the
# dev_boot_usb is also cleared by firmware.
disable_dev_boot
echo "Booting from USB device is disabled."
clear_update_cookies
}
# Recovery Installer
mode_recovery() {
local prefix="mode_recovery"
[ "${FLAGS_mode}" = "recovery" ] || prefix="${FLAGS_mode}(recovery)"
if [ "${FLAGS_update_main}" = ${FLAGS_TRUE} ]; then
if ! is_mainfw_write_protected; then
verbose_msg "$prefix: update RO+RW"
crosfw_preserve_vpd
crosfw_preserve_gbb
crosfw_update_main
else
# TODO(hungte) check if FMAP is not changed
verbose_msg "$prefix: update main/RW:A,B,SHARED"
prepare_main_image
prepare_main_current_image
check_compatible_keys
crosfw_update_main "$SLOT_A"
crosfw_update_main "$SLOT_B"
crosfw_update_main "$SLOT_RW_SHARED"
fi
fi
if [ "${FLAGS_update_ec}" = ${FLAGS_TRUE} ]; then
if ! is_ecfw_write_protected; then
verbose_msg "$prefix: update ec/RO+RW"
crosfw_update_ec
fi
# If EC is write-protected, software sync will lock RW EC after kernel
# starts (left firmware boot stage). We can't "recovery" EC RW in this case.
# Ref: crosbug.com/p/16087.
verbose_msg "$prefix: EC may be restored or updated in next boot."
fi
if [ "${FLAGS_update_pd}" = ${FLAGS_TRUE} ]; then
if ! is_pdfw_write_protected; then
verbose_msg "$prefix: update pd/RO+RW"
crosfw_update_pd
fi
# If PD is write-protected, software sync will lock RW PD after kernel
# starts (left firmware boot stage). We can't recover PD RW in this case.
# Ref: crosbug.com/p/16087.
verbose_msg "$prefix: PD may be restored or updated in next boot."
fi
clear_update_cookies
}
# Factory Installer
mode_factory_install() {
# Everything executed here must assume the system may be not using ChromeOS
# firmware.
is_write_protection_disabled ||
die_need_ro_update "You need to first disable hardware write protection."
if [ "${FLAGS_update_main}" = ${FLAGS_TRUE} ]; then
# We may preserve bitmap here, just like recovery mode. However if there's
# some issue (or incompatible stuff) found in bitmap, we will need a method
# to update the bitmaps.
crosfw_preserve_vpd || verbose_msg "Warning: cannot preserve VPD."
crosfw_update_main
fi
if [ "${FLAGS_update_ec}" = ${FLAGS_TRUE} ]; then
crosfw_update_ec
fi
if [ "${FLAGS_update_pd}" = ${FLAGS_TRUE} ]; then
crosfw_update_pd
fi
cros_clear_nvdata
silent_sh enable_dev_boot
clear_update_cookies
}
# Factory Wipe
mode_factory_final() {
silent_sh disable_dev_boot
clear_update_cookies
}
# Updates for incompatible RW firmware (need to update RO)
mode_incompatible_update() {
# TODO(hungte) check if we really need to stop user by comparing RO firmware
# image, bit-by-bit.
is_write_protection_disabled ||
die_need_ro_update "You need to first disable hardware write protection."
mode_recovery
}
# Checks if current system firmware version number is different than the one
# bundled in updater. Note root/recovery keys, TPM version, vblock key versions,
# and firmware integrity are all unchecked.
mode_fast_version_check() {
if [ -n "$IMAGE_MAIN" -a "$RO_FWID" != "$TARGET_FWID" ]; then
die "Main FWID: $RO_FWID != $TARGET_FWID"
fi
if [ -n "$IMAGE_EC" -a "$ECID" != "$TARGET_ECID" ]; then
die "EC FWID: $ECID != $TARGET_ECID"
fi
if [ -n "$IMAGE_PD" -a "$PDID" != "$TARGET_PDID" ]; then
die "PD FWID: $PDID != $TARGET_PDID"
fi
}
# ----------------------------------------------------------------------------
# Main Entry
main_check_rw_compatible() {
local is_compatible="${FLAGS_TRUE}"
if [ "${FLAGS_check_rw_compatible}" = ${FLAGS_FALSE} ]; then
verbose_msg "Bypassed RW compatbility check. You're on your own."
return $is_compatible
fi
# Try explicit match
if [ -n "$CUSTOMIZATION_RW_COMPATIBLE_CHECK" ]; then
debug_msg "Checking customized RW compatibility..."
"$CUSTOMIZATION_RW_COMPATIBLE_CHECK" || is_compatible="${FLAGS_ERROR}"
elif [ "$is_compatible" = "${FLAGS_TRUE}" ]; then
cros_check_stable_firmware || is_compatible="${FLAGS_FALSE}"
fi
case "$is_compatible" in
"${FLAGS_FALSE}" )
verbose_msg "Try to update with recovery mode..."
mode_recovery
;;
"${FLAGS_ERROR}" )
verbose_msg "
RW firmware update is not compatible with current RO firmware.
Starting full update...
"
mode_incompatible_update
;;
* )
true
esac
return $is_compatible
}
main() {
cros_acquire_lock
set_flags
# factory compatibility
if [ "${FLAGS_factory}" = ${FLAGS_TRUE} ] ||
[ "${FLAGS_mode}" = "factory" ]; then
FLAGS_mode=factory_install
fi
local ro_type=""
cros_is_ro_normal_boot && ro_type="$ro_type[RO_NORMAL]"
verbose_msg "Starting $TARGET_PLATFORM firmware updater v4 (${FLAGS_mode})..."
local package_info="$TARGET_FWID"
local current_info="RO:$RO_FWID $ro_type, ACT:$FWID"
if [ -n "${TARGET_ECID%IGNORE}" ]; then
package_info="$package_info / EC:$TARGET_ECID"
fi
if [ -n "$ECID" ]; then
current_info="$current_info / EC:$ECID"
fi
if [ -n "${TARGET_PDID%IGNORE}" ]; then
package_info="$package_info / PD:$TARGET_PDID"
fi
if [ -n "$PDID" ]; then
current_info="$current_info / PD:$PDID"
fi
verbose_msg " - Updater package: [$package_info]"
verbose_msg " - Current system: [$current_info]"
if [ "${FLAGS_update_main}" = ${FLAGS_TRUE} -a -n "${HWID}" ]; then
# always preserve HWID for current system, if available.
crosfw_preserve_hwid
debug_msg "preserved HWID as: $HWID."
fi
local wpmsg="$(cros_report_wp_status $FLAGS_update_main \
$FLAGS_update_ec $FLAGS_update_pd)"
verbose_msg " - Write protection: $wpmsg"
# Check platform except in factory_install mode.
if [ "${FLAGS_check_platform}" = ${FLAGS_TRUE} ] &&
[ "${FLAGS_mode}" != "factory_install" ] &&
! cros_check_compatible_platform "${TARGET_PLATFORM}" "${PLATFORM}"; then
alert_unknown_platform "$PLATFORM" "$TARGET_PLATFORM"
exit 1
fi
# load customization
if [ -r "$CUSTOMIZATION_SCRIPT" ]; then
debug_msg "loading customization..."
. ./$CUSTOMIZATION_SCRIPT
# invoke customization
debug_msg "starting customized updater main..."
$CUSTOMIZATION_MAIN
fi
load_keyset
case "${FLAGS_mode}" in
# Modes which update RW firmware only and only in normal mode; these need to
# verify if existing RO firmware is compatible. If not, try to perform
# RO+RW update.
autoupdate )
debug_msg "mode with dev and compatibility check: ${FLAGS_mode}"
main_check_rw_compatible &&
mode_"${FLAGS_mode}"
;;
# Modes which update RW firmware only; these need to verify if existing RO
# firmware is compatible. If not, try to perform RO+RW update.
recovery )
debug_msg "mode with compatibility check: ${FLAGS_mode}"
main_check_rw_compatible &&
mode_"${FLAGS_mode}"
;;
# Modes which don't mix existing RO firmware with new RW firmware from the
# updater. They either copy RW firmware between EEPROM slots, or copy both
# RO+RW from the shellball. Either way, RO+RW compatibility is assured.
startup | bootok | todev | tonormal | factory_install | factory_final | \
incompatible_update | fast_version_check )
debug_msg "mode without incompatible checks: ${FLAGS_mode}"
mode_"${FLAGS_mode}"
;;
"" )
die "Please assign updater mode by --mode option."
;;
* )
die "Unknown mode: ${FLAGS_mode}"
;;
esac
verbose_msg "Firmware update (${FLAGS_mode}) completed."
}
# Parse command line
FLAGS "$@" || exit 1
eval set -- "$FLAGS_ARGV"
# Exit on error
set -e
# Main Entry
main