util/amdtools: Add script to update the SPI speed in the EFS header

The update_efs_spi_speed allows changing the SPI speed manually in a
binary that has already been built.  This will allow binaries not built
for the EM100 SPI ROM emulator to be updated so that they will work.
There is a corresponding change that will check to see if the EFS value
has been modified from the original speed and will prevent coreboot from
updating the SPI speed and mode.

BUG=b:177233017
TEST=Update SPI speed in existing binary.  See that SPI speed has
changed.

Signed-off-by: Martin Roth <martin@coreboot.org>
Change-Id: I905a9fd8bd93a28aab927dffecbbcf24934b9e03
Reviewed-on: https://review.coreboot.org/c/coreboot/+/56644
Reviewed-by: Raul Rangel <rrangel@chromium.org>
Reviewed-by: Karthik Ramasubramanian <kramasub@google.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
GitOrigin-RevId: a95cd265bdaf1529094040e91cf8f1e0036f5a7f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/3151304
Tested-by: Copybara Service <copybara-worker-blackhole@google.com>
Reviewed-by: Andrew McRae <amcrae@google.com>
Reviewed-by: Dossym Nurmukhanov <dossym@chromium.org>
Commit-Queue: Andrew McRae <amcrae@google.com>
diff --git a/util/amdtools/update_efs_spi_speed b/util/amdtools/update_efs_spi_speed
new file mode 100755
index 0000000..00b82bd
--- /dev/null
+++ b/util/amdtools/update_efs_spi_speed
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: BSD-3-Clause
+
+ROM="$1"
+declare -i NEW_SPEED="$2"
+
+readonly EFS_SIG_DWORD="55aa55aa"
+readonly FAST_SPEED_NEW_F15_MOD_60_6F_OFFSET=0x41
+readonly SPI_FASTSPEED_F17_MOD_00_2F_OFFSET=0x44
+readonly SPI_FASTSPEED_F17_MOD_30_3F_OFFSET=0x48
+
+# print out the very simple usage
+usage() {
+  echo "Usage: $0 <ROM> <Speed>"
+  echo "  Speed must be between 0 & 5"
+  echo "    0: 66.66Mhz"
+  echo "    1: 33.33MHz"
+  echo "    2: 22.22MHz"
+  echo "    3: 16.66MHz"
+  echo "    4: 100MHz"
+  echo "    5: 800KHz"
+}
+
+# Validate the input parameters
+if [[ $# -ne 2 || ! -f "${ROM}" || "${NEW_SPEED}" -lt 0 || "${NEW_SPEED}" -gt 5 ]]; then
+  usage
+  exit 1
+fi
+
+# Read a 32, 16, or 8 bit value from a location in a binary file
+getval() {
+  local location=$1
+  local length=$2
+
+  if [[ ${length} -eq 1 ]]; then
+    dd "if=${ROM}" bs=1 "skip=${location}" "count=${length}" 2>/dev/null | hexdump -v -e '1/1 "%02x\n"'
+  elif [[ ${length} -eq 2 ]]; then
+    dd "if=${ROM}" bs=1 "skip=${location}" "count=${length}" 2>/dev/null | hexdump -v -e '1/2 "%04x\n"'
+  elif [[ ${length} -eq 4 ]]; then
+    dd "if=${ROM}" bs=1 "skip=${location}" "count=${length}" 2>/dev/null | hexdump -v -e '1/4 "%08x\n"'
+  else
+    echo "Error: invalid value"
+    exit 1
+  fi
+}
+
+# Update a location in a binary
+# Note that the passed in value must be formatted correctly:
+# Each byte needs to be specified as "\\xXX" where X is a hex digit
+setval() {
+  local location=$1
+  local length=$2
+  local hexval=$3
+
+  # shellcheck disable=SC2059
+  if ! printf "$hexval" | dd "of=${ROM}" bs=1 "seek=${location}" "count=${length}" conv=notrunc status=none; then
+    echo "Error: Could not write to ${ROM}"
+    exit 1
+  fi
+}
+
+# Print the speed associated with the passed-in value
+showspeed() {
+  local speedval=$1
+  case ${speedval} in
+  0 | 00) echo "0: 66.66Mhz" ;;
+  1 | 01) echo "1: 33.33MHz" ;;
+  2 | 02) echo "2: 22.22MHz" ;;
+  3 | 03) echo "3: 16.66MHz" ;;
+  4 | 04) echo "4: 100MHz" ;;
+  5 | 05) echo "5: 800KHz" ;;
+  ff) echo "Error: Speed not set" ;;
+  *) echo "Error: Unknown speed (${speedval})" ;;
+  esac
+}
+
+# Locate the SPI speed data and update it to the new speed
+update_efs() {
+  local location=$1
+  local updated_speed=0
+
+  for speed_offset in FAST_SPEED_NEW_F15_MOD_60_6F_OFFSET SPI_FASTSPEED_F17_MOD_00_2F_OFFSET SPI_FASTSPEED_F17_MOD_30_3F_OFFSET; do
+    local speed_val
+    local speed_loc=$((location + speed_offset))
+    speed_val=$(getval "${speed_loc}" "1")
+
+    if [[ "${speed_val}" != "ff" ]]; then
+      printf "Found speed value of %s at %#06x\n" "$(showspeed "${speed_val}")" "${speed_loc}"
+      updated_speed=1
+      setval "${speed_loc}" "1" "\\x0${NEW_SPEED}"
+      speed_val=$(getval "${speed_loc}" "1")
+      printf "New speed value: %s\n" "$(showspeed "${speed_val}")"
+    fi
+
+  done
+  if [[ ${updated_speed} -eq 0 ]]; then
+    echo "Error: Could not find speed value to update."
+    exit 1
+  fi
+}
+
+# Find the EFS location and update the speed
+main() {
+  local location
+  local val
+
+  for i in {0..5}; do
+    location="$((0xffffff - (0x80000 << i) + 0x20000 + 1))"
+    val="$(getval "${location}" 4 )"
+    if [[ "${val}" == "${EFS_SIG_DWORD}" ]]; then
+      printf "EFS found at %#06x\n" "${location}"
+      update_efs "${location}"
+      exit 0
+    fi
+  done
+
+  echo "Error: EFS not found in ${ROM}."
+  exit 1
+}
+
+main