| #!/bin/sh |
| # |
| # Copyright (c) 2010 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. |
| |
| # Updater for firmware v1 (legacy FMAP) |
| # This is designed for platform Mario (CR48). |
| # 1. May include both BIOS and EC firmware |
| # 2. RW firmware includes both developer and normal in one slot |
| # 3. Auto update is currently disabled. |
| |
| SCRIPT_BASE="$(dirname "$0")" |
| . "$SCRIPT_BASE/common.sh" |
| |
| # customizable parameters |
| # ---------------------------------------------------------------------------- |
| # If you want to customize any of these parameters, make a script and name it |
| # as the constant string in CUSTOM_SCRIPT_FILENAME. |
| |
| # The script to be loaded for customization; |
| # please put all your own settings in this file. |
| CUSTOM_SCRIPT_FILENAME='legacy_updater_custom.sh' |
| |
| # Default file names of carried (target) image files |
| BIOS_IMAGE_FILENAME='bios.bin' |
| EC_IMAGE_FILENAME='ec.bin' |
| |
| # flashrom(8) tool path, can be changed to use system default flashrom |
| FLASHROM_TOOLPATH='flashrom' |
| |
| # Default command to reboot immediately |
| CHROMEOS_REBOOT_CMD="reboot" |
| |
| # Default command to decode fmap |
| CHROMEOS_FMAP_DECODE_CMD='mosys -k eeprom map' |
| # note: an alternative is "fmap_decode, or dump_fmap" |
| |
| # Default Chrome OS control tag file to request firmware update after reboot |
| CHROMEOS_NEED_REBOOT_TAG='/mnt/stateful_partition/.need_firmware_update' |
| |
| # Workaround for chrome-os-partner:1563. Backup for CMOS flag. |
| NEED_FIRMWARE_TRYB='/mnt/stateful_partition/.need_firmware_tryb' |
| |
| # Default command to change boot index. |
| CHROMEOS_CHANGE_BOOT_INDEX_CMD='crossystem fwb_tries=1 || true' |
| |
| # Default command to query next (planned) boot index |
| CHROMEOS_QUERY_TRIAL_BOOT_CMD='crossystem fwb_tries || true' |
| |
| # Default command to select BIOS as flashrom target |
| CHROMEOS_SELECT_BIOS_OPT='-p internal:bus=spi' |
| |
| # Default command to select EC (Embedded Controller) as flashrom target |
| CHROMEOS_SELECT_EC_OPT='-p internal:bus=lpc' |
| |
| # Default Chrome OS BIOS firmware memory layout, |
| # overwrite this if you plan to support different layout |
| # TODO(hungte) we may remove this and always rely on fmap in the future. |
| # NOTE: RW_VPD actually exists inside NV_COMMON_STORE now (may be moved to |
| # somewhere else anyway), since we rely on fmap now so putting it there |
| # should be OK. |
| CHROMEOS_BIOS_LAYOUT_DESC=" |
| FV_LOG = 0x020000, |
| NV_COMMON_STORE = 0x010000, |
| VBOOTA = 0x010000, |
| FVMAIN = 0x0D0000, |
| VBOOTB = 0x010000, |
| FVMAINB = 0x0D0000, |
| NVSTORAGE = 0x010000, |
| RW_VPD = 0x001000, |
| FV_RW_RESERVED = *, |
| | |
| FV_RO_RESERVED = *, |
| RO_VPD = 0x020000, |
| FVDEV = 0x100000, |
| FV_RO_DATA = 0x020000, |
| FV_GBB = 0x040000, |
| FV_BSTUB = 0x040000, |
| " |
| |
| # Default Chrome OS BIOS firmware write-protection memory layout. |
| CHROMEOS_BIOS_WP_LAYOUT_DESC=" |
| RW, |
| | |
| RO, |
| " |
| |
| # Default Chrome OS BIOS Firmware RO/RW list |
| CHROMEOS_BIOS_RO_LIST="FV_BSTUB FVDEV" |
| CHROMEOS_BIOS_RW_A_LIST="VBOOTA FVMAIN" |
| CHROMEOS_BIOS_RW_B_LIST="VBOOTB FVMAINB" |
| # To support changing GBB/HWID after firmware update (in factory), |
| # we only check/update GBB in factory mode. |
| CHROMEOS_BIOS_FACTORY_RO_LIST="FV_BSTUB FV_GBB FVDEV" |
| |
| # Default VBOOT section content (for checking layout correctness) |
| CHROMEOS_VBOOT_LIST="VBOOTA VBOOTB" |
| CHROMEOS_VBOOT_SIGNATURE="CHROMEOS" |
| CHROMEOS_VBOOT_SIGNATURE_LENGTH=8 |
| |
| # Default (GBB) section which contains rootkey |
| CHROMEOS_ROOTKEY_SECTION_NAME="FV_GBB" |
| |
| # Default Chrome OS Embedded Controller firmware memory layout, |
| # overwrite this if you plan to support different layout |
| CHROMEOS_EC_LAYOUT_DESC=" |
| EC_RO |
| | |
| EC_RW |
| " |
| |
| # Default Chrome OS Embedded Controller firmware write-protection memory layout. |
| CHROMEOS_EC_WP_LAYOUT_DESC=" |
| RO, |
| | |
| RW, |
| " |
| |
| # Default Chrome OS EC Firmware RO/RW list |
| CHROMEOS_EC_RO_LIST="EC_RO" |
| CHROMEOS_EC_RW_LIST="EC_RW" |
| |
| # Default Chrome OS Firmware verification skip list |
| # Syntax: list of string tuples (separated by ':'): PARTNAME:OFFSET:SIZE |
| # Use this when flashrom chip will be automatically changed data during rewrite |
| # (eg, time stamp, checksum) |
| CHROMEOS_BIOS_SKIP_VERIFY_LIST="" |
| CHROMEOS_EC_SKIP_VERIFY_LIST="EC_RO:0x48:4" |
| |
| # A list (separated by space) of sections that you want to always preserve, even |
| # in factory whole image rewrite. Example: "RO_VPD FV_GBB" |
| CHROMEOS_BIOS_PRESERVE_LIST="" |
| CHROMEOS_EC_PRESERVE_LIST="" |
| |
| # Default list of those targets requiring foreground execution for updates |
| # (eg, Embedded Controller, because it may freeze keyboard) |
| CHROMEOS_NEED_FOREGROUND_UPDATE_TARGETS="" |
| |
| # Default list of those targets requiring to reboot immediately after updates |
| # (eg, Embedded Controller, because it may freeze keyboard) |
| # Note: does not apply to factory, as factory install is noninteractive. |
| CHROMEOS_NEED_DIRECT_REBOOT_UPDATE_TARGETS="" |
| |
| # Default list of targets to be updated. Can be changed by --targets='xxx'. |
| CHROMEOS_UPDATE_TARGETS="bios ec" |
| |
| # ---------------------------------------------------------------------------- |
| # Global variables |
| |
| # Set this to 1 to support auto updates |
| # TODO(hungte) this is now set to zero because we don't prefer any updates on |
| # legacy systems like CR48. We may set this to 1, make it a command line param, |
| # or remove whole legacy updater in future. |
| is_allow_au=0 |
| |
| # Set this to 1 for more messages |
| is_verbose=0 |
| |
| # Set this to 1 to allow rebooting system directly if required |
| is_allow_reboot=0 |
| |
| # Set this to 1 to enable background update process |
| is_background=0 |
| |
| # Set this to 1 to enable factory test / installer mode |
| is_factory=0 |
| |
| # Set this to 1 for recovery mode |
| is_recovery=0 |
| |
| # Set this to 1 to check/update only RW parts (skip RO), |
| # 0 for RW (A/B at the same time) plus RO code (boot stub / recovery image) |
| # and keep VPD/GBB. |
| is_rw_only=1 |
| |
| # Set this to 1 to enable checking flash memory layout compatibility |
| is_check_layout=1 |
| |
| # Set this to 1 to enable checking ChromeOS rootkey compatibility |
| is_check_rootkey=1 |
| |
| # Set this to 1 to enable checking ChromeOS firmware signature (VBOOT) |
| is_check_vboot=1 |
| |
| # Set this to 0 to disable Chrome OS A/B style 2 stage update |
| # (can be changed by --rw-ab-together) |
| allow_2stage_update=1 |
| |
| # Set this to 1 to enable write protection after update complete |
| is_enable_write_protect=0 |
| |
| # Set this to 1 to enable double confirm of each write to flashrom |
| is_always_verify=0 |
| |
| # Set this to 1 to provide debug messages |
| is_debug=0 |
| |
| # Set to 0 if you don't want to use fmap_decode for layout |
| allow_fmap_decode_layout=1 |
| |
| # (internal) File name of current flashrom data |
| CURRENT_IMAGE='_current.bin' |
| # (internal) File name of current flashrom layout |
| CURRENT_LAYOUT='_layout.txt' |
| # (internal) Skip verify list of current target |
| CURRENT_SKIP_VERIFY="" |
| # (internal) Target option for flashom |
| CURRENT_TARGET_OPT="" |
| # (internal) List to for keeping what targets has been modified. |
| CURRENT_MODIFIED_TARGETS="" |
| |
| # (internal) layout data structure |
| # Many POSIX shells do not support association/array, let's use a simple |
| # string list to simulate array (since the numer of list is not so much). |
| LAYOUT_LIST="" |
| LAYOUT_SIZE="" |
| LAYOUT_OFFSET="" |
| |
| # Script should exit on any unexpected error |
| set -e |
| |
| # Use bundled tools with highest priority, to prevent dependency when updating |
| cros_setup_path |
| |
| # ---------------------------------------------------------------------------- |
| # Firmware Update Procedure |
| # |
| # TODO(hungte) We can check firmware version number with the version in TPM |
| # later. If it's older than TPM then we can skip update process |
| # because we know it will eventually fail. |
| # ---------------------------------------------------------------------------- |
| # We support both factory setup and auto-update here. |
| # 1. Read current flashrom. |
| # 2. If RO part is different (factory first-time setup), rewrite whole image: |
| # 2a. If rewrite failed, abort. |
| # 2b. If rewrite succeeded, exit as normal. |
| # 3. If we are running in 'factory test' or 'factory installer' mode, |
| # update all different RW parts at the same time. |
| # 3a. If rewrite failed, abort. |
| # 3b. If rewrite succeeded, exit as normal. |
| # 4. (partial-update) Compare RW parts, and rewrite only those different. |
| # NOTE: for Chrome OS main firmware (A/B design), see "Chrome OS Main |
| # Firmware Update Procedure" below, otherwise: |
| # 4a. If rewrite failed, abort. |
| # 4b. If rewrite succeeded, exit as normal or reboot if required. |
| # ---------------------------------------------------------------------------- |
| # Chrome OS Main Firmware Update Procedure: |
| # The following procedure use these terms to refer firmware images: |
| # A = firmware A in current flash ROM (VBOOTA+FVMAIN) |
| # B = firmware B in current flash ROM (VBOOTB+FVMAINB) |
| # T = "target", image carried in update pack (shell-ball, part A == part B) |
| # 1. If T == B == A, that means no need to update. |
| # Do nothing and exit. |
| # 2. Get last boot firmware index. |
| # 3. If last boot = B... |
| # Do copy B to A. |
| # 3a. If T == B, that means a successful update. |
| # Do nothing and exit. |
| # 3b. If T != B, that means A went bad. |
| # Skip to 5. |
| # 4. Now last boot = A. |
| # If T == B, that means trial boot from B failed; |
| # Do copy A to B and exit. |
| # 5. Now T != B... |
| # Do copy T to B. |
| # 5a. If T != A, this is a fresh update we need to try. |
| # Set firmware_try_b and reboot. |
| # 5b. If T == A, this means B went bad. |
| # Simply do nothing (no need to reboot). |
| # ---------------------------------------------------------------------------- |
| |
| # Utilities |
| |
| is_positive() { |
| # Returns $FLAGS_TRUE or $FLAGS_FALSE for compare result of $1 > 0 |
| # NOTE: this function CANNOT invoke check_param. |
| [ $1 -gt 0 ] |
| } |
| |
| list_car() { |
| # (lisp-style) Prints the first parameter (to ease parsing) |
| # NOTE: this function CANNOT invoke check_param. |
| echo $1 |
| } |
| |
| list_cdr() { |
| # (lisp-style) Discards the first parameter and prints the remainings |
| # NOTE: this function CANNOT invoke check_param. |
| shift |
| echo $@ |
| } |
| |
| list_length() { |
| # (lisp-style) Prints the length of param. |
| # NOTE: this function CANNOT invoke check_param. |
| echo $# |
| } |
| |
| nth() { |
| # (lisp-style) Prints the (zero-based) nth parameter |
| # NOTE: this function CANNOT invoke check_param. |
| # Syntax: nth(n, ...) |
| shift $(($1 + 1)) |
| echo $1 |
| } |
| |
| assert_str() { |
| # Checks if $1 is non-empty string, and print message provided in $2 |
| # NOTE: this function CANNOT invoke check_param. |
| if [ -z "$1" ]; then |
| if [ -z "$*" ]; then |
| die "!!! assert_str failed. abort." |
| else |
| die "!!! assert_str failed: $*. abort." |
| fi |
| fi |
| } |
| |
| in_list() { |
| # Determines if a given name is in shell-style list. |
| check_param "in_list(name, list)" "$@" |
| |
| local name |
| for name in $2; do |
| if [ "$name" = "$1" ]; then |
| return $FLAGS_TRUE |
| fi |
| done |
| return $FLAGS_FALSE |
| } |
| |
| echo_file_size() { |
| # Prints the size of file $1 |
| check_param "echo_file_size(filename)" "$@" |
| |
| list_car $(wc -c $1) |
| } |
| |
| execute_command() { |
| # Executes a shell command (and provide message if required) |
| check_param "execute_command(cmd)" "$@" |
| local ret=$FLAGS_TRUE |
| |
| # to interpret quotes correctly, use $* instead of $@ |
| is_positive $is_verbose && alert " * execute_command: $*" || true |
| ( eval "$@" ) >_exec.stdout 2>_exec.stderr || ret=$? |
| if [ "$ret" != "0" ]; then |
| alert " !! Execution failed ($ret): $*" |
| alert " Messages:" |
| cat _exec.stdout >&2 |
| cat _exec.stderr >&2 |
| fi |
| return $ret |
| } |
| |
| has_command() { |
| # Test if some command is available in this system. |
| check_param "has_command(cmd)" "$@" |
| |
| # XXX 'type' in dash does not have '-P' option |
| type "$1" >/dev/null 2>&1 || return $? |
| } |
| |
| compare_file() { |
| # Compare two files (platform independent) |
| check_param "compare_file(file1, file2)" "$@" |
| |
| # try to find a file compare tool... |
| if has_command cmp; then |
| # cmp and diff are usually in same package. |
| debug_msg "compare with: cmp -s $1 $2" |
| cmp -s $1 $2 || return $? |
| elif has_command md5sum; then |
| debug_msg "compare with: md5sum $1 $2" |
| local md5_1 md5_2 |
| md5_1=$(md5sum $1) && md5_1=$(list_car $md5_1) || die "cannot md5sum $1" |
| md5_2=$(md5sum $2) && md5_2=$(list_car $md5_2) || die "cannot md5sum $2" |
| debug_msg "md5: $1=$md5_1, $2=$md5_2" |
| [ "$md5_1" = "$md5_2" ] || return $? |
| [ -n "$md5_1" ] || return $? |
| elif has_command od; then |
| debug_msg "compare with: od $1 $2" |
| local od1 od2 |
| od1=$(od -t x1 $1) || die "cannot od $1" |
| od2=$(od -t x1 $2) || die "cannot od $2" |
| [ "$od1" = "$od2" ] || return $? |
| else |
| # TODO(hungte) we may even use hexdump, od, hd, uuencode... |
| die "sorry, cannot find any file compare tools." |
| fi |
| return $FLAGS_TRUE |
| } |
| |
| # ---------------------------------------------------------------------------- |
| # Flashrom Specific Utilities |
| |
| flashrom_read_whole() { |
| # Reads entire flashrom into $CURRENT_IMAGE file. |
| check_param "flashrom_read_whole()" "$@" |
| |
| execute_command "$FLASHROM_TOOLPATH $CURRENT_TARGET_OPT -r $CURRENT_IMAGE" || |
| die "cannot read flashrom" |
| } |
| |
| flashrom_write_whole() { |
| # Writes entire flashrom image from given file. |
| check_param "flashrom_read_whole(image_file)" "$@" |
| |
| execute_command "$FLASHROM_TOOLPATH $CURRENT_TARGET_OPT -w $1" || |
| die "cannot write whole flashrom" |
| } |
| |
| flashrom_lookup_section_info() { |
| # looks up section and return related nth data |
| check_param "flashrom_lookup_section_info(name, info_list)" "$@" |
| |
| local name i=0 |
| for name in $LAYOUT_LIST; do |
| if [ "$name" = "$1" ]; then |
| echo $(nth $i $2) |
| return |
| fi |
| i=$(($i + 1)) |
| done |
| die "invalid section name: $1 [$2]" |
| } |
| |
| flashrom_echo_section_offset() { |
| # Prints section offset from current layout |
| check_param "flashrom_echo_section_offset(section_name)" "$@" |
| |
| flashrom_lookup_section_info $1 "$LAYOUT_OFFSET" |
| } |
| |
| flashrom_echo_section_size() { |
| # Prints section size from current layout |
| check_param "flashrom_echo_section_size(section_name)" "$@" |
| |
| flashrom_lookup_section_info $1 "$LAYOUT_SIZE" |
| } |
| |
| flashrom_check_valid_section_name() { |
| # Checks if $1 is a valid section name. |
| check_param "flashrom_check_valid_section_name(section_name)" "$@" |
| |
| # if anything goes wrong, flashrom_lookup_section_info should die |
| flashrom_echo_section_offset $1 >/dev/null |
| flashrom_echo_section_size $1 >/dev/null |
| } |
| |
| flashrom_partial_write() { |
| # Writes (partially) given sections to flashrom |
| check_param "flashrom_partial_write(list, image_file)" "$@" |
| |
| local list i |
| for i in $1 ; do |
| # check if every given section is in layout because flashrom does not check. |
| flashrom_check_valid_section_name $i |
| list="$list -i $i" |
| done |
| local opt="$CURRENT_TARGET_OPT -l $CURRENT_LAYOUT $list -w $2" |
| execute_command "$FLASHROM_TOOLPATH $opt" || |
| die "flashrom_partial_write failed" |
| } |
| |
| flashrom_select_target() { |
| # Selects flashrom mapping target |
| check_param "flashrom_select_target(target)" "$@" |
| |
| case $1 in |
| bios ) |
| CURRENT_TARGET_OPT="$CHROMEOS_SELECT_BIOS_OPT" |
| ;; |
| ec ) |
| CURRENT_TARGET_OPT="$CHROMEOS_SELECT_EC_OPT" |
| ;; |
| reset ) |
| CURRENT_TARGET_OPT="" |
| ;; |
| * ) |
| die "unknown target for flashrom selection: $1" |
| esac |
| } |
| |
| flashrom_enable_write_protect() { |
| # Enables the "write protection" for specified section on flashrom. |
| check_param "flashrom_enable_write_protect(section)" "$@" |
| |
| flashrom_check_valid_section_name $1 || |
| die "flashrom_enable_write_protect: invalid target section: $1" |
| local offset=$(flashrom_echo_section_offset $1) |
| local size=$(flashrom_echo_section_size $1) |
| |
| debug_msg "flashrom_enable_write_protect(section=$1, off=$offset, sz=$size)" |
| local opt="$CURRENT_TARGET_OPT" |
| execute_command "$FLASHROM_TOOLPATH $opt --wp-range $offset $size" || |
| die "flashrom_enable_write_protect failed (wp-range $offset $size)" |
| execute_command "$FLASHROM_TOOLPATH $opt --wp-enable" || |
| die "flashrom_enable_write_protect failed (wp-enable $offset $size)" |
| } |
| |
| flashrom_build_layout_file() { |
| # Creates $CURRENT_LAYOUT file according to current layout information |
| check_param "flashrom_build_layout_file()" "$@" |
| |
| # write layout file |
| rm -f $CURRENT_LAYOUT |
| local i=0 name size offset off_beg off_end off_size |
| for name in $LAYOUT_LIST ; do |
| size=$(nth $i $LAYOUT_SIZE) |
| offset=$(nth $i $LAYOUT_OFFSET) |
| off_beg=$(printf '0x%06x' $offset) |
| off_end=$(printf '0x%06x' $(($offset + $size - 1))) |
| off_size=$(printf '0x%06x' $size) |
| echo "$off_beg:$off_end $name" >>$CURRENT_LAYOUT |
| debug_msg "LAYOUT: off=$off_beg size=$off_size $name -> $offset $size" |
| i=$(($i + 1)) |
| done |
| |
| [ $i != 0 ] || die "Empty memory layout information." |
| } |
| |
| flashrom_detect_layout_by_fmap_decode() { |
| # Detects flashrom layout by given image file and creates $CURRENT_LAYOUT |
| check_param "flashrom_detect_layout_by_fmap_decode(image_file)" "$@" |
| |
| debug_msg "trying flashrom_detect_layout_by_fmap_decode" |
| local voff vsize vnames pre_parse |
| pre_parse="$($CHROMEOS_FMAP_DECODE_CMD $1 2>/dev/null)" || |
| return $FLAGS_FALSE |
| if [ -z "$pre_parse" ]; then |
| return $FLAGS_FALSE |
| fi |
| debug_msg "detect_layout_by_fmap_decode" |
| voff=$(echo "$pre_parse" | |
| grep 'area_offset="' | |
| sed 's/.*area_offset="\([^"]*\)".*/\1/' ); |
| vsize=$(echo "$pre_parse" | |
| grep 'area_size="' | |
| sed 's/.*area_size="\([^"]*\)".*/\1/' ); |
| # debug_msg "offsets: $voff" |
| # debug_msg "sizes: $vsize" |
| |
| # convert and build name array. Note shell script does not allow spaces in |
| # list, so we convert spaces to '%20' temporary. |
| vnames=$(echo "$pre_parse" | sed 's/" area_/"\tarea_/g' | awk -F '\t' ' |
| BEGIN { |
| n="Log Volume"; map[n]="FV_LOG"; |
| n="Firmware A Key"; map[n]="VBOOTA"; |
| n="Firmware A Data"; map[n]="FVMAIN"; |
| n="Firmware B Key"; map[n]="VBOOTB"; |
| n="Firmware B Data"; map[n]="FVMAINB"; |
| n="Recovery Firmware"; map[n]="FVDEV"; |
| n="GBB Area"; map[n]="FV_GBB"; |
| n="Boot Stub"; map[n]="FV_BSTUB"; |
| n="RO VPD"; map[n]="RO_VPD"; |
| n="RW VPD"; map[n]="RW_VPD"; |
| |
| n="VBLOCK_A"; map[n]="VBOOTA"; |
| n="FW_MAIN_A"; map[n]="FVMAIN"; |
| n="VBLOCK_B"; map[n]="VBOOTB"; |
| n="FW_MAIN_B"; map[n]="FVMAINB"; |
| n="GBB"; map[n]="FV_GBB"; |
| n="BOOT_STUB"; map[n]="FV_BSTUB"; |
| n="RECOVERY"; map[n]="FVDEV"; |
| } |
| /area_/ { |
| gsub( /.*area_name="/, "" ); |
| gsub( /".*/, "" ); |
| if ($1 in map) |
| print map[$1]; |
| else { |
| gsub( / /, "%20" ); |
| print $1; |
| } |
| } |
| ') |
| |
| # verify collected information |
| [ $(list_length $voff) -eq $(list_length $vsize) ] || |
| die "number of offsets ($(list_length $voff))" \ |
| " must match sizes ($(list_length $vsize))" |
| [ $(list_length $voff) -eq $(list_length $vnames) ] || |
| die "number of offsets ($(list_length $voff))" \ |
| " must match names ($(list_length $vnames))" |
| |
| # rebuild layout information |
| LAYOUT_LIST="$vnames" |
| local i=0 |
| local name |
| for name in $LAYOUT_LIST; do |
| LAYOUT_SIZE="$LAYOUT_SIZE $(($(nth $i $vsize)))" |
| LAYOUT_OFFSET="$LAYOUT_OFFSET $(($(nth $i $voff)))" |
| i=$(($i + 1)) |
| done |
| |
| flashrom_build_layout_file |
| return $FLAGS_TRUE |
| } |
| |
| flashrom_detect_layout_by_default_map() { |
| # Detects flashrom layout by $CURRENT_IMAGE and creates $CURRENT_LAYOUT |
| check_param "flashrom_detect_layout_by_default_map(target)" "$@" |
| |
| debug_msg "flashrom_detect_layout: by_default_map" |
| if [ ! -s $CURRENT_IMAGE ]; then |
| die "invalid flashrom image in $CURRENT_IMAGE" |
| fi |
| local rom_size=$(echo_file_size $CURRENT_IMAGE) |
| local layout_desc |
| |
| case $1 in |
| bios ) |
| layout_desc="$CHROMEOS_BIOS_LAYOUT_DESC" |
| ;; |
| ec ) |
| layout_desc="$CHROMEOS_EC_LAYOUT_DESC" |
| ;; |
| bios_wp ) |
| layout_desc="$CHROMEOS_BIOS_WP_LAYOUT_DESC" |
| ;; |
| ec_wp ) |
| layout_desc="$CHROMEOS_EC_WP_LAYOUT_DESC" |
| ;; |
| * ) |
| die "unknown target for layout detection: $1" |
| esac |
| |
| # calculate block size |
| local blocks=$(($(echo $layout_desc | sed 's/[^|]//g' | wc -c))) |
| local bs=$((rom_size / blocks)) |
| |
| # canonicalize layout description |
| local parsed=$(echo $layout_desc | sed 's/[ \t]*//g; s/[|]/,|,/g') |
| |
| # use awk to generate the form NAME=SIZE. |
| # variable length fields will get duplicated (with determined final size) at |
| # end of each block, with prefix '|' -- this is called "fix-up field". |
| # NOTE: awk compatibility issue here. |
| # awk on modern BSD takes 0x as hex numbers no strtonum. |
| # gawk (awk on Linux) has strtonum bug accepts 0x only in --posix. |
| local awk_cmd='gawk --posix' |
| has_command gawk || awk_cmd='awk' |
| debug_msg "selected awk command: $awk_cmd" |
| parsed="$(echo -n "$parsed" | $awk_cmd -v bs=$bs ' |
| BEGIN { FS = "="; RS="," } |
| /^[^\|]/ { v=$2+0; print $1"="v; sum+=v; if(v==0) zero=$1 } |
| /^\|/ { print "|"zero"="bs-sum; sum=0 } |
| END { print "|"zero"="bs-sum; sum=0 } |
| ')" || die "flashrom_detect_layout: failed to process with awk." |
| |
| # build list and size of each section |
| local entry ename esize |
| for entry in $parsed ; do |
| ename=${entry%%=*} |
| assert_str "$ename" "parsing layout failed: $entry, missing section name" |
| esize=${entry##*=} |
| assert_str "$ename" "parsing layout failed: $entry, missing section size" |
| esize=$(($esize)) |
| if [ "${ename%%|*}" = "" ]; then |
| # found a "fix-up" field, modify the field. |
| debug_msg "fix layout size [$ename] to $esize" |
| LAYOUT_SIZE=$(echo $LAYOUT_SIZE | sed "s/FIXME/$esize/") |
| else |
| [ $esize -eq 0 ] && esize=FIXME || true |
| LAYOUT_SIZE="$LAYOUT_SIZE $esize" |
| LAYOUT_LIST="$LAYOUT_LIST $ename" |
| fi |
| done |
| |
| # build offsets |
| local offset=0 |
| for esize in $LAYOUT_SIZE; do |
| LAYOUT_OFFSET="$LAYOUT_OFFSET $offset" |
| offset=$(($offset + $esize)) |
| done |
| |
| flashrom_build_layout_file |
| } |
| |
| flashrom_reset_layout() { |
| # Resets all previous detected layout information |
| |
| LAYOUT_LIST="" |
| LAYOUT_SIZE="" |
| LAYOUT_OFFSET="" |
| } |
| |
| flashrom_detect_layout() { |
| # Detects flashrom layout by target code or image file name. |
| check_param "flashrom_detect_layout(target, image_filename)" "$@" |
| |
| flashrom_reset_layout |
| assert_str $allow_fmap_decode_layout |
| if is_positive $allow_fmap_decode_layout && |
| flashrom_detect_layout_by_fmap_decode $2; then |
| debug_msg "using decoded fmap and ignore default map" |
| else |
| flashrom_detect_layout_by_default_map $1 |
| fi |
| } |
| |
| flashrom_section_filename() { |
| # Prints temporary file name for given section name ($1) |
| check_param "flashrom_section_filename(section)" "$@" |
| |
| echo _$1.blob |
| } |
| |
| flashrom_get_section() { |
| # Extract a blob data from given section of a image file, and |
| # prints the (generated) filename containing section data |
| check_param "flashrom_get_section(section, image, ...)" "$@" |
| |
| local offset=$(flashrom_echo_section_offset $1) |
| local size=$(flashrom_echo_section_size $1) |
| local fname=$3$(flashrom_section_filename $1) |
| |
| execute_command "dd if=$2 of=$fname bs=1 skip=$offset count=$size" || |
| die "flashrom_get_section($1, $2, $3)" |
| echo $fname |
| } |
| |
| flashrom_put_section() { |
| # Overwrite a section in image file from given data blob file |
| check_param "flashrom_put_section(section, image, data_file)" "$@" |
| |
| local offset=$(flashrom_echo_section_offset $1) |
| local size=$(flashrom_echo_section_size $1) |
| local data_size=$(echo_file_size $3) |
| |
| [ $data_size -eq $size ] || |
| die "flashrom_put_section: incompatible data($3) for section '$1'" |
| |
| execute_command "dd if=$3 of=$2 bs=1 seek=$offset count=$size conv=notrunc" || |
| die "flashrom_put_section($1, $2, $3)" |
| } |
| |
| flashrom_cat_section() { |
| # Read a blob data (via od) from given section of a image file, and |
| # print to stdout. |
| check_param "flashrom_cat_section(section, image)" "$@" |
| |
| local offset=$(flashrom_echo_section_offset $1) |
| local size=$(flashrom_echo_section_size $1) |
| |
| # cannot use execute_command here because we need output |
| debug_msg "(flashrom_cat_section) od -A n -t x1 -j $offset -N $size $2" |
| od -A n -t x1 -j $offset -N $size $2 || |
| die "flashrom_cat_section($1, $2)" |
| } |
| |
| flashrom_set_skip_verify_list() { |
| # Enables and checks a 'skip verify' list |
| check_param "flashrom_set_skip_verify_list(opt_list)" "$@" |
| |
| local ventry vsec voff vsize |
| local section_size |
| for ventry in $1 ; do |
| # decompose name:offset:size |
| vsec=$(echo $ventry | cut -d: -f 1) || die "invalid entry: $ventry" |
| voff=$(($(echo $ventry | cut -d: -f 2))) || die "invalid entry: $ventry" |
| vsize=$(($(echo $ventry | cut -d: -f 3))) || die "invalid entry:$ventry" |
| flashrom_check_valid_section_name $vsec |
| section_size=$(flashrom_echo_section_size $vsec) |
| if [ $(($voff + $vsize)) -gt $section_size ]; then |
| die "skip verify entry exceed section boundary: $ventry" |
| fi |
| done |
| CURRENT_SKIP_VERIFY="$1" |
| } |
| |
| flashrom_preserve_sections() { |
| # Preserves assigned sections from current flashrom to target image |
| check_param "flashrom_preserve_sections(opt_list,current,image)" "$@" |
| local new_image_name=$3 |
| |
| if [ -n "$1" ]; then |
| debug_msg "preserve sections: $opt_list" |
| new_image_name=$(env TMPDIR=. mktemp _psvXXXXXXXX) || |
| die "cannot create temporary file for preserving sections" |
| cp -f $3 $new_image_name |
| flashrom_copy_image "$1" "$1" $2 $new_image_name |
| fi |
| echo $new_image_name |
| # TODO we need some way to clear the preserve images. |
| } |
| |
| flashrom_apply_skip_verify() { |
| # Applies 'skip verify' (fill with zero) to section blob |
| check_param "flashrom_apply_skip_verify(section, blob_filename)" "$@" |
| |
| local ventry vsec voff vsize |
| local blobsz |
| local section=$1 |
| |
| for ventry in $CURRENT_SKIP_VERIFY ; do |
| # decompose name:offset:size |
| vsec=$(echo $ventry | cut -d: -f 1) |
| if [ "$vsec" != "$section" ]; then |
| continue |
| fi |
| debug_msg "found skip on $vsec" |
| # voff and vsize should be already checked. use them directly. |
| voff=$(($(echo $ventry | cut -d: -f 2))) |
| vsize=$(($(echo $ventry | cut -d: -f 3))) |
| |
| # if udev/devfs is not ready, we may need to use some other big files. |
| # $CURRENT seems like a good choice. |
| local zero_fn=/dev/zero |
| if [ ! -r $zero_fn ]; then |
| alert "warning: $zero_fn is not available. use current image." |
| zero_fn=$CURRENT_IMAGE |
| fi |
| debug_msg "apply skip verify: (sec=$1, blob=$2)[$ventry]" |
| |
| local dd_opt="if=$zero_fn of=$2 bs=1 seek=$voff count=$vsize conv=notrunc" |
| execute_command "dd $dd_opt" || |
| die "flashrom_apply_skip_verify($1, $2)[$ventry]" |
| done |
| } |
| |
| flashrom_verify_sections() { |
| # Verifies a list of sections in 2 images |
| check_param "flashrom_verify_sections(list1, list2, image1, image2)" "$@" |
| |
| # extract images |
| local list1="$1" list2="$2" image1="$3" image2="$4" |
| local result=0 len1="" len2="" |
| |
| # process if we need whole image verification (list=*) |
| if [ "$list1" = "*" ]; then |
| if [ "$list2" != "*" ]; then |
| die "(internal error) comparing whole images need both list to '*'". |
| fi |
| if [ -z "$CURRENT_SKIP_VERIFY" ]; then |
| # quick compare |
| compare_file $image1 $image2 || result=$? |
| debug_msg "flashrom_verify_image('$image1','$image2'): $result" |
| return $result |
| else |
| die "sorry, not implemented now." |
| fi |
| else |
| # set $list2 to sequential parameters. $[1-4] becomes different now. |
| set $list2 |
| local blob1 blob2 section1 section2 |
| for section1 in $list1; do |
| section2=$1 |
| assert_str "$section2" "unmatched verify list: $list1 <-> $list2" |
| shift |
| # TODO(hungte) even if skip_verify list is not empty, we can still use od |
| # to compare those fields not in skip_verify list. |
| if has_command od && [ -z "$CURRENT_SKIP_VERIFY" ]; then |
| # quick compare via od+shell |
| blob1=$(flashrom_cat_section $section1 $image1) && |
| blob2=$(flashrom_cat_section $section2 $image2) || |
| die "invalid section in $section1 $section2" |
| len1=${#blob1} |
| len2=${#blob2} |
| # XXX we can't check "$len1=$len2" because cat results may be very |
| # different. |
| debug_msg "(cat) section1[$section1]:$len1, section2[$section2]:$len2" |
| [ $len1 -gt 0 ] || die "empty section!? check $section1" |
| [ $len2 -gt 0 ] || die "empty section!? check $section2" |
| [ "$blob1" = "$blob2" ] || result=$? |
| unset blob1 blob2 # hope this helps free memory |
| else |
| # extract blobs and compare |
| blob1=$(flashrom_get_section $section1 $image1 _fvs1) && |
| blob2=$(flashrom_get_section $section2 $image2 _fvs2) || |
| die "invalid section in $section1 $section2" |
| flashrom_apply_skip_verify $section1 $blob1 |
| flashrom_apply_skip_verify $section2 $blob2 |
| len1="$len1,$(echo_file_size $blob1)" && |
| len2="$len2,$(echo_file_size $blob2)" || |
| die "cannot get file size: $blob1 $blob2" |
| [ "$len1" = "$len2" ] || |
| die "flashrom_verify_sections: unmatched length: " \ |
| "$section1:$len1 $section2:$len2" |
| compare_file $blob1 $blob2 || result=$? |
| # clean up if not debug mode |
| is_positive $is_debug || rm -f $blob1 $blob2 |
| fi |
| # quick abort |
| [ "$result" = "0" ] || break |
| done |
| fi |
| |
| debug_msg "flashrom_verify_sections($list1,$list2,$image1,$image2): $result" |
| return $result |
| } |
| |
| flashrom_copy_image() { |
| # Copies section data between two image files. |
| check_param "flashrom_copy_image(list_src, list_dst, img_src, img_dst)" "$@" |
| |
| # extract images |
| local section_src section_dst |
| local list_src="$1" list_dst="$2" image_src="$3" image_dst="$4" |
| set $2 |
| for section_src in $list_src; do |
| section_dst=$1 |
| shift |
| debug_msg "flashrom_copy_image: $section_src->$section_dst" |
| assert_str "$section_dst" "copy_image: unmatched: '$list_src':'$list_dst'" |
| local offset_src=$(flashrom_echo_section_offset $section_src) |
| local size_src=$(flashrom_echo_section_size $section_src) |
| local offset_dst=$(flashrom_echo_section_offset $section_dst) |
| local size_dst=$(flashrom_echo_section_size $section_dst) |
| if [ "$size_src" != "$size_dst" ]; then |
| die "copy_image: incompatible section: $section_src,$section_dst" |
| fi |
| local dd_cmd="dd if=$image_src of=$image_dst bs=1 conv=notrunc" |
| dd_cmd="$dd_cmd skip=$offset_src seek=$offset_dst count=$size_src" |
| execute_command "$dd_cmd" || die "flashrom_copy_image() faild" |
| done |
| } |
| |
| # ---------------------------------------------------------------------------- |
| # Chrome OS Specific Utilities |
| |
| chromeos_need_reboot() { |
| # Utility to schedule a system reboot. |
| check_param "chromeos_need_reboot()" "$@" |
| |
| # In current Chrome OS Auto Update design, the actual behavior is to |
| # request firmware update script being executed again after reboot. |
| touch "$CHROMEOS_NEED_REBOOT_TAG" || die "cannot tag for reboot" |
| sync # to make sure the tag is flushed to disk |
| } |
| |
| chromeos_get_last_boot_index() { |
| # Utility to get last boot BIOS firmware index |
| check_param "chromeos_get_last_boot_index()" "$@" |
| |
| # See "Google Chrome OS Firmware - High Level Specification" section |
| # "BINF (Chrome OS boot information)" for more detail. |
| local binf_val="$(crossystem mainfw_act || true)" |
| debug_msg "original binf_val=$binf_val" |
| |
| # Possible values: 'A', 'B', 'recovery' |
| # get_index return values requires 0=A, 1=B |
| case $binf_val in |
| A | recovery ) |
| return 0 |
| ;; |
| B ) |
| return 1 |
| ;; |
| * ) |
| die "unknown last boot index in BINF.1: [$binf_val]." |
| esac |
| } |
| |
| chromeos_check_scheduled_trial_reboot() { |
| # Utility to check if a reboot with try_firmware_b is scheduled. |
| # Returns $FLAGS_TRUE or $FLAGS_FALSE |
| check_param "chromeos_check_scheduled_trial_reboot()" "$@" |
| |
| local bootinfo=$(eval $CHROMEOS_QUERY_TRIAL_BOOT_CMD 2>/dev/null) |
| if [ "$bootinfo" = "1" ]; then |
| debug_msg "chromeos_check_scheduled_trial_reboot: TRUE" |
| return $FLAGS_TRUE |
| fi |
| debug_msg "chromeos_check_scheduled_trial_reboot: FALSE" |
| return $FLAGS_FALSE |
| } |
| |
| chromeos_change_boot_index() { |
| # Utility to change firmware boot index. |
| check_param "chromeos_change_boot_index()" "$@" |
| |
| # In Chrome OS, we use the "try_firmware_b". |
| execute_command "$CHROMEOS_CHANGE_BOOT_INDEX_CMD" || |
| die "chromeos_change_boot_index(): failed to set try_firmware_b" |
| |
| # Workaround for chrome-os-partner:1563. Touch the try-firmware-B flag file |
| # in case CMOS loses the flag. |
| touch "$NEED_FIRMWARE_TRYB" |
| } |
| |
| chromeos_need_foreground() { |
| # Determines if given target needs foreground update. |
| # Returns $FLAGS_TRUE or $FLAGS_FALSE |
| check_param "chromeos_need_foreground(target)" "$@" |
| |
| case $1 in |
| bios | ec ) |
| true |
| ;; |
| * ) |
| die "chromeos_need_foreground: invalid target ($1)" |
| esac |
| |
| local target |
| for target in $CHROMEOS_NEED_FOREGROUND_UPDATE_TARGETS; do |
| if [ "$target" = $1 ]; then |
| debug_msg "chromeos_need_foreground: NEED ($target)" |
| return $FLAGS_TRUE |
| fi |
| done |
| return $FLAGS_FALSE |
| } |
| |
| chromeos_post_update() { |
| # Put all procedures / checks here to perform after an update (which really |
| # changed some data on flashrom). |
| check_param "chromeos_post_update(target)" "$@" |
| |
| # Log modified targets |
| CURRENT_MODIFIED_TARGETS="$CURRENT_MODIFIED_TARGETS $1" |
| } |
| |
| chromeos_check_background_update() { |
| # Checks if given target is safe for background update. |
| # Returns $FLAGS_TRUE for safe to continue, $FLAGS_FALSE to abort. |
| check_param "chromeos_check_background_update(target)" "$@" |
| |
| assert_str $is_background |
| local target="$1" |
| if is_positive $is_background && chromeos_need_foreground "$target"; then |
| echo " - running in background update mode, postpone to next boot." |
| chromeos_need_reboot || die "cannot set reboot tag" |
| return $FLAGS_FALSE |
| fi |
| return $FLAGS_TRUE |
| } |
| |
| chromeos_check_same_root_keys() { |
| # Checks if the root keys (from Google Binary Block) are the same |
| # Returns $FLAGS_TRUE for same, $FLAGS_FALSE for different, |
| # $FLAGS_ERROR for missing in current image |
| check_param "chromeos_check_same_root_keys(current, target)" "$@" |
| |
| # if we can't find gbb_utility, ignore it. |
| if ! has_command "gbb_utility"; then |
| debug_msg "cannot find gbb_utility." |
| return $FLAGS_TRUE |
| fi |
| |
| # try to retrieve the keys |
| local ret=$FLAGS_TRUE |
| local keyfile1 keyfile2 keyfile1_strip keyfile2_strip keyfiles |
| keyfile1=$(env TMPDIR=. mktemp _gk1XXXXXXXX) || |
| die "canot create temporary file for root key retrieval" |
| keyfile2=$(env TMPDIR=. mktemp _gk2XXXXXXXX) || |
| die "canot create temporary file for root key retrieval" |
| keyfile1_strip=${keyfile1}_strip |
| keyfile2_strip=${keyfile2}_strip |
| keyfiles="$keyfile1 $keyfile2 $keyfile1_strip $keyfile2_strip" |
| # current may not contain root key, but target MUST have a root key |
| if execute_command "gbb_utility -g --rootkey=$keyfile1 $1" 2>/dev/null; then |
| execute_command "gbb_utility -g --rootkey=$keyfile2 $2" || |
| die "cannot get rootkey from $2" |
| # to workaround key paddings... |
| cat $keyfile1 | sed 's/\xff*$//g; s/\x00*$//g;' >$keyfile1_strip |
| cat $keyfile2 | sed 's/\xff*$//g; s/\x00*$//g;' >$keyfile2_strip |
| compare_file "$keyfile1_strip" "$keyfile2_strip" || ret=$FLAGS_FALSE |
| else |
| debug_msg "warning: cannot get rootkey from $1" |
| ret=$FLAGS_ERROR |
| fi |
| |
| # clean up if not debug mode |
| is_positive $is_debug || rm -f $keyfiles |
| debug_msg "chromeos_check_same_root_keys: result(shell-style)=$ret" |
| return $ret |
| } |
| |
| |
| # ---------------------------------------------------------------------------- |
| # Updater |
| |
| start_firmware_section_update() { |
| # Updates some sections into system flashrom. |
| check_param "start_firmware_section_update(src_list, dst_list, image) " "$@" |
| debug_msg "start_firmware_section_update(src=$1,dest=$2,image=$3)" |
| |
| # create image for flashrom |
| debug_msg "creating temporary image file" |
| local tmp_image |
| tmp_image=$(env TMPDIR=. mktemp _imgXXXXXXXX) || |
| die "cannot create temporary file for building image" |
| cp -f $CURRENT_IMAGE $tmp_image |
| flashrom_copy_image "$1" "$2" $3 $tmp_image || |
| die "failed to create temporary image for update" |
| |
| # write to flashrom |
| debug_msg "writing partial ($2) image to flashrom" |
| flashrom_partial_write "$2" $tmp_image || |
| die "failed to partial write to flashrom" |
| |
| # verify image |
| if is_positive $is_always_verify ; then |
| debug_msg "verifying image from flashrom: read current image" |
| flashrom_read_whole || |
| die "cannot read flashrom for verification" |
| # TODO(hungte) current implementation verifies only copy destination. |
| # we may consider comparing whole image in the future. |
| debug_msg "verifying image from flashrom: compare with target image" |
| flashrom_verify_sections "$1" "$2" $CURRENT_IMAGE $tmp_image || |
| die "update result: flashrom image data verification failed." |
| fi |
| |
| # clean up if not debug mode |
| is_positive $is_debug || rm -f $tmp_image |
| } |
| |
| chromeos_firmware_AB_update() { |
| # Performs Chrome OS "AB Main Firmware" Update Rules |
| check_param "chromeos_firmware_AB_update(listA, listB, new_image)" "$@" |
| |
| local listA="$1" |
| local listB="$2" |
| local target=$3 |
| local base=$CURRENT_IMAGE |
| local index_A=0 |
| local index_B=1 |
| local T_equals_A=1 |
| local T_equals_B=1 |
| |
| flashrom_verify_sections "$listA" "$listA" $base $target || T_equals_A=0 |
| flashrom_verify_sections "$listB" "$listB" $base $target || T_equals_B=0 |
| debug_msg "AB_update: T_equals_A=$T_equals_A T_equals_B=$T_equals_B" |
| |
| # T==A==B should be already handled... |
| if is_positive $T_equals_A && is_positive $T_equals_B ; then |
| die "T==A==B, must be handled somewhere else." |
| fi |
| |
| # A, B should be the same for target image. |
| flashrom_verify_sections "$listA" "$listB" $target $target || |
| die "invalid image file (different A/B section): $target" |
| |
| # determine last boot index |
| local last_boot_index=0 |
| chromeos_get_last_boot_index || last_boot_index=$? |
| debug_msg "AB_update: last_boot_index=$last_boot_index" |
| if [ $last_boot_index != $index_A -a $last_boot_index != $index_B ]; then |
| die "unknown boot index: $last_boot_index" |
| fi |
| |
| if [ $last_boot_index = $index_B ]; then |
| # do copy B to A |
| if is_positive $T_equals_B ; then |
| echo " * action: copy B to A (after successful update)" |
| start_firmware_section_update "$listB" "$listA" $base |
| return |
| else |
| echo " * action: copy B to A (A went bad)" |
| start_firmware_section_update "$listB" "$listA" $base |
| last_boot_index=$index_A |
| T_equals_A=$T_equals_B |
| fi |
| fi |
| |
| if is_positive $T_equals_B && |
| chromeos_check_scheduled_trial_reboot; then |
| # A very special case: if boot with B was already scheduled, that means |
| # an update has not been verified yet while update program was invoked |
| # again. Treat T_equals_B as FALSE to prevent a rollback. |
| echo " * action: skip (same update already scheduled and need a reboot)" |
| return |
| fi |
| |
| if is_positive $T_equals_B ; then |
| # trial boot from B failed (need recover) |
| echo " * action: copy A to B (after try-boot failure)" |
| start_firmware_section_update "$listA" "$listB" $base |
| return |
| fi |
| |
| # either a flash update, or B went bad |
| if ! is_positive $T_equals_A ; then |
| echo " * action: copy T to B (flash update, try_b then reboot)" |
| start_firmware_section_update "$listB" "$listB" $target |
| echo " - selecting firmware B for next boot" |
| chromeos_change_boot_index || die "cannot set try_firmware_b" |
| echo " - need to reboot..." |
| chromeos_need_reboot || die "cannot set update tag for next reboot." |
| else |
| echo " * action: copy T to B (B went bad)" |
| start_firmware_section_update "$listB" "$listB" $target |
| fi |
| } |
| |
| check_chromeos_vboot_section() { |
| # Checks if the layout has proper VBOOT record |
| check_param "check_chromeos_vboot_section(vboot_list, image_file)" "$@" |
| |
| local vboot_section_name offset length value |
| for vboot_section_name in $1; do |
| offset="$(flashrom_echo_section_offset $vboot_section_name)" |
| length="$CHROMEOS_VBOOT_SIGNATURE_LENGTH" |
| value="$(dd if=$2 bs=1 skip=$offset count=$length 2>/dev/null)" |
| debug_msg "check_chromeos_vboot_section: $vboot_section_name@$offset:$value" |
| if [ "$value" != "$CHROMEOS_VBOOT_SIGNATURE" ]; then |
| return $FLAGS_FALSE |
| fi |
| done |
| return $FLAGS_TRUE |
| } |
| |
| build_layout_signature() { |
| # Builds a signature of given layout sections. |
| check_param "build_layout_signature(section_list)" "$@" |
| |
| local signature="LAYOUT_SIG" |
| local name="" offset="" size="" |
| for name in $1; do |
| offset=$(flashrom_echo_section_offset $name) |
| size=$(flashrom_echo_section_size $name) |
| signature="$signature,($name,$offset,$size)" |
| done |
| echo $signature |
| } |
| |
| update_firmware() { |
| # Firmware update procedure. |
| check_param "update_firmware( |
| code, name, image, ro, rw_a, |
| opt_rw_b, opt_skip, opt_preserve, opt_vboot, opt_rootkey)" "$@" |
| |
| local code=$1 |
| local image_fname=$3 current_fname=$CURRENT_IMAGE |
| local orig_image_fname=$image_fname |
| local ro_list="$4" rw_list_a="$5" rw_list_b="$6" |
| local rw_list="$rw_list_a $rw_list_b" |
| echo " * Updating $2..." |
| flashrom_select_target $code |
| echo " - reading current flashrom image" |
| flashrom_read_whole |
| |
| # Check if target image size is same as current flashrom chip. |
| # TODO(hungte) In the future we may allow expanding image to real flashrom |
| # chips, but let's assume they must be the same now. |
| [ $(echo_file_size $current_fname) -eq $(echo_file_size $image_fname) ] || |
| die "Target image size=$(echo_file_size $image_fname) is" \ |
| "different from real flashrom=$(echo_file_size $current_fname)" |
| |
| # process layout |
| flashrom_detect_layout $code $current_fname |
| local current_layout_signature="$(build_layout_signature "$ro_list $rw_list")" |
| debug_msg "current layout signature: $current_layout_signature" |
| flashrom_detect_layout $code $image_fname |
| local target_layout_signature="$(build_layout_signature "$ro_list $rw_list")" |
| debug_msg "target layout signature: $target_layout_signature" |
| flashrom_set_skip_verify_list "$7" |
| |
| # build a preserved image if required |
| if [ -n "$8" ]; then |
| image_fname=$(flashrom_preserve_sections "$8" $current_fname $image_fname) \ |
| || die "cannot create temporary file for preserving sections" |
| fi |
| |
| # if the layout is different, we only allow factory setup |
| assert_str $is_check_layout |
| if is_positive $is_check_layout && |
| [ "$current_layout_signature" != "$target_layout_signature" ]; then |
| if ! is_positive $is_factory; then |
| die "Image memory layout is incompatible to current system." \ |
| "You may need to perform a factory install by --factory." |
| fi |
| fi |
| |
| # verify if this seems like a valid layout |
| assert_str $is_check_vboot |
| if is_positive $is_check_vboot && [ -n "$9" ] && |
| ! check_chromeos_vboot_section "$9" $image_fname; then |
| die "Invalid memory layout or corrupted ChromeOS firmware image." |
| fi |
| |
| # check rootkey to see if they are compatible |
| local compare_rootkey_result=0 |
| assert_str $is_check_rootkey |
| if is_positive $is_check_rootkey && [ -n "${10}" ]; then |
| chromeos_check_same_root_keys $current_fname $image_fname || |
| compare_rootkey_result=$? |
| fi |
| |
| # if the rootkey seems different, we only allow factory setup |
| debug_msg "result: $compare_rootkey_result" |
| if [ $compare_rootkey_result -ne 0 ] && ! is_positive $is_factory; then |
| if [ $compare_rootkey_result -eq 1 ]; then |
| die "Incompatible firmware image (Root key is different). " \ |
| "You may need to perform a factory install by --factory." |
| else |
| die "Cannot find root key in current system. " \ |
| "You may need to perform a factory install by --factory." |
| fi |
| fi |
| |
| # It is possible to check write protection status via $ACPI_ROOT/BINF.3 |
| # (Boot GPIO States) or use factory mode tags to decide if we can update |
| # RO. However a different RO (with write protected) usually means we're |
| # running on a different (wrong) configuration. So the logic here is, if |
| # RO is different, rewrite entire image or fail. |
| |
| # An update should be always called after chromeos_check_background but before |
| # chromeos_post_update. |
| # Even if is_factory=true, is_background still may to be true because when the |
| # updater is trying to upgrade an incompatible firmware, it may force entering |
| # factory mode. |
| |
| if is_positive $is_recovery && [ "$code" = "bios" ]; then |
| # To allow recovering RW_SHARED section which is not in any FMAP sections of |
| # legacy CR-48 BIOS, we need to rewrite whole RW area. |
| |
| # use CHROMEOS_BIOS_WP_LAYOUT_DESC to construct layout for whole RW. |
| flashrom_reset_layout |
| flashrom_detect_layout_by_default_map "bios_wp" |
| ro_list="RO" |
| rw_list_a="RW" |
| rw_list_b="" |
| rw_list="$rw_list_a" |
| ro_list="" |
| is_rw_only=1 |
| allow_2stage_update=0 |
| fi |
| |
| if ! is_positive $is_rw_only; then |
| echo " - checking RO data: $ro_list" |
| local verify_list="*" |
| if ! flashrom_verify_sections \ |
| "$ro_list" "$ro_list" $current_fname $image_fname ; then |
| chromeos_check_background_update "$code" || return |
| # for factory, rewrite everything. otherwise (with-ro-code), |
| # update only the sections we want. |
| if is_positive $is_factory; then |
| echo " - start factory (first-time) setup" |
| echo " * action: rewrite whole image" |
| flashrom_write_whole $image_fname |
| else |
| echo " - start RW (AB together) + RO code (bootstub/recovery) update" |
| local update_list="$ro_list" |
| flashrom_verify_sections \ |
| "$rw_list" "$rw_list" $current_fname $image_fname || |
| update_list="$update_list $rw_list" |
| echo " * action: update $update_list" |
| start_firmware_section_update \ |
| "$update_list" "$update_list" $image_fname |
| verify_list="$update_list" |
| fi |
| |
| if is_positive $is_always_verify ; then |
| echo " - verify written image" |
| flashrom_read_whole |
| flashrom_verify_sections \ |
| "$verify_list" "$verify_list" $current_fname $image_fname || |
| die " ! image verification failed..." |
| fi |
| chromeos_post_update "$code" |
| echo " - success and complete." |
| return |
| fi |
| fi |
| |
| # factory mode, with RO OK; try to update all RW at the same time. |
| if is_positive $is_factory; then |
| echo " - start factory (RW-only) update" |
| if flashrom_verify_sections \ |
| "$rw_list" "$rw_list" $current_fname $image_fname ; then |
| echo " - no need to update." |
| else |
| chromeos_check_background_update "$code" || return |
| echo " * action: update $rw_list" |
| start_firmware_section_update "$rw_list" "$rw_list" $image_fname |
| chromeos_post_update "$code" |
| echo " - success and complete." |
| fi |
| return |
| fi |
| |
| # auto-partial-update here. |
| echo " - start auto-partial-update" |
| echo " - checking RW data" |
| if flashrom_verify_sections "$rw_list" "$rw_list" $current_fname $image_fname |
| then |
| # T==A==B, everything is updated |
| echo " - already updated to latest version." |
| return |
| fi |
| |
| # some part is different, determine what task to do here. |
| chromeos_check_background_update "$code" || return |
| |
| if is_positive $allow_2stage_update && [ -n "$rw_list_b" ]; then |
| # A/B |
| debug_msg "invoke firmware_AB_update" |
| chromeos_firmware_AB_update "$rw_list_a" "$rw_list_b" $image_fname || |
| die "AB firmware update failed" |
| else |
| # simple update |
| debug_msg "invoke firmware_section_update" |
| echo " * action: update $rw_list" |
| start_firmware_section_update "$rw_list" "$rw_list" $image_fname || |
| die "RW firmware update failed" |
| fi |
| chromeos_post_update "$code" |
| |
| echo " - success and complete." |
| } |
| |
| end_update_firmware() { |
| # Put all procedures / checks here to perform after an update trial (no matter |
| # if any data is really changed. See also chromeos_post_update). |
| check_param "end_update_firmware(code)" "$@" |
| |
| # enable write-protection if required |
| assert_str $is_enable_write_protect |
| if is_positive $is_enable_write_protect; then |
| echo " - enable write protection..." |
| flashrom_reset_layout |
| flashrom_detect_layout_by_default_map "$1_wp" || die "invalid map info." |
| flashrom_enable_write_protect "RO" || die "write protection failed." |
| fi |
| |
| # reset flashrom target |
| flashrom_select_target "reset" || alert "Cannot reset flashrom target." |
| } |
| |
| updater_shutdown() { |
| # Put all procedures / checks here to perform after all updates are complete. |
| # Ex, check if we need to reboot directly for the keyboard frozen issue |
| local target |
| assert_str is_factory |
| assert_str is_background |
| assert_str is_allow_reboot |
| debug_msg "updater_shutdown: modified targets are: $CURRENT_MODIFIED_TARGETS" |
| |
| for target in $CURRENT_MODIFIED_TARGETS; do |
| debug_msg "updater_shutdown: checking target $target" |
| |
| # check if we need to reboot. |
| # note: the leading space before $CHROMEOS_NEED_DIRECT_REBOOT_UPDATE_TARGETS |
| # is required for in_list to work properly on empty list. |
| if is_positive $is_allow_reboot && |
| in_list "$target" " $CHROMEOS_NEED_DIRECT_REBOOT_UPDATE_TARGETS"; then |
| echo " ** this update needs to reboot system immediately. **" |
| debug_msg "!!!!! going to reboot NOW !!!!!" |
| sync; sync; sync |
| execute_command "$CHROMEOS_REBOOT_CMD" || die "cannot reboot" |
| fi |
| done |
| } |
| |
| clear_update_cookies() { |
| # Silently clear all possible update request cookies |
| crossystem fwb_tries=0 >/dev/null 2>&1 || true |
| crossystem fwupdate_tries=0 >/dev/null 2>&1 || true |
| rm -f "$CHROMEOS_NEED_REBOOT_TAG" >/dev/null 2>&1 || true |
| rm -f "$NEED_FIRMWARE_TRYB" >/dev/null 2>&1 || true |
| } |
| |
| issue_1563_workaround() { |
| # TODO(hungte) Refactory these stuff to work with latest tools (crossystem). |
| # crossystem and nvram are fixed in latest OS; however when updating from an |
| # old CR48, the updater will be executed inside an old environment; so we need |
| # to keep both reboot_mode and nvram re-creation here. |
| # Workaround for chrome-os-partner:1563. If we're supposed to reboot into |
| # firmware B but haven't, it's likely because the CMOS has lost its state, so |
| # let's try once more right now. |
| NEED_FIRMWARE_UPDATE='/mnt/stateful_partition/.need_firmware_update' |
| NEED_FIRMWARE_TRYB='/mnt/stateful_partition/.need_firmware_tryb' |
| BINF1='/sys/devices/platform/chromeos_acpi/BINF.1' |
| |
| # /dev/nvram is required by reboot_mode. chromeos_startup may re-mount devfs |
| # and caused nvram to disappear for a while. Since we cannot wait for |
| # delayed-loading, mknod provides a quick solution. |
| [ -e /dev/nvram ] || mknod /dev/nvram c 10 144 |
| |
| if [ -f "$NEED_FIRMWARE_TRYB" ]; then |
| # Yes, flag file is present. Make sure we don't do this more than once |
| rm -f "$NEED_FIRMWARE_TRYB" |
| if [ -r "$BINF1" ]; then |
| if [ "$(cat "$BINF1")" = "1" ]; then |
| # Yes, in firmware A, so we need to try firmware B |
| crossystem fwb_tries=1 || reboot_mode --try_firmware_b=1 |
| # When we do, we want to run the shellball again. |
| crossystem fwupdate_tries=1 || touch "$NEED_FIRMWARE_UPDATE" |
| reboot |
| # should never get here |
| sleep 10 |
| die "ERROR: $0 should have rebooted" |
| fi |
| fi |
| fi |
| } |
| |
| # ---------------------------------------------------------------------------- |
| # Main Entry |
| |
| if is_positive "$is_allow_au"; then |
| issue_1563_workaround |
| fi |
| |
| # if a mode is assigned by parameter, we won't do any further mode detection. |
| # if multiple modes were listed as parameter, the last one takes effect. |
| is_mode_assigned=0 |
| |
| # parse command line |
| for arg in "$@"; do |
| |
| # pre-process argument |
| arg_name="${arg%%=*}" |
| arg_value="" |
| arg_check_value=1 |
| if [ "$arg_name" != "$arg" ]; then |
| arg_value="=${arg#*=}" |
| fi |
| arg_name="$(echo "$arg_name" | sed 's/_/-/g' | sed 's/^--no-/--no/')" |
| if [ "${arg_name%%check*}" = "--no" ]; then |
| arg_check_value=0 |
| fi |
| arg="${arg_name}${arg_value}" |
| |
| case $arg in |
| -h | --help ) |
| # usage_help |
| echo "$0 [options] |
| |
| mode selection (only the last one assigned mode will take effect): |
| --mode=MODE Mode string: startup, bootok, autoupdate, todev, |
| recovery, factory_install, factory_final |
| --factory Force using factory mode (write RO+RW+GBB/VPD together) |
| --with-ro-code Update RW(A/B together) & RO(BSTUB/FVDEV), keep GBB/VPD |
| --rw-ab-together Update R/W firmware, and update A/B parts together |
| --rw-only (default) Update R/W firmware in 2 stage, skip whole RO |
| |
| mode aliases: |
| --factory-mode Factory Install Mode, alias for --factory |
| --recovery-mode Recovery Install Mode, alias for --with-ab-together |
| --os-install-mode OS Install Mode, alias for --with-ab-together |
| --background-mode Background Update Mode, alias for --background-update |
| |
| special options: |
| --background-update Skip update that may freeze keyboard/reboot (eg, EC) |
| --[no]allow-reboot Allow updater to reboot device if required |
| --write-protect Enable write protection after update complete |
| --targets='list' Targets to be updated (def: '$CHROMEOS_UPDATE_TARGETS') |
| |
| checking and verification: |
| --always-verify Force verifying each write to flashrom |
| --[no]check-layout Check if the flash memory layout is compatible |
| --[no]check-rootkey Check if the rootkey is compatible for updating |
| --[no]check-vboot Check if the carried image has proper VBOOT signature |
| |
| general options: |
| --verbose Print verbose messages |
| --debug Provide debug information |
| --debug-help Detail help message on debug commands |
| --help This help message" |
| exit 0 |
| ;; |
| --debug-help ) |
| echo "Extra parameters by for debugging: |
| |
| --debug-trace Print executed shell commands line-by-line" |
| exit 0 |
| ;; |
| --mode=* ) |
| arg_value="${arg_value#=}" |
| case $arg_value in |
| startup ) |
| is_allow_reboot=1 |
| allow_2stage_update=1 |
| is_factory=0 |
| is_rw_only=1 |
| is_mode_assigned=1 |
| if is_positive "$is_allow_au"; then |
| verbose_msg " * Auto Update in Reboot (A/B 2 stage)" |
| else |
| # silent exit |
| debug_msg "mode [$arg_value] is disabled on this platform" |
| clear_update_cookies |
| exit 0 |
| fi |
| ;; |
| autoupdate ) |
| is_background=1 |
| allow_2stage_update=1 |
| is_factory=0 |
| is_rw_only=1 |
| is_mode_assigned=1 |
| if is_positive "$is_allow_au"; then |
| verbose_msg " * Auto Update in Background (try A/B 2 stage)" |
| else |
| # silent exit |
| debug_msg "mode [$arg_value] is disabled on this platform" |
| clear_update_cookies |
| exit 0 |
| fi |
| ;; |
| recovery ) |
| allow_2stage_update=0 |
| is_factory=0 |
| is_rw_only=1 |
| is_mode_assigned=1 |
| is_recovery=1 |
| verbose_msg " * (Recovery) Update R/W firmware (Whole RW at once)" |
| ;; |
| factory_install | factory ) |
| allow_2stage_update=0 |
| is_factory=1 |
| is_rw_only=0 |
| is_mode_assigned=1 |
| verbose_msg " * Enable factory mode" |
| ;; |
| incompatible_update | factory_final | todev | tonormal | bootok ) |
| alert "Warning: mode [$arg_value] is not available on this platform." |
| exit 0 |
| ;; |
| * ) |
| alert "Warning: mode [$arg_value] is not supported." |
| exit 0 |
| ;; |
| esac |
| ;; |
| --factory* ) |
| allow_2stage_update=0 |
| is_factory=1 |
| is_rw_only=0 |
| is_mode_assigned=1 |
| verbose_msg " * Enable factory mode" |
| ;; |
| --with-ro* ) |
| allow_2stage_update=0 |
| is_factory=0 |
| is_rw_only=0 |
| is_mode_assigned=1 |
| verbose_msg " * Enable RW+RO Code (bootstub/recovery) mode" |
| ;; |
| --rw-ab* | --recovery* | --os-install* ) |
| allow_2stage_update=0 |
| is_factory=0 |
| is_rw_only=1 |
| is_mode_assigned=1 |
| verbose_msg " * Update R/W firmware (A/B at once)" |
| ;; |
| --rw-only ) |
| allow_2stage_update=1 |
| is_factory=0 |
| is_rw_only=1 |
| is_mode_assigned=1 |
| verbose_msg " * Update only R/W firmware (try A/B 2 stage)" |
| ;; |
| --background* ) |
| is_background=1 |
| verbose_msg " * Enable background update mode" |
| ;; |
| --allow-reboot* ) |
| is_allow_reboot=1 |
| verbose_msg " * Allow rebooting system after updates applied" |
| ;; |
| --noallow-reboot* ) |
| is_allow_reboot=0 |
| verbose_msg " * Disallow rebooting system after updates applied" |
| ;; |
| --write-protect* | --enable-write-protect* ) |
| is_enable_write_protect=1 |
| verbose_msg " * Enable write protection" |
| ;; |
| --target*=* ) |
| CHROMEOS_UPDATE_TARGETS="${arg#--target*=}" |
| verbose_msg " * Target(s) changed to: $CHROMEOS_UPDATE_TARGETS" |
| ;; |
| --always-verify ) |
| is_always_verify=1 |
| verbose_msg " * Force verification to every write" |
| ;; |
| --check-layout | --nocheck-layout ) |
| is_check_layout=$arg_check_value |
| ;; |
| --check-rootkey | --nocheck-rootkey ) |
| is_check_rootkey=$arg_check_value |
| ;; |
| --check-vboot | --nocheck-vboot ) |
| is_check_vboot=$arg_check_value |
| ;; |
| --verbose ) |
| is_verbose=1 |
| FLAGS_verbose=$FLAGS_TRUE |
| ;; |
| --debug ) |
| is_debug=1 |
| FLAGS_debug=$FLAGS_TRUE |
| ;; |
| --debug-trace ) |
| is_debug=1 |
| FLAGS_debug=$FLAGS_TRUE |
| set -x |
| ;; |
| --force ) |
| verbose_msg " * Ignoring --force." |
| ;; |
| * ) |
| echo " ! ERROR: unknown parameter: $arg" |
| echo " ! Please invoke with --help for full syntax." |
| exit 1 |
| esac |
| shift |
| done |
| |
| # detect and adjust modes |
| assert_str $is_mode_assigned |
| |
| # load customized information |
| if [ -r $CUSTOM_SCRIPT_FILENAME ]; then |
| echo " * Loading custom script '$CUSTOM_SCRIPT_FILENAME'..." |
| . $CUSTOM_SCRIPT_FILENAME |
| fi |
| |
| # report and adjust working mode |
| if is_positive $is_factory; then |
| echo " % Firmware Update Mode: Factory (RO+RW+Everything)" |
| is_rw_only=0 |
| # is_background cannot be set to 0 here, because we may still need to check it |
| # in factory mode when the 'factory mode' was enforced by an incompatible |
| # version of firmware. |
| elif is_positive $is_rw_only; then |
| is_positive $allow_2stage_update && |
| echo " % Firmware Update Mode: RW Only (2 stage update for A/B)" || |
| echo " % Firmware Update Mode: RW Only (A/B together)" |
| else |
| echo " % Firmware Update Mode: RO(code) + RW(A/B together)" |
| fi |
| |
| if is_positive $is_debug; then |
| is_verbose=1 |
| FLAGS_verbose=$FLAGS_TRUE |
| debug_msg "(mode) is_factory: $is_factory" |
| debug_msg "(mode) is_rw_only: $is_rw_only" |
| debug_msg "(mode) allow_2stage_update: $allow_2stage_update" |
| debug_msg "(opt) is_background: $is_background" |
| debug_msg "(opt) is_allow_reboot: $is_allow_reboot" |
| debug_msg "(opt) is_always_verify: $is_always_verify" |
| debug_msg "(opt) is_enable_write_protect: $is_enable_write_protect" |
| debug_msg "(opt) allow_fmap_decode_layout: $allow_fmap_decode_layout" |
| debug_msg "(opt) update targets: $CHROMEOS_UPDATE_TARGETS" |
| debug_msg "(chk) is_check_layout: $is_check_layout" |
| debug_msg "(chk) is_check_rootkey : $is_check_rootkey" |
| debug_msg "(chk) is_check_vboot: $is_check_vboot" |
| debug_msg "(msg) is_verbose: $is_verbose" |
| debug_msg "(msg) is_debug: $is_debug" |
| fi |
| |
| for target in $CHROMEOS_UPDATE_TARGETS; do |
| case $target in |
| bios ) |
| debug_msg "checking target=bios" |
| # update BIOS firmware |
| if [ -f $BIOS_IMAGE_FILENAME ]; then |
| bios_ro_list="$CHROMEOS_BIOS_FACTORY_RO_LIST" |
| is_positive $is_factory || bios_ro_list="$CHROMEOS_BIOS_RO_LIST" |
| update_firmware "bios" "BIOS" \ |
| $BIOS_IMAGE_FILENAME \ |
| "$bios_ro_list" \ |
| "$CHROMEOS_BIOS_RW_A_LIST" "$CHROMEOS_BIOS_RW_B_LIST" \ |
| "$CHROMEOS_BIOS_SKIP_VERIFY_LIST" \ |
| "$CHROMEOS_BIOS_PRESERVE_LIST" \ |
| "$CHROMEOS_VBOOT_LIST" \ |
| "$CHROMEOS_ROOTKEY_SECTION_NAME" |
| end_update_firmware "bios" |
| fi |
| ;; |
| ec ) |
| debug_msg "checking target=ec" |
| # update BIOS firmware |
| # update EC firmware |
| if [ -f $EC_IMAGE_FILENAME ]; then |
| update_firmware "ec" "Embedded Controller (EC)" \ |
| $EC_IMAGE_FILENAME \ |
| "$CHROMEOS_EC_RO_LIST" \ |
| "$CHROMEOS_EC_RW_LIST" "" \ |
| "$CHROMEOS_EC_SKIP_VERIFY_LIST" \ |
| "$CHROMEOS_EC_PRESERVE_LIST" \ |
| "" \ |
| "" |
| end_update_firmware "ec" |
| fi |
| ;; |
| * ) |
| die "Unknown target: $target" |
| ;; |
| esac |
| done |
| |
| updater_shutdown |
| echo " *** Firmware update complete. ***" |