# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Flash Master values are specific to the Skylake SPI descriptor
FLMSTR_UNLOCKED="00ffffff00ffffff00ffffff"
FLMSTR_LOCKED="000ba000000dc00000088000"
FLMSTR_XXD_OPT="-p -s128 -l12" # bytes 128-140
FLMSTR_CURRENT=""
FLMSTR_TARGET=""

# Read FLMSTR values from current and target images
read_flmstr() {
  # Read the current SI_DESC region from flash
  prepare_main_current_image "SI_DESC"

  FLMSTR_CURRENT=$(xxd $FLMSTR_XXD_OPT "$DIR_CURRENT/$IMAGE_MAIN")
  FLMSTR_TARGET=$(xxd $FLMSTR_XXD_OPT "$IMAGE_MAIN")
}

# Write FLMSTR locked or unlocked setting to image
write_flmstr() {
  local state="$1" # $FLMSTR_LOCKED or $FLMSTR_UNLOCKED
  local image="$2" # path to image to write FLMSTR into
  echo "$state" | xxd -r $FLMSTR_XXD_OPT - "$image"
}

# Check if  ME state is set to specific value in firmware log
check_me_state() {
  local state="$1" # ME state to look for in firmware log
  local value="$2" # Expected value for the ME state
  local fwlog="/sys/firmware/log"
  [ -e "$fwlog" ] || return $FLAGS_FALSE

  # Read current ME state value from firmware log
  local current_value="$(sed -ne "s/^ME: $state \+: \(.*\)/\1/p" $fwlog)"

  if [ "$current_value" != "$value" ]; then
    verbose_msg "Unexpected ME $state: $current_value != $value"
    return $FLAGS_FALSE
  fi
  return $FALSE_TRUE
}

# Check ME state in /sys/firmware/log
check_me_health() {
  check_me_state "Current Working State" "Normal" || return $FLAGS_FALSE
  check_me_state "Current Operation Mode" "Normal" || return $FLAGS_FALSE
  check_me_state "Current Operation State" "M0 with UMA" || return $FLAGS_FALSE
  check_me_state "Error Code" "No Error" || return $FLAGS_FALSE
  return $FLAGS_TRUE
}

# Lock the SPI descriptor in bootok mode
lock_spi_descriptor() {
  # Don't lock if update needed
  if [ "$FWID" != "$TARGET_FWID" ]; then
    return
  fi

  # Read the current FLMSTR values
  read_flmstr

  # Check that the Management Engine state is OK
  if ! check_me_health; then
    # Unable to repair if SPI descriptor is locked
    if [ "$FLMSTR_CURRENT" != "$FLMSTR_UNLOCKED" ]; then
      verbose_msg "Unable to repair ME, SPI descriptor is locked"
      return
    fi

    # Write ME region from target image
    invoke "flashrom $TARGET_OPT_MAIN $WRITE_OPT -w $IMAGE_MAIN -i SI_ME"

    verbose_msg "ME repair attempted, will be verified after reboot"
    return
  fi

  # Check if current descriptor is already locked
  if [ "$FLMSTR_CURRENT" = "$FLMSTR_LOCKED" ]; then
    verbose_msg "SPI Descriptor already locked"
    return
  fi

  # Check if target descriptor is locked
  if [ "$FLMSTR_TARGET" != "$FLMSTR_LOCKED" ]; then
    verbose_msg "SPI Descriptor not locked in target image"
    return
  fi

  # Check if current descriptor is properly unlocked
  if [ "$FLMSTR_CURRENT" != "$FLMSTR_UNLOCKED" ]; then
    verbose_msg "SPI Descriptor has unknown FLMSTR $FLMSTR_CURRENT"
    return
  fi

  # Lock SPI Descriptor
  local image="$DIR_CURRENT/$IMAGE_MAIN"
  write_flmstr "$FLMSTR_LOCKED" "$image"

  # Write new SPI Descriptor
  invoke "flashrom $TARGET_OPT_MAIN $WRITE_OPT -w $image -i SI_DESC"

  verbose_msg "SPI Descriptor has been locked"
}

# Preserve unlocked SPI descriptor in autoupdate mode
preserve_spi_descriptor() {
  # Read the current FLMSTR values
  read_flmstr

  # Check if current image is unlocked and target is locked
  if [ "$FLMSTR_CURRENT" = "$FLMSTR_UNLOCKED" ] &&
     [ "$FLMSTR_TARGET" = "$FLMSTR_LOCKED" ]; then
    # Preserve the unlocked state in the target image and
    # let it get locked after reboot by bootok step.
    write_flmstr "$FLMSTR_UNLOCKED" "$IMAGE_MAIN"
    verbose_msg "SPI descriptor will be locked after reboot"
  fi
}

updater_custom_main() {
  case "$FLAGS_mode" in
    autoupdate | recovery )
      # Preserve unlocked SPI descriptor in autoupdate mode so
      # it can safely be done in bootok mode after reboot.
      preserve_spi_descriptor
      ;;

    bootok )
      # Lock SPI descriptor in bootok mode if firmware has been
      # updated and the ME is in a good state.
      lock_spi_descriptor
      ;;
  esac
}
