#!/bin/sh
#
# Copyright (c) 2011 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.

# THIS FILE DEPENDS ON common.sh

# ----------------------------------------------------------------------------
# ChromeOS Firmware Unpacking Utilities

# Folders for preparing and unpacking firmware
# Layout: DIR_[TYPE]/[TYPE]/[SECTION], DIR_[TYPE]/[IMAGE]
DIR_CURRENT="_current"
DIR_TARGET="_target"

# Parameters for flashrom command
TARGET_OPT_MAIN="-p host"
TARGET_OPT_EC="-p ec"
TARGET_OPT_PD="-p ec:dev=1"
WRITE_OPT="--fast-verify"

# Image and key files.
IMAGE_MAIN="bios.bin"
IMAGE_MAIN_RW="bios_rw.bin"  # optional image.
IMAGE_EC="ec.bin"
IMAGE_PD="pd.bin"
KEYSET_DIR="keyset"

# Overrides this with any function name to perform special tasks when EC is
# updated (Ex, notify EC to check battery firmware updates)
CUSTOMIZATION_EC_POST_UPDATE=""
CUSTOMIZATION_PD_POST_UPDATE=""

# Unpacks target image, in full image and unpacked form.
crosfw_unpack_image() {
  check_param "crosfw_unpack_image(type, image, target_opt)" "$@"
  local type_name="$1" image_name="$2" target_opt="$3"
  debug_msg "preparing $type_name firmware images..."
  mkdir -p "$DIR_TARGET/$type_name"
  cp -f "$image_name" "$DIR_TARGET/$image_name"
  ( cd "$DIR_TARGET/$type_name";
    dump_fmap -x "../$image_name" >/dev/null 2>&1) ||
    die "Invalid firmware image (missing FMAP) in $image_name."
}

# Unpacks image from current system EEPROM, in full and/or unpacked form.
crosfw_unpack_current_image() {
  check_param "crosfw_unpack_current_image(type,image,target_opt,...)" "$@"
  local type_name="$1" image_name="$2" target_opt="$3"
  shift; shift; shift
  debug_msg "trying to read $type_name firmware from system EEPROM..."
  mkdir -p "$DIR_CURRENT/$type_name"
  local list="" i=""
  for i in $@ ; do
    list="$list -i $i"
  done
  invoke "flashrom $target_opt $list -r $DIR_CURRENT/$image_name"
  # current may not have FMAP... (ex, from factory setup)
  ( cd "$DIR_CURRENT/$type_name";
    dump_fmap -x "../$image_name" >/dev/null 2>&1) || true
}

# Copies VPD data by merging sections from current system into target firmware
# image file.
crosfw_dupe_vpd() {
  check_param "crosfw_dupe_vpd(vpd_list, output, opt_input)" "$@"
  # Preserve VPD when updating an existing system (maya be legacy firmware or
  # ChromeOS firmware). The system may not have FMAP, so we need to use
  # emulation mode otherwise reading sections may fail.
  local vpd_list="$1"
  local output="$2"
  local input="$3"
  debug_msg "crosfw_dupe_vpd: Duplicating VPD... ($vpd_list)"

  # TODO(hungte) Speed up by reading only partitions specified in vpd_list
  # unless active firmware is nonchrome.
  if [ -z "$input" ]; then
    input="_vpd_temp.bin"
    debug_msg "Reading active firmware..."
    silent_invoke "flashrom $TARGET_OPT_MAIN -r $input" ||
      die "Failed to read current main firmware."
  fi
  local size_input="$(cros_get_file_size "$input")"
  local size_output="$(cros_get_file_size "$output")"

  if [ -z "$size_input" ] || [ "$size_input" = "0" ]; then
    die "Invalid current main firmware. Abort."
  fi
  if [ "$size_input" != "$size_output" ]; then
    die "Incompatible firmware image size ($size_input != $size_output)."
  fi

  # Build command for VPD list
  local vpd_list_cmd="" vpd_name=""
  local is_trusted_vpd=1
  local param="dummy:emulate=VARIABLE_SIZE,image=$output,size=$size_input"

  if dump_fmap "$input" >/dev/null 2>&1; then
    # Trust the FMAP in input
    debug_msg "Using VPD location from input FMAP"
    is_trusted_vpd=
    local tmpdir="_dupe_vpd.tmp"
    local input_path="$(readlink -f "$input")"
    (mkdir "$tmpdir"; cd "$tmpdir"; dump_fmap -x "$input_path") >/dev/null 2>&1
    for vpd_name in $vpd_list; do
      vpd_list_cmd="$vpd_list_cmd -i $vpd_name:$tmpdir/$vpd_name"
    done
  else
    debug_msg "Using VPD location from target FMAP"
    for vpd_name in $vpd_list; do
      vpd_list_cmd="$vpd_list_cmd -i $vpd_name"
    done
  fi

  debug_msg "Preserving VPD: -p $param $vpd_list_cmd"
  if ! silent_invoke "flashrom -p $param $vpd_list_cmd -w $input"; then
    if [ -n "$is_trusted_vpd" ]; then
      die "Failed to dupe VPD. Please check target firmware image."
    else
      alert "Warning: corrupted VPD in current firmware - reset."
    fi
  fi

  # Check if VPD is valid. Reset if required.
  debug_msg "Checking VPD partitions..."
  for vpd_name in $vpd_list; do
    if ! silent_invoke "vpd -i $vpd_name -f $output"; then
      alert "Warning: corrupted VPD ($vpd_name) - Reset."
      silent_invoke "vpd -O -i $vpd_name -f $output"
    fi
  done

  debug_msg "crosfw_dupe_vpd: $output updated."
}

# Utility to return if main firmware write protection is enabled.
is_mainfw_write_protected() {
  if [ -n "$FLAGS_wp" ]; then
    verbose_msg "Warning: MAIN WP state overridden as ${FLAGS_wp}"
    "${FLAGS_wp}"
  else
    cros_is_hardware_write_protected &&
      cros_is_software_write_protected "$TARGET_OPT_MAIN"
  fi
}

# Utility to return if EC firmware write protection is enabled.
is_ecfw_write_protected() {
  if [ -n "$FLAGS_wp" ]; then
    verbose_msg "Warning: EC WP state overridden as ${FLAGS_wp}"
    "${FLAGS_wp}"
  else
    cros_is_hardware_write_protected &&
      cros_is_software_write_protected "$TARGET_OPT_EC"
  fi
}

# Utility to return if PD firmware write protection is enabled.
is_pdfw_write_protected() {
  if [ -n "$FLAGS_wp" ]; then
    verbose_msg "Warning: PD WP state overridden as ${FLAGS_wp}"
    "${FLAGS_wp}"
  else
    cros_is_hardware_write_protected &&
      cros_is_software_write_protected "$TARGET_OPT_PD"
  fi
}

crosfw_dup2_mainfw() {
  # Syntax: crosfw_dup2_mainfw SLOT_FROM SLOT_TO
  #    Duplicates a slot to another place on system live firmware
  local slot_from="$1" slot_to="$2"
  # Writing in flashrom, even if there's nothing to write, is still much slower
  # than reading because it needs to read + verify whole image. So we should
  # only write firmware on if there's something changed.
  local temp_from="_dup2_temp_from"
  local temp_to="_dup2_temp_to"
  local temp_image="_dup2_temp_image"
  debug_msg "invoking: crosfw_dup2_mainfw($@)"
  local opt_read_slots="-i $slot_from:$temp_from -i $slot_to:$temp_to"
  invoke "flashrom $TARGET_OPT_MAIN $opt_read_slots -r $temp_image"
  if [ -s "$temp_from" ] && cros_compare_file "$temp_from" "$temp_to"; then
    debug_msg "crosfw_dup2_mainfw: slot content is the same, no need to copy."
  else
    slot="$slot_to:$temp_from"
    invoke "flashrom $TARGET_OPT_MAIN $WRITE_OPT -w $temp_image -i $slot"
  fi
}

# Compares two slots from current and target folder.
crosfw_is_equal_slot() {
  check_param "crosfw_is_equal_slot(type, slot, ...)" "$@"
  local type_name="$1" slot_name="$2" slot2_name="$3"
  [ "$#" -lt 4 ] || die "crosfw_is_equal_slot: internal error"
  [ -n "$slot2_name" ] || slot2_name="$slot_name"
  local current="$DIR_CURRENT/$type_name/$slot_name"
  local target="$DIR_TARGET/$type_name/$slot2_name"
  cros_compare_file "$current" "$target"
}

# Gets the hash from the current slot
crosfw_slot_hash() {
  check_param "crosfw_slot_hash(type, slot)" "$@"
  local type_name="$1" slot_name="$2"
  [ "$#" -lt 3 ] || die "crosfw_slot_hash: internal error"
  local current="$DIR_CURRENT/$type_name/$slot_name"
  cros_get_file_hash "$current"
}

# ----------------------------------------------------------------------------
# General Updater

# Update Main Firmware (BIOS, AP)
crosfw_update_main() {
  # Syntax: crosfw_update_main SLOT FIRMWARE_SOURCE_TYPE
  #    Write assigned type (normal/developer) of firmware source into
  #    assigned slot (returns directly if the target is already filled
  #    with correct data)
  # Syntax: crosfw_update_main SLOT
  #    Write assigned slot from MAIN_TARGET_IMAGE.
  # Syntax: crosfw_update_main
  #    Write complete MAIN_TARGET_IMAGE

  local slot="$1"
  local source_type="$2"
  debug_msg "invoking: crosfw_update_main($@)"
  # TODO(hungte) verify if slot is valid.
  [ -s "$IMAGE_MAIN" ] || die "missing firmware image: $IMAGE_MAIN"
  if [ "$slot" = "" ]; then
    invoke "flashrom $TARGET_OPT_MAIN $WRITE_OPT -w $IMAGE_MAIN"
  elif [ "$source_type" = "" ]; then
    invoke "flashrom $TARGET_OPT_MAIN $WRITE_OPT -w $IMAGE_MAIN -i $slot"
  else
    local section_file="$DIR_TARGET/$TYPE_MAIN/$source_type"
    [ -s "$section_file" ] || die "crosfw_update_main: missing $section_file"
    slot="$slot:$section_file"
    invoke "flashrom $TARGET_OPT_MAIN $WRITE_OPT -w $IMAGE_MAIN -i $slot"
  fi
}

# Update Embedded Controller Firmware
crosfw_update_ec() {
  local slot="$1"
  debug_msg "invoking: crosfw_update_ec($@)"
  # Syntax: crosfw_update_ec SLOT
  #    Update assigned slot with proper firmware.
  # Syntax: crosfw_update_ec
  #    Write complete MAIN_TARGET_IMAGE
  # TODO(hungte) verify if slot is valid.
  [ -s "$IMAGE_EC" ] || die "missing firmware image: $IMAGE_EC"
  [ -z "$CUSTOMIZATION_EC_PRE_UPDATE" ] || "$CUSTOMIZATION_EC_PRE_UPDATE"
  if [ -n "$slot" ]; then
    invoke "flashrom $TARGET_OPT_EC $WRITE_OPT -w $IMAGE_EC -i $slot"
  else
    invoke "flashrom $TARGET_OPT_EC $WRITE_OPT -w $IMAGE_EC"
  fi
  [ -z "$CUSTOMIZATION_EC_POST_UPDATE" ] || "$CUSTOMIZATION_EC_POST_UPDATE"
}

# Update Embedded Controller PD Firmware
crosfw_update_pd() {
  local slot="$1"
  debug_msg "invoking: crosfw_update_pd($@)"
  # Syntax: crosfw_update_pd SLOT
  #    Update assigned slot with proper firmware.
  # Syntax: crosfw_update_pd
  #    Write complete MAIN_TARGET_IMAGE
  [ -s "$IMAGE_PD" ] || die "missing firmware image: $IMAGE_PD"
  [ -z "$CUSTOMIZATION_PD_PRE_UPDATE" ] || "$CUSTOMIZATION_PD_PRE_UPDATE"
  if [ -n "$slot" ]; then
    invoke "flashrom $TARGET_OPT_PD $WRITE_OPT -w $IMAGE_PD -i $slot"
  else
    invoke "flashrom $TARGET_OPT_PD $WRITE_OPT -w $IMAGE_PD"
  fi
  [ -z "$CUSTOMIZATION_PD_POST_UPDATE" ] || "$CUSTOMIZATION_PD_POST_UPDATE"
}

# Note following "preserve" utilities will change $IMAGE_MAIN so any processing
# to the file (ex, prepare_main_image) must be invoked AFTER this call.
crosfw_preserve_hwid() {
  [ -s "$IMAGE_MAIN" ] || die "preserve_hwid: no main firmware."
  silent_invoke "gbb_utility -s --hwid='$HWID' $IMAGE_MAIN"
}

crosfw_preserve_vpd() {
  crosfw_dupe_vpd "RO_VPD RW_VPD" "$IMAGE_MAIN" ""
}

crosfw_preserve_bmpfv() {
  if [ -z "$HWID" ]; then
    debug_msg "crosfw_preserve_bmpfv: Running non-ChromeOS firmware. Skip."
    return
  fi
  debug_msg "Preseving main firmware bitmap volume data..."
  [ -s "$IMAGE_MAIN" ] || die "crosfw_preserve_bmpfv: no main firmware."
  # Preserves V1, V2 bitmap volumes.
  silent_invoke "flashrom $TARGET_OPT_MAIN -i GBB:_gbb.bin -r _temp.rom"
  silent_invoke "gbb_utility -g --bmpfv=_bmpfv.bin _gbb.bin"
  [ -s "_bmpfv.bin" ] || die "crosfw_preserve_bmpfv: invalid bmpfv"
  silent_invoke "gbb_utility -s --bmpfv=_bmpfv.bin $IMAGE_MAIN"
}

crosfw_preserve_gbb() {
  if [ -z "$HWID" ]; then
    debug_msg "crosfw_preserve_gbb: Running non-ChromeOS firmware. Skip."
    return
  fi
  debug_msg "Preseving main firmware GBB data..."
  [ -s "$IMAGE_MAIN" ] || die "crosfw_preserve_gbb: no main firmware."
  silent_invoke "flashrom $TARGET_OPT_MAIN -i GBB:_gbb.bin -r _temp.rom"

  # Preseves flags (--flags output format: "flags: 0x0000001")
  local flags="$(gbb_utility -g --flags _gbb.bin 2>/dev/null |
                 sed -nr 's/^flags: ([x0-9]+)/\1/p')"
  debug_msg "Current firmware flags: $flags"
  if [ -n "$flags" ]; then
    silent_invoke "gbb_utility -s --flags=$((flags)) $IMAGE_MAIN"
  fi
}

crosfw_preserve_nvram() {
  if [ -z "$HWID" ]; then
    debug_msg "crosfw_preserve_nvram: Running non-ChromeOS firmware. Skip."
    return
  fi
  [ -s "$IMAGE_MAIN" ] || die "crosfw_preserve_nvram: no main firmware."
  local offset=$(dump_fmap -p "$IMAGE_MAIN" RW_NVRAM | cut -d' ' -f2)
  local size=$(dump_fmap -p "$IMAGE_MAIN" RW_NVRAM | cut -d' ' -f3)
  if [ -z "$size" ]; then
    debug_msg "crosfw_preserve_nvram: System doesn't have flash NVRAM. Skip."
    return
  fi

  debug_msg "Preseving main firmware NVRAM data..."
  silent_invoke "flashrom $TARGET_OPT_MAIN -r -i RW_NVRAM:_nvram.rom"
  if [ "$size" != $(cros_get_file_size _nvram.rom) ]; then
    verbose_msg "RW_NVRAM size changed, cannot preserve old state!"
    return
  fi
  silent_invoke "dd seek=$offset count=$size bs=1 conv=notrunc \
  		 if=_nvram.rom of=$IMAGE_MAIN"
}
