#!/bin/bash
# Copyright 2019 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.

. /usr/share/misc/shflags

readonly DEFAULT_RETRIES=${DEFAULT_RETRIES:-4}
readonly STM32MON_CONNECT_RETRIES=${STM32MON_CONNECT_RETRIES:-6}
readonly STM32MON_SERIAL_BAUDRATE=${STM32MON_SERIAL_BAUDRATE:-115200}

DEFINE_boolean 'read' "${FLAGS_FALSE}" 'Read instead of write' 'r'
# Both flash read and write protection are removed by default, but you
# can optionally enable them (for testing purposes).
DEFINE_boolean 'remove_flash_read_protect' "${FLAGS_TRUE}" \
  'Remove flash read protection while performing command' 'U'
DEFINE_boolean 'remove_flash_write_protect' "${FLAGS_TRUE}" \
  'Remove flash write protection while performing command' 'u'
DEFINE_integer 'retries' "${DEFAULT_RETRIES}" 'Specify number of retries' 'R'
DEFINE_integer 'baudrate' "${STM32MON_SERIAL_BAUDRATE}" 'Specify UART baudrate' 'B'
DEFINE_boolean 'hello' "${FLAGS_FALSE}" 'Only ping the bootloader' 'H'
DEFINE_boolean 'services' "${FLAGS_TRUE}" \
  'Stop and restart conflicting fingerprint services' 's'
FLAGS_HELP="Usage: ${0} [flags] [ec.bin]"

#        EXIT_SUCCESS=0
#        EXIT_FAILURE=1
#        EXIT_BASHBUILTIN=2
readonly EXIT_ARGUMENT=3
readonly EXIT_CONFIG=4
readonly EXIT_PRECONDITION=5
readonly EXIT_RUNTIME=6

# Process commandline flags
FLAGS "${@}" || exit "${EXIT_ARGUMENT}"
eval set -- "${FLAGS_ARGV}"

readonly CROS_EC_SPI_MODALIAS_STR="of:NcrfpTCgoogle,cros-ec-spi"

readonly CROS_EC_UART_MODALIAS_STR="of:NcrfpTCgoogle,cros-ec-uart"

klog() {
  echo "flash_fp_mcu: $*" > /dev/kmsg
}

check_hardware_write_protect_disabled() {
  local hardware_write_protect_state
  if ! hardware_write_protect_state="$(crossystem wpsw_cur)"; then
    echo "Failed to get hardware write protect status" >&2
    exit "${EXIT_PRECONDITION}"
  fi
  if [[ "${hardware_write_protect_state}" != "0" ]]; then
    echo "Please make sure hardware write protect is disabled."
    echo "See https://www.chromium.org/chromium-os/firmware-porting-guide/firmware-ec-write-protection"
    exit "${EXIT_PRECONDITION}"
  fi
}

# Get the gpiochip base number from sysfs that matches
# a device path
get_sysfs_gpiochip_base() {
  local match="${1}"

  for chip in /sys/class/gpio/gpiochip*
  do
    case "$(readlink ${chip})" in
     ${match})
      echo "${chip#/sys/class/gpio/gpiochip}"
      return 0
      ;;
    esac
  done

  return 1
}

# Get the spiid for the fingerprint sensor based on the modalias
# string: https://crbug.com/955117
get_spiid() {
  # TODO(b/179533783): Fix modalias on strongbad and remove this bypass.
  if [[ -n "${DEVICEID}" ]]; then
    echo "${DEVICEID}"
    return 0
  fi

  for dev in /sys/bus/spi/devices/*; do
    if [[ "$(cat "${dev}/modalias")" == "${CROS_EC_SPI_MODALIAS_STR}" ]]; then
      basename "${dev}"
      return 0
    fi
  done

  return 1
}

# Get the uartid for the fingerprint sensor based on the modalias
get_uartid() {
  for dev in /sys/bus/serial/devices/*; do
    if [ -f "${dev}/modalias" ]; then
      if [[ "$(cat "${dev}/modalias")" == "${CROS_EC_UART_MODALIAS_STR}" ]]; then
        basename "${dev}"
        return 0
      fi
    fi
  done

  return 1
}

# Usage: gpio <unexport|export|in|out|0|1|get> <signal> [signal...]
gpio() {
  local cmd="$1"
  shift

  for signal in "$@"; do
    case "${cmd}" in
      unexport|export)
        klog "Set gpio ${signal} to ${cmd}"
        echo "${signal}" > "/sys/class/gpio/${cmd}"
        ;;
      in|out)
        local direction="${cmd}"
        klog "Set gpio ${signal} direction to ${direction}"
        echo "${direction}" > "/sys/class/gpio/gpio${signal}/direction"
        ;;
      0|1)
        local value="${cmd}"
        klog "Set gpio ${signal} to ${value}"
        echo "${value}" > "/sys/class/gpio/gpio${signal}/value"
        ;;
      get)
        local value="${cmd}"
        klog "Get gpio ${signal}"
        cat "/sys/class/gpio/gpio${signal}/value"
        ;;
      *)
        echo "Invalid gpio command: ${cmd}" >&2
        exit "${EXIT_RUNTIME}"
        ;;
    esac
  done
}

# Usage: warn_gpio <signal> <expected_value> <msg>
warn_gpio() {
  local signal=$1
  local expected_value=$2
  local msg=$3

  local value
  if ! value="$(gpio get "${signal}")"; then
    echo "Error fetching gpio value ${signal}" >&2
    exit "${EXIT_RUNTIME}"
  fi

  if [[ "${value}" != "${expected_value}" ]]; then
    echo "${msg}" >&2
    return 1
  fi
}

# Taken verbatim from
# https://chromium.googlesource.com/chromiumos/docs/+/HEAD/lsb-release.md#shell
# This should not be used by anything except get_platform_name.
# See https://crbug.com/98462.
lsbval() {
  local key="$1"
  local lsbfile="${2:-/etc/lsb-release}"

  if ! echo "${key}" | grep -Eq '^[a-zA-Z0-9_]+$'; then
    return 1
  fi

  sed -E -n -e \
    "/^[[:space:]]*${key}[[:space:]]*=/{
      s:^[^=]+=[[:space:]]*::
      s:[[:space:]]+$::
      p
    }" "${lsbfile}"
}

# Get the underlying board (reference design) that we're running on (not the
# FPMCU or sensor).
# This may be an extended platform name, like nami-kernelnext, hatch-arc-r,
# or hatch-borealis.
get_platform_name() {
  local platform_name

  # We used to use "cros_config /identity platform-name", but that is specific
  # to mosys and does not actually provide the board name in all cases.
  # cros_config intentionally does not provide a way to get the board
  # name: b/156650654.

  # If there was a way to get the board name from cros_config, it's possible
  # that it could fail in the following cases:
  #
  # 1) We're running on a non-unibuild device (the only one with FP is nocturne)
  # 2) We're running on a proto device during bringup and the cros_config
  #    settings haven't yet been setup.
  #
  # In all cases we can fall back to /etc/lsb-release. It's not recommended
  # to do this, but we don't have any other options in this case.
  echo "Getting platform name from /etc/lsb-release." 1>&2
  platform_name="$(lsbval "CHROMEOS_RELEASE_BOARD")"
  if [[ -z "${platform_name}" ]]; then
    return 1
  fi

  echo "${platform_name}"
}

# Given a full platform name, extract the base platform.
#
# Tests are also run on modified images, like hatch-arc-r, hatch-borealis, or
# hatch-kernelnext. These devices still have fingerprint and are expected to
# pass tests. The full platform name reflects these modifications and might
# be needed to apply an alternative configuration (kernelnext). Other modified
# tests (arc-r) just need to default to the base platform config, which is
# identified by this function.
# See b/186697064.
#
# Examples:
# * platform_base_name "hatch-kernelnext" --> "hatch"
# * platform_base_name "hatch-arc-r"      --> "hatch"
# * platform_base_name "hatch-borealis"   --> "hatch"
# * platform_base_name "hatch"            --> "hatch"
#
# Usage: platform_base_name <platform_name>
platform_base_name() {
  local platform_name="$1"

  # We remove any suffix starting at the first '-'.
  echo "${platform_name%%-*}"
}

get_default_fw() {
  local board
  board="$(cros_config /fingerprint board)"

  # If cros_config returns "", that is okay assuming there is only
  # one firmware file on disk.

  local -a fws
  mapfile -t fws < <(find /opt/google/biod/fw -name "${board}*.bin")

  if [[ "${#fws[@]}" -ne 1 ]]; then
    return 1
  fi

  echo "${fws[0]}"
}

# Find processes that have the named file, active or deleted, open.
#
# Deleted files are important because unbinding/rebinding cros-ec
# with biod/timberslide running will result in the processes holding open
# a deleted version of the files. Issues can arise if the process continue
# to interact with the deleted files (ex kernel panic) while the raw driver
# is being used in flash_fp_mcu. The lsof and fuser tools can't seem to
# identify usages of the deleted named file directly, without listing all
# files. This takes a large amount out time on Chromebooks, thus we need this
# custom search routine.
#
# Usage: proc_open_file [file_pattern]
proc_open_file() {
  local file_pattern="$1"

  # Avoid overloading kernel max arguments with the number of file names.
  local -a FDS=( /proc/*/fd/* )
  xargs ls -l <<<"${FDS[*]}" 2>/dev/null | grep "${file_pattern}" | \
    awk '{split($9,p,"/"); print "PID", p[3], "->", $11, $12}'
  return "${PIPESTATUS[1]}"
}

flash_fp_mcu_stm32() {
  local transport="${1}"
  local device="${2}"
  local gpio_nrst="${3}"
  local gpio_boot0="${4}"
  local gpio_pwren="${5}"
  local file="${6}"
  local deviceid

  local stm32mon_flags="-p --retries ${STM32MON_CONNECT_RETRIES}"

  if [[ "${transport}" == "UART" ]]; then
    stm32mon_flags+=" --baudrate ${FLAGS_baudrate} --device ${device}"
  else
    stm32mon_flags+=" -s ${device}"
  fi

  if [[ "${FLAGS_hello}" -eq "${FLAGS_FALSE}" ]]; then
    if [[ "${FLAGS_remove_flash_write_protect}" -eq "${FLAGS_TRUE}" ]]; then
      stm32mon_flags+=" -u"
    fi

    if [[ "${FLAGS_remove_flash_read_protect}" -eq "${FLAGS_TRUE}" ]]; then
      stm32mon_flags+=" -U"
    fi

    if [[ "${FLAGS_read}" -eq "${FLAGS_TRUE}" ]]; then
      # Read from FPMCU to file
      if [[ -e "${file}" ]]; then
        echo "Output file already exists: ${file}"
        return "${EXIT_PRECONDITION}"
      fi
      echo "# Reading to '${file}' over ${transport}"
      stm32mon_flags+=" -r ${file}"
    else
      # Write to FPMCU from file
      if [[ ! -f "${file}" ]]; then
        echo "Invalid image file: ${file}"
        return "${EXIT_PRECONDITION}"
      fi
      echo "# Flashing '${file}' over ${transport}"
      stm32mon_flags+=" -e -w ${file}"
    fi
  else
    echo "# Saying hello over ${transport}"
  fi


  check_hardware_write_protect_disabled

  if [[ "${transport}" == "UART" ]]; then
    if ! deviceid="$(get_uartid)"; then
      echo "Unable to find FP sensor UART device: ${CROS_EC_UART_MODALIAS_STR}"
      return "${EXIT_PRECONDITION}"
    fi
  else

    if ! deviceid="$(get_spiid)"; then
      echo "Unable to find FP sensor SPI device: ${CROS_EC_SPI_MODALIAS_STR}"
      return "${EXIT_PRECONDITION}"
    fi
  fi

  echo "Flashing ${transport} device ID: ${deviceid}"

  # Remove cros_fp if present
  klog "Unbinding cros-ec driver"
  if [[ "${transport}" == "UART" ]]; then
    echo "${deviceid}" > /sys/bus/serial/drivers/cros-ec-uart/unbind
  else
    echo "${deviceid}" > /sys/bus/spi/drivers/cros-ec-spi/unbind
  fi

  # Configure the MCU Boot0 and NRST GPIOs
  gpio export "${gpio_boot0}" "${gpio_nrst}"
  gpio out    "${gpio_boot0}" "${gpio_nrst}"

  # Reset sequence to enter bootloader mode
  gpio 1 "${gpio_boot0}"
  gpio 0 "${gpio_nrst}"
  sleep 0.001

  klog "Binding raw driver"
  if [[ "${transport}" == "UART" ]]; then
    # load AMDI0020:01 ttyS1
    echo AMDI0020:01 > /sys/bus/platform/drivers/dw-apb-uart/unbind;
    echo AMDI0020:01 > /sys/bus/platform/drivers/dw-apb-uart/bind;
  else
    echo spidev > "/sys/bus/spi/devices/${deviceid}/driver_override"
    echo "${deviceid}" > /sys/bus/spi/drivers/spidev/bind
    # The following sleep is a workaround to mitigate the effects of a
    # poorly behaved chip select line. See b/145023809.
  fi
  sleep 0.5

  # We do not expect the drivers to change the pin state when binding.
  # If you receive this warning, the driver needs to be fixed on this board
  # and this flash attempt will probably fail.
  warn_gpio "${gpio_boot0}" 1 \
    "WARNING: One of the drivers changed BOOT0 pin state on bind attempt."
  warn_gpio "${gpio_nrst}" 0 \
    "WARNING: One of the drivers changed NRST pin state on bind attempt."

  if [[ ! -c "${device}" ]]; then
    echo "Failed to bind raw device driver." >&2
    return "${EXIT_RUNTIME}"
  fi

  local attempt=0
  local cmd_exit_status=1
  local cmd="stm32mon ${stm32mon_flags}"

  for attempt in $(seq "${FLAGS_retries}"); do
    # Reset sequence to enter bootloader mode
    gpio 0 "${gpio_nrst}"
    sleep 0.01

    # Release reset as the SPI bus is now ready
    gpio 1 "${gpio_nrst}"

    # As per section '68: Bootloader timings' from application note below:
    # https://www.st.com/resource/en/application_note/cd00167594-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdf
    # bootloader startup time is 16.63 ms for STM32F74xxx/75xxx and 53.975 ms
    # for STM32H74xxx/75xxx. SPI needs 1 us delay for one SPI byte sending.
    # Keeping some margin, add delay of 100 ms to consider minimum bootloader
    # startup time after the reset for stm32 devices.
    sleep 0.1

    # Print out the actual underlying command we're running and run it
    echo "# ${cmd}"
    ${cmd}
    cmd_exit_status=$?

    if [[ "${cmd_exit_status}" -eq 0 ]]; then
      break
    fi
    echo "# Attempt ${attempt} failed."
    echo
    sleep 1
  done

  # unload device
  if [[ "${transport}" != "UART" ]]; then
    klog "Unbinding raw driver"
    echo "${deviceid}" > /sys/bus/spi/drivers/spidev/unbind
  fi

  # Go back to normal mode
  gpio out "${gpio_nrst}"
  gpio 0   "${gpio_boot0}" "${gpio_nrst}"
  gpio 1   "${gpio_nrst}"

  # Give up GPIO control, unless we need to keep these driving as
  # outputs because they're not open-drain signals.
  # TODO(b/179839337): Make this the default and properly support
  # open-drain outputs on other platforms.
  if [[ "${PLATFORM_BASE_NAME}" != "strongbad" ]] &&
     [[ "${PLATFORM_BASE_NAME}" != "herobrine" ]]; then
    gpio in       "${gpio_boot0}" "${gpio_nrst}"
  fi
  gpio unexport "${gpio_boot0}" "${gpio_nrst}"

  # Dartmonkey's RO has a flashprotect logic issue that forces reboot loops
  # when SW-WP is enabled and HW-WP is disabled. It is avoided if a POR is
  # detected on boot. We force a POR here to ensure we avoid this reboot loop.
  # See to b/146428434.
  if [[ "${gpio_pwren}" -gt 0 ]]; then
    echo "Power cycling the FPMCU."
    gpio export "${gpio_pwren}"
    gpio out    "${gpio_pwren}"
    gpio 0      "${gpio_pwren}"
    # Must outlast hardware soft start, which is typically ~3ms.
    sleep 0.5
    gpio 1      "${gpio_pwren}"
    # Power enable line is externally pulled down, so leave as output-high.
    gpio unexport "${gpio_pwren}"
  fi

  # Put back cros_fp driver if transport is SPI
  if [[ "${transport}" != "UART" ]]; then
    # wait for FP MCU to come back up (including RWSIG delay)
    sleep 2
    klog "Binding cros-ec driver"
    echo "" > "/sys/bus/spi/devices/${deviceid}/driver_override"
    echo "${deviceid}" > /sys/bus/spi/drivers/cros-ec-spi/bind
  fi

  if [[ "${cmd_exit_status}" -ne 0 ]]; then
    return "${EXIT_RUNTIME}"
  fi

  # Inform user to reboot if transport is UART.
  # Display fw version is transport is SPI
  if [[ "${transport}" == "UART" ]]; then
    echo "Please reboot this device."
  else
    # Test it
    klog "Query version and reset flags"
    ectool --name=cros_fp version
    ectool --name=cros_fp uptimeinfo
  fi
}

config_hatch() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev1.1"
  # See
  # third_party/coreboot/src/soc/intel/cannonlake/include/soc/gpio_soc_defs.h
  # for pin name to number mapping.
  # Examine `cat /sys/kernel/debug/pinctrl/INT34BB:00/gpio-ranges` on a hatch
  # device to determine gpio number from pin number.
  readonly GPIO_CHIP="gpiochip200"
  # FPMCU RST_ODL is on GPP_A12 = 200 + 12 = 212
  readonly GPIO_NRST=212
  # FPMCU BOOT0 is on GPP_A22 = 200 + 22 = 222
  readonly GPIO_BOOT0=222
  # FP_PWR_EN is on GPP_C11 = 456 + (192 - 181) = 456 + 11 = 467
  readonly GPIO_PWREN=467
}

config_herobrine() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev11.0"
  # TODO(b/179533783): Fix this script to look for non-ACPI modalias
  readonly DEVICEID="spi11.0"

  readonly GPIO_CHIP="gpiochip336"
  # FPMCU RST_ODL is $(gpiofind FP_RST_L) is gpiochip0 78
  readonly GPIO_NRST=$((336 + $(gpiofind FP_RST_L|cut -f2 -d" ")))
  # FPMCU BOOT0 is $(gpiofind FPMCU_BOOT0) is gpiochip0 77
  readonly GPIO_BOOT0=$((336 + $(gpiofind FPMCU_BOOT0|cut -f2 -d" ")))
  # EN FP RAILS is $(gpiofind EN_FP_RAILS) is gpiochip0 42
  readonly GPIO_PWREN=$((336 + $(gpiofind EN_FP_RAILS|cut -f2 -d" ")))
}

config_nami() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev1.0"

  readonly GPIO_CHIP="gpiochip360"
  # FPMCU RST_ODL is on GPP_C9 = 360 + 57 = 417
  readonly GPIO_NRST=417
  # FPMCU BOOT0 is on GPP_D5 = 360 + 77 = 437
  readonly GPIO_BOOT0=437
  # FP_PWR_EN is on GPP_B11 = 360 + 35 = 395
  readonly GPIO_PWREN=395
}

config_nami-kernelnext() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev1.0"

  readonly GPIO_CHIP="gpiochip360"
  # FPMCU RST_ODL is on GPP_C9 = 360 + 57 = 417
  readonly GPIO_NRST=417
  # FPMCU BOOT0 is on GPP_D5 = 360 + 77 = 437
  readonly GPIO_BOOT0=437
  # FP_PWR_EN is on GPP_B11 = 360 + 35 = 395
  readonly GPIO_PWREN=395
}

config_nocturne() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev32765.0"

  readonly GPIO_CHIP="gpiochip360"
  # FPMCU RST_ODL is on GPP_C10 = 360 + 58 = 418
  readonly GPIO_NRST=418
  # FPMCU BOOT0 is on GPP_C8 = 360 + 56 = 416
  readonly GPIO_BOOT0=416
  # FP_PWR_EN is on GPP_A11 = 360 + 11 = 371
  readonly GPIO_PWREN=371
}

config_nocturne-kernelnext() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev1.0"

  readonly GPIO_CHIP="gpiochip360"
  # FPMCU RST_ODL is on GPP_C10 = 360 + 58 = 418
  readonly GPIO_NRST=418
  # FPMCU BOOT0 is on GPP_C8 = 360 + 56 = 416
  readonly GPIO_BOOT0=416
  # FP_PWR_EN is on GPP_A11 = 360 + 11 = 371
  readonly GPIO_PWREN=371
}

config_strongbad() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev10.0"
  # TODO(b/179533783): Fix modalias on strongbad and remove this bypass.
  readonly DEVICEID="spi10.0"

  local dev_path="*/platform/soc@0/3500000.pinctrl/gpio/*"
  local gpiobase
  if ! gpiobase=$(get_sysfs_gpiochip_base "${dev_path}"); then
    echo "Unable to find gpio chip base"
    return "${EXIT_PRECONDITION}"
  fi

  readonly GPIO_CHIP="gpiochip${gpiobase}"
  # FPMCU RST_ODL is $(gpiofind FP_RST_L) is gpiochip0 22
  readonly GPIO_NRST=$((${gpiobase} + $(gpiofind FP_RST_L|cut -f2 -d" ")))
  # FPMCU BOOT0 is $(gpiofind FPMCU_BOOT0) is gpiochip0 10
  readonly GPIO_BOOT0=$((${gpiobase} + $(gpiofind FPMCU_BOOT0|cut -f2 -d" ")))
  # TODO(b/179839337): Hardware currently doesn't support PWREN, but the
  # next revision will. Add a comment here about the power enable gpio.
  readonly GPIO_PWREN=-1
}

config_volteer() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev1.0"

  # See kernel/v5.4/drivers/pinctrl/intel/pinctrl-tigerlake.c
  # for pin name and pin number.
  # Examine `cat /sys/kernel/debug/pinctrl/INT34C5:00/gpio-ranges` on a
  # volteer device to determine gpio number from pin number.
  # For example: GPP_C23 is UART2_CTS which can be queried from EDS
  # the pin number is 194. From the gpio-ranges, the gpio value is
  # 408 + (194-171) = 431

  readonly GPIO_CHIP="gpiochip152"
  # FPMCU RST_ODL is on GPP_C23 = 408 + (194 - 171) = 431
  readonly GPIO_NRST=431
  # FPMCU BOOT0 is on GPP_C22 = 408 + (193 - 171) = 430
  readonly GPIO_BOOT0=430
  # FP_PWR_EN is on GPP_A21 = 216 + (63 - 42) = 237
  readonly GPIO_PWREN=237
}

config_brya() {
  readonly TRANSPORT="SPI"
  readonly DEVICE="/dev/spidev0.0"

  # See kernel/v5.10/drivers/pinctrl/intel/pinctrl-tigerlake.c
  # for pin name and pin number.
  # Examine `cat /sys/kernel/debug/pinctrl/INTC1055:00/gpio-ranges` on a
  # brya device to determine gpio number from pin number.
  # For example: GPP_D1 is ISH_GP_1 which can be queried from EDS
  # the pin number is 100 from the pinctrl-tigerlake.c.
  # From the gpio-ranges, the gpio value is 312 + (100-99) = 313

  readonly GPIO_CHIP="gpiochip152"
  # FPMCU RST_ODL is on GPP_D1 = 312 + (100 - 99) = 313
  readonly GPIO_NRST=313
  # FPMCU BOOT0 is on GPP_D0 = 312 + (99 - 99) = 312
  readonly GPIO_BOOT0=312
  # FP_PWR_EN is on GPP_D2 = 312 + (101 - 99) = 314
  readonly GPIO_PWREN=314
}

config_brask() {
  # Let's call the config_brya since brask follows the brya HW design
  config_brya
}

config_zork() {
  readonly TRANSPORT="UART"
  readonly DEVICE="/dev/ttyS1"

  readonly GPIO_CHIP="gpiochip256"
  # FPMCU RST_ODL is on AGPIO 11 = 256 + 11 = 267
  readonly GPIO_NRST=267
  # FPMCU BOOT0 is on AGPIO 69 = 256 + 69 = 325
  readonly GPIO_BOOT0=325
  # FPMCU PWR_EN is on AGPIO 32 = 256 + 32 = 288, but should not be
  # necessary for flashing. Set invalid value.
  readonly GPIO_PWREN=-1
}

config_guybrush() {
  readonly TRANSPORT="UART"

  readonly DEVICE="/dev/ttyS1"

  readonly GPIO_CHIP="gpiochip256"
  # FPMCU RST_ODL is on AGPIO 11 = 256 + 11 = 267
  readonly GPIO_NRST=267
  # FPMCU BOOT0 is on AGPIO 144 = 256 + 144 = 400
  readonly GPIO_BOOT0=400
  # FPMCU PWR_EN is on AGPIO 32 = 256 + 32 = 288, but should not be
  # necessary for flashing. Set invalid value.
  readonly GPIO_PWREN=-1
}

main() {
  local filename="$1"

  # print out canonical path to differentiate between /usr/local/bin and
  # /usr/bin installs
  readlink -f "$0"

  # The "platform name" corresponds to the underlying board (reference design)
  # that we're running on (not the FPMCU or sensor). At the moment all of the
  # reference designs use the same GPIOs. If for some reason a design differs in
  # the future, we will want to add a nested check in the config_<platform_name>
  # function. Doing it in this manner allows us to reduce the number of
  # configurations that we have to maintain (and reduces the amount of testing
  # if we're only updating a specific config_<platform_name>).
  if ! PLATFORM_NAME="$(get_platform_name)"; then
    echo "Failed to get platform name"
    exit "${EXIT_CONFIG}"
  fi
  readonly PLATFORM_NAME

  if ! PLATFORM_BASE_NAME="$(platform_base_name "${PLATFORM_NAME}")"; then
    echo "Failed to get platform base name"
    exit "${EXIT_CONFIG}"
  fi
  readonly PLATFORM_BASE_NAME

  echo "Platform name is ${PLATFORM_NAME} (${PLATFORM_BASE_NAME})."

  # Check that the config function exists
  if [[ "$(type -t "config_${PLATFORM_NAME}")" == "function" ]]; then
    readonly PLATFORM_CONFIG="${PLATFORM_NAME}"
  elif [[ "$(type -t "config_${PLATFORM_BASE_NAME}")" == "function" ]]; then
    readonly PLATFORM_CONFIG="${PLATFORM_BASE_NAME}"
  else
    echo "No config for platform ${PLATFORM_NAME}." >&2
    exit "${EXIT_CONFIG}"
  fi

  echo "Using config for ${PLATFORM_CONFIG}."

  if ! "config_${PLATFORM_CONFIG}"; then
    echo "Configuration failed for platform ${PLATFORM_CONFIG}." >&2
    exit "${EXIT_CONFIG}"
  fi

  # Help the user out with defaults, if no *file* was given.
  if [[ "$#" -eq 0 ]]; then
    # If we are actually reading, set to a timestamped temp file.
    if [[ "${FLAGS_read}" -eq "${FLAGS_TRUE}" ]]; then
      filename="/tmp/fpmcu-fw-$(date --iso-8601=seconds).bin"
    else
      # Assume we are "writing" the default firmware to the FPMCU.
      if ! filename="$(get_default_fw)"; then
        echo "Failed to identify a default firmware file" >&2
        exit "${EXIT_CONFIG}"
      fi
    fi
  fi

  # Check that the gpiochip exists
  if [[ ! -e "/sys/class/gpio/${GPIO_CHIP}" ]]; then
    echo "Cannot find GPIO chip: ${GPIO_CHIP}"
    exit "${EXIT_CONFIG}"
  fi

  if [[ "${FLAGS_services}" -eq "${FLAGS_TRUE}" ]]; then
    echo "# Stopping biod and timberslide"
    stop biod
    stop timberslide LOG_PATH=/sys/kernel/debug/cros_fp/console_log
  fi

  # If cros-ec driver isn't bound on startup, this means the final rebinding
  # may fail.
  if [[ ! -c "/dev/cros_fp" ]]; then
    echo "WARNING: The cros-ec driver was not bound on startup." >&2
  fi
  if [[ -c "${DEVICE}" ]]; then
    echo "WARNING: The raw driver was bound on startup." >&2
  fi
  local files_open
  # Ensure no processes have cros_fp device or debug device open.
  # This might be biod and/or timberslide.
  if files_open=$(proc_open_file "/dev/cros_fp\|/sys/kernel/debug/cros_fp/*")
  then
    echo "WARNING: Another process has a cros_fp device file open." >&2
    echo "${files_open}" >&2
    echo "Try 'stop biod' and" >&2
    echo "'stop timberslide LOG_PATH=/sys/kernel/debug/cros_fp/console_log'" >&2
    echo "before running this script." >&2
    echo "See b/188985272." >&2
  fi
  # Ensure no processes are using the raw driver. This might be a wedged
  # stm32mon process spawned by this script.
  if files_open=$(proc_open_file "${DEVICE}"); then
    echo "WARNING: Another process has ${DEVICE} open." >&2
    echo "${files_open}" >&2
    echo "Try 'fuser -k ${DEVICE}' before running this script." >&2
    echo "See b/188985272." >&2
  fi

  local ret
  flash_fp_mcu_stm32 \
    "${TRANSPORT}"   \
    "${DEVICE}"      \
    "${GPIO_NRST}"   \
    "${GPIO_BOOT0}"  \
    "${GPIO_PWREN}"  \
    "${filename}"
  ret=$?

  if [[ "${FLAGS_services}" -eq "${FLAGS_TRUE}" ]]; then
    echo "# Restarting biod and timberslide"
    start timberslide LOG_PATH=/sys/kernel/debug/cros_fp/console_log
    start biod
  fi

  exit "${ret}"
}

main "$@"
