# A set of common helper functions useful for iMX5 and iMX6 updaters

### Immediate execution

UPDDIR="$(dirname "${UPDFILE}")"
THISCODENAME="$(grep CODENAME /etc/updater/updater.conf | cut -d= -f2)"
FAMILY_NAME=$(cut -f2 /proc/navico_platform/family_id || echo unknown)
FAMILYID=$FAMILY_NAME
SUBFAMILYID=$(cut -f1 /proc/navico_platform/sub_family_id || echo unknown)
PROC_SUB_FAMILY=$(cut -f3 /proc/navico_platform/cpuinfo || echo unknown)

if grep -q "i.MX 6" /proc/cpuinfo; then
  ISMX6=true
else
  ISMX6=false
fi

# Check if it's TI CPU.
if grep -q "AM572x" /proc/cpuinfo; then
  ISTI=true
elif grep -q "AM33XX" /proc/cpuinfo; then
  ISTI=true
else
  ISTI=false
fi

### Functions

# Find the framebuffer geometry of primary fb.
# The y-axis may be double that of standard sizes.
display_res() {
    local fb_path="$(readlink -f ${FRAMEBUFFER:-/dev/fb0})"
    local fb_sys_path="/sys/class/graphics/fb${fb_path##*[^0-9]}"
    local xres yres

    if [ ! -e "$fb_sys_path" ]; then
        echo unknown
        return
    fi

    if [ "$ISMX6" = true ]; then
        xres=$(cat ${fb_sys_path}/virtual_size | cut -d',' -f1)
        yres=$(cat ${fb_sys_path}/virtual_size | cut -d',' -f2)
    elif [ "$ISTI" = true ]; then
        xres=0
        yres=0
    else
        xres=$(sed -n '/[[:digit:]]x/{s/^[^[:digit:]]*\([[:digit:]]\+\)x.*$/\1/;p}' ${fb_sys_path}/mode)
        yres=$(sed -n '/x[[:digit:]]/{s/^.*x\([[:digit:]]\+\).*$/\1/;p}' ${fb_sys_path}/mode)
    fi

    echo "${xres}x${yres}"
}

# 4GB and 8GB MLC has 512k ERASESIZE and 4k PAGESIZE
# 1GB NAND has 256k ERASESIZE and 4k PAGESIZE
# 128MB NAND has 128k ERASESIZE, 2k PAGESIZE and 512b sub-page size
nand_geometry() {
  local ERASESIZE=$(($(cat /sys/class/mtd/mtd0/erasesize) / 1024))
  local PAGESIZE=$(($(cat /sys/class/mtd/mtd0/writesize) / 1024))

  echo ${PAGESIZE}k${ERASESIZE}k
}

nand_page_size() {
  cat /sys/class/mtd/mtd0/writesize
}

nand_erase_size() {
  cat /sys/class/mtd/mtd0/erasesize
}

## Try and figure out the NAND size that is present
nand_size() {
  local SIZES=$(cat /proc/mtd | grep mtd | cut -d " " -f 2)
  local NANDSIZE=0

  # Sum the MTD partitions. This is a bit flawed, because there is no rule that mtd partitions
  # must use the whole device, or that the mtd partitions cannot overlap. Our mx51 platform
  # uses the whole device in non-overlapping partitions, so this will work.
  for size in $SIZES; do
    size=$((0x$size))
    NANDSIZE=$(($NANDSIZE + $size))
  done
  unset size

  echo $NANDSIZE
}

have_emmc() {
  # HFP-1223: DO NOT MODIFY THIS FUNCTION.  It is overridden by "shims" in the initramfs.
  # HFP-1223: Future changes to support new SoC/eMMC's should be reflected in the shim, not here.
  # HFP-1223: This definition will only be used on kernels produced without the shim (<= 3.0.35).
  #
  # Tolerate different driver names: mxsdhci on mx51, sdhci-esdhc-imx on imx6 kernel version 3.0.35.
  if [ -e /sys/devices/platform/mxsdhci.2/mmc_host/mmc*/mmc*:0001 ] || \
     [ -e /sys/devices/platform/sdhci-esdhc-imx.2/mmc_host/mmc*/mmc*:0001 ]; then
    return 0
  else
    return 1
  fi
}

show_debug_msg() {
  ${DEBUG} && {
     echo "Showing 'debug mode' notification."
     splashy_update "print Just in case you didn't already
know, you are running this device
in debug mode!"
     sleep 6
     splashy_update "print This updater will be more verbose
than normal and there are other
differences in system behaviour
that you may not be expecting."
     sleep 9
     splashy_update "print Don't say I didn't warn you!"
     sleep 3
     splashy_update "clear"
  }
}

# Get the MTD used for UBI
find_ubi_mtd_partition() {
  local UBIMTD="$(grep -i '"system"' /proc/mtd | sed 's/^mtd//; s/:.*//')"
  if [ -z "${UBIMTD}" ]; then
    return 1
  fi
  echo $UBIMTD
}

find_mtd_partition_from_name() {
    local mtd="$(grep -i "\"${1}\"" /proc/mtd | cut -d':' -f1)"
    if [ -z "$mtd" ]; then
        return 1
    fi
    echo $mtd
}

# Returns the named UBI-volume device in format ubiX_Y (eg ubi0_1)
# Name can optionally include UBI device, eg "ubi0:root" (rather than just "root")
find_ubi_volume_num_from_name() {
    local dev=${1%%:*} name=${1#*:}

    [ "$dev" = "$name" ] && dev="ubi0"

    local syspath
    for syspath in /sys/class/ubi/${dev}_?; do
        if [ "$(cat ${syspath}/name 2>/dev/null)" = "$name" ]; then
            echo "${syspath##*/}"
            return 0
        fi
    done

    return 1
}

ATTACH_CHECK_UBI_VER=2
# HFP-2039: This function *might* be overridden/shimmed if a newer
# HFP-2039: version exists in the (running kernel) initramfs.
# HFP-2039: Refer initrd/lib/functions/updater-shims.sh.
attach_check_ubi() {
    local ubimtd reqd_vols vol

    ubimtd=$(find_ubi_mtd_partition) || return 1
    ubiattach /dev/ubi_ctrl -m $ubimtd || true

    if [ -n "${REQD_UBI_VOLS:-}" ]; then
        reqd_vols="$REQD_UBI_VOLS"
    elif have_emmc; then
        reqd_vols="NOS root home"
    else
        reqd_vols="NOS root home data"
    fi

    for vol in $reqd_vols; do
        if ! find_ubi_volume_num_from_name "$vol" >/dev/null; then
            printf 'Missing UBIFS volume "%s"\n' "$vol" >&2
            return 1
        fi
    done
}

get_upd_component() {
  # Pipe the named updater component to stdout.
  local name=$1

  if [ -s "$name" ]; then
    # If "name" refers to an existing file, use it.
    cat "$name"
  else
    # Otherwise assume "name" is in the updater tarball.
    tar -xOf "$UPDFILE" "${COMPONENT_PREFIX:-}${name}"
  fi
}

start_splashy() {
  # If updater packages a version of splashy, use that. Older mx51 builds include splashy in the init ramdisk,
  # so if this is not present it is not necesarily fatal
  if sha1check splashy.tar.xz; then
    get_upd_component splashy.tar.xz | xzcat | tar x -C /
    [ -x /etc/rc.d/rc.splashysetup ] && /etc/rc.d/rc.splashysetup
    if [ -x /usr/sbin/splashy -a -x /usr/sbin/splashy_update ]; then
      USE_SPLASHY=true
    fi
  elif [ -x /usr/sbin/splashy_theme -a -x /usr/sbin/splashy -a -x /usr/sbin/splashy_update ]; then
    USE_SPLASHY=true
  fi

  # Make splashy_update failure nonfatal.
  splashy_update () {
     [ -n "$USE_SPLASHY" ] && command "splashy_update" "$@" 2>/dev/null || true;
     return 0;
  }

  [ -n "$USE_SPLASHY" ] && (
    cd /etc/splashy
    if [ -e "config-logoprogress.xml" ]; then
      ln -sf "config-logoprogress.xml" config.xml
    fi
  )
  [ -n "$USE_SPLASHY" ] && splashy boot || true;

  PROGRESS=0
  splashy_update "progress ${PROGRESS}"
}

start_progress_bar() {
  # Background progress loop.
  (
    set +x
    if ! have_emmc ; then
      PROGRESSBARDELAY=$((2*${PROGRESSBARDELAY}))
    fi
    while :;
     do
       if [ ${PROGRESS} -ge 95 ]; then
         splashy_update "noop"
       else
         PROGRESS=$((PROGRESS+1))
         splashy_update "progress ${PROGRESS}"
       fi
       sleep ${PROGRESSBARDELAY}
       if [ ${PROGRESS} -ge 75 ]; then
         # 2x delay
         sleep ${PROGRESSBARDELAY}
       fi
       if [ ${PROGRESS} -ge 90 ]; then
         # 4x delay
         sleep ${PROGRESSBARDELAY}
         sleep ${PROGRESSBARDELAY}
       fi
     done
  ) &
  echo "$!" > /run/splashy-progress.pid
}

stop_progress_bar() {
  kill $(cat /run/splashy-progress.pid)
  rm /run/splashy-progress.pid
  splashy_update "clear"
  splashy_update "progress 100"
}

# Check a component exists and it is not corrupted
# $1 = component name
# $2 = fail if component missing
validate_component() {
  # What are we upgrading?
  echo "Calculating work for ${1}..."
  ${DEBUG} && {
      splashy_update "clear"
      splashy_update "print Calculating work for
${1}..."
   }

   sha1check ${1}
}

log_update_failure() {
   local mnt=/mnt/home
   mkdir -p $mnt

   if mount $mnt ; then
      if [ -e $mnt/update-history.txt ]; then
	 # modify pending to failure
         sed -i '$ s/^\(.*\)\tP$/\1\t1/' $mnt/update-history.txt
         sync
      fi
      umount $mnt
   else
      echo "Failed to log update failure" >&2
   fi
   rmdir $mnt || true
}

internal_fail() {
  printf "$1"
  if "${DEBUG}"; then
    splashy_update "print $1"
  else
    splashy_update "print ERROR:  System is damaged.
Contact customer service."
  fi

   while true; do
      splashy_update "noop"
      sleep 60
   done
}

corrupt_component_fail() {
  log_update_failure
  touch /stop-progress-flag.$$
  if "${DEBUG}" ; then
    echo "ERROR:  Bad update for ${1}.  Halted!"
    splashy_update "print ERROR:  Bad update for
${1}
Refusing to program bad software.
Please replace your update file."
  else
     echo "ERROR:  This update is damaged.  Halted!"
     splashy_update "print ERROR:  This update is damaged.
Refusing to program bad software.
Please replace your update file."
  fi
  set +x

  if [ $(cut -f2 /proc/navico_platform/disp_size) = "none" ]; then
     exit 0
  else
     wait_for_media
     splashy_update "exit"
     reboot -f
  fi
}

write_mtd_partition_from_file() {
  local partition_name=$1
  local filename=$2

  local PARTITION_SIZE_HEX=$(grep -i "${partition_name}" /proc/mtd | \
         sed 's/^mtd.\: *//; s/ .*$//')

  ${DEBUG} && echo -n ", length 0x${PARTITION_SIZE_HEX}"

  local PARTITION_DEV=$(grep -i "\"${partition_name}\"" /proc/mtd | \
         sed 's/: .*$//' )
  ${DEBUG} && echo ", device ${PARTITION_DEV}"

  # Make sure we're not flashing an image larger than our partition.
  local PARTITION_SIZE_DEC=$(printf "%d" "0x${PARTITION_SIZE_HEX}")
  local COMPONENT_SIZE_DEC=$(stat -c %s "$filename")

  ${DEBUG} && echo "DEBUG: Partition size is ${PARTITION_SIZE_DEC} bytes"
  ${DEBUG} && echo "DEBUG: Source image is   ${COMPONENT_SIZE_DEC} bytes"
  if [ "${COMPONENT_SIZE_DEC}" -gt "${PARTITION_SIZE_DEC}" ]; then
    echo "ERROR: $filename too big for target"
    splashy_update "print ERROR: $filename is
too big for target"
    log_update_failure
    wait_for_media
    splashy_update "exit"
    exit 1
  fi

  echo "Erasing flash: ${partition_name}"
  ${DEBUG} && splashy_update "print Erasing flash:
${partition_name}"

  flash_eraseall -q /dev/"${PARTITION_DEV}"

  ## Write region
  # Using nandwrite
  #   nandwrite doesn't do streaming input, extract to a temporary file.
  #   WARNING:   This temporary file must be less than (system memory - running kernel)
  echo "Writing new image: $filename"
  ${DEBUG} && splashy_update "print Writing new image:
$filename"

  nandwrite -p /dev/${PARTITION_DEV} $filename
}

write_mtd_partition() {
  local mtd_name=$1 file=$2

  if [ -s "$file" ]; then
    write_mtd_partition_from_file "$mtd_name" "$file"
  else
    local file_tmp="${file}-$$.tmp"
    get_upd_component "$file" > "$file_tmp" || corrupt_component_fail "$file"
    write_mtd_partition_from_file "$mtd_name" "$file_tmp"
    rm "$file_tmp"
  fi
}

update_kernel_or_fail() {
  write_mtd_partition kernel "$1"
}

update_rescue_or_fail() {
  write_mtd_partition rescue "$1"
}

update_check_bi_swap_enabled() {
  # Don't rely on the /sys/devices/platform/mxc_nandv2_flash.0/disable_bi_swap file if we are in
  # a factory update (since the running kernel will always start with bi_swap ON)
  local BI_SWAP
  if egrep -iqs unsupervised-update /proc/cmdline ; then
    local SAVE_BI_SWAP=$(cat /sys/devices/platform/mxc_nandv2_flash.0/disable_bi_swap)

    echo 1 > /sys/devices/platform/mxc_nandv2_flash.0/disable_bi_swap
    dd if=/dev/mtd0 | strings > ubootstrings
    echo $SAVE_BI_SWAP > /sys/devices/platform/mxc_nandv2_flash.0/disable_bi_swap

    if grep -q u-boot ubootstrings; then
      echo u-boot found
      BI_SWAP=$(cat ubootstrings | grep "bi_swap=[01]" | head -n 1)
      echo BI_SWAP = $BI_SWAP

      rm ubootstrings

      if [ -z "$BI_SWAP" ]; then
        # If bi_swap can't be found in u-boot environment, assume the installed bootloader will be pre- bi_swap change
        return 1
      elif [ "$BI_SWAP" = "bi_swap=1" ]; then
        return 0
      else
        return 1
      fi
    else
      # No u-boot detected. Flash is (probably) blank. Turn on bi_swap
      echo u-boot not found

      rm ubootstrings
      return 0
    fi
  else
    # For normal in-field updates, the kernel could be very old, in which case the disable_bi_swap
    # file will not exist. In this case the bi_swap is always OFF

    if [ ! -e /sys/devices/platform/mxc_nandv2_flash.0/disable_bi_swap ]; then
      return 1
    elif  [ "$(cat /sys/devices/platform/mxc_nandv2_flash.0/disable_bi_swap)" = "1" ]; then
      return 1
    else
      return 0
    fi
  fi
}

update_boot_imx51() {
  local filename=$1

  mkdir boottmp
  get_upd_component "$filename" | xzcat | tar x -C boottmp

  if update_check_bi_swap_enabled; then
    BI_SWAP=1
  else
    BI_SWAP=0
  fi
  echo Setting bi_swap=$BI_SWAP
  sed -i "s/bi_swap=[01]/bi_swap=${BI_SWAP}/" boottmp/all.env

  # If not forcing the CODENAME then preserve any existing one
  if [ -z "$CONVCODENAME" ]; then
    CONVCODENAME=$(sed -n 's/.*navico_sku\.codename=\([^ ]*\).*/\1/p' /proc/cmdline)
  fi

  # If we 'are' overriding the codename then flag it
  case "${CONVCODENAME}" in
  flensburg)
    sed -i 's/navico_sku=${navico_sku}/& navico_sku.codename=flensburg/' boottmp/all.env
    ;;
  esac

  ENVPAD=boottmp/envpad-arm

  ENV_OFFSET=$( ${ENVPAD} --info | sed 's/.*offset=\([0-9]*\).*/\1/' )
  ${ENVPAD} --info

  local BOOT_LOADER=boottmp/boot.bin

  for copy in 1 2; do
    dd if=${BOOT_LOADER} bs=${ENV_OFFSET} count=1
    cat boottmp/all.env | ${ENVPAD} -
  done > uboot.bin
  rm -fR boottmp

  write_mtd_partition_from_file boot uboot.bin

  rm uboot.bin

  return 0
}

update_boot_imx6() {
  local filename=$1	# The bootloader tarball, with tools etc included
  local TMP_DIR=/tmp/boottmp

  # HFP-5876: 64bit 1GB iMX61DL CPU card isn't supported anymore
  local major_version=$(echo $version | cut -f1 -d .)
  local minor_version=$(echo $version | cut -f2 -d .)
  local current_major_ver=$(cut -f1  /proc/navico_platform/boot_ver)
  local current_minor_ver=$(cut -f2  /proc/navico_platform/boot_ver)
  local hw_id=$(cut -f1 /proc/navico_platform/cpu_card_ver)
  if [ $(( 10*major_version + minor_version )) -ge 88 -a         \
       $(( 10*current_major_ver + current_minor_ver )) -le 87 -a \
       "$PROC_SUB_FAMILY" = "dl" -a $(( hw_id & 0x60 )) -eq $(( 0x40 )) ]; then
       echo "Not safe to update the bootloader, skipping."
       ${DEBUG} && {
           splashy_update "print Not safe to update the
bootloader, skipping."
           sleep 1
       }
      return 0
  fi

  rm -fr $TMP_DIR && mkdir -p $TMP_DIR
  get_upd_component "$filename" | xzcat | tar -C $TMP_DIR -xf -

  local ENVPAD=$TMP_DIR/envpad-arm
  local ENV=$TMP_DIR/all.env
  local UBOOT_ORIG=$TMP_DIR/u-boot.bin
  local ENV_OFFSET=$( ${ENVPAD} --info | sed 's/.*offset=\([0-9]*\).*/\1/' )
  local UBOOT_NEW=${UBOOT_ORIG}.new

  if [ ! -x "$ENVPAD" -o ! -f "$ENV" -o ! -s "$UBOOT_ORIG" ]; then
    printf "Invalid bootloader component, aborting.\n" >&2
    return 1
  fi

  # Write the device specific MAC/IP into the U-Boot environment
  if ! grep -q unsupervised-update /proc/cmdline; then
    if mount /mnt && mount /mnt/etc/NOS && [ -s /mnt/etc/NOS/MAC ]; then
      local mac_addr=$(head -1 /mnt/etc/NOS/MAC)
      local ip_addr=$(awk '{
          split($1, a, ":");
          printf("10.%d.%d.%d\n", "0x"a[4], "0x"a[5], "0x"a[6]);
        }' </mnt/etc/NOS/MAC)
      sed -i '/^\(ethaddr\|ipaddr\|netmask\|gatewayip\)=.*/d' $ENV
      printf "ethaddr=%s\nipaddr=%s\nnetmask=255.255.255.255\ngatewayip=10.255.255.254\n" ${mac_addr} ${ip_addr} >> $ENV
    fi
    umount /mnt/etc/NOS || true
    umount /mnt || true
    sort $ENV > ${ENV}.sorted
    mv ${ENV}.sorted $ENV
  fi

  # If not forcing the CODENAME then preserve any existing one
  if [ -z "${CONVCODENAME:-}" ]; then
    CONVCODENAME=$(sed -n 's/.*navico_sku\.codename=\([^ ]*\).*/\1/p' /proc/cmdline)
  fi

  # If we 'are' overriding the codename then flag it
  if [ -n "${CONVCODENAME:-}" ]; then
    sed -i 's/navico_sku=${navico_sku}/& navico_sku.codename='"${CONVCODENAME}/" $ENV
  fi

  {
    dd if=$UBOOT_ORIG bs=$ENV_OFFSET count=1
    ${ENVPAD} $ENV
  } >$UBOOT_NEW

  # HFP-1035 Bobo used Micron 1 GiB NAND, need to use different command for kobs-ng
  # HFP-1806 Support installation of 384 and 512 kiB bootloader images
  local search_exp
  local uboot_sz=$(stat -c'%s' "$UBOOT_NEW")
  local boot_cfg="$((uboot_sz/1024)):$(($(nand_erase_size)/1024))"

  case $boot_cfg in
  "384:128")  search_exp=1 ;;
  "384:256")  search_exp=1 ;;
  "512:128")  search_exp=0 ;;
  "512:256")  search_exp=0 ;;
  *)          echo "Unsupported bootloader configuration $boot_cfg"
              return 1
              ;;
  esac

  local ret
  flash_eraseall /dev/mtd0
  $TMP_DIR/kobs-ng init -w -v --search_exponent=$search_exp $UBOOT_NEW
  ret=$?

  rm -fr $TMP_DIR
  return $ret
}

update_boot_ti() {
  local filename=$1 # The bootloader tarball
  local TMP_DIR=/tmp/boottmp

  rm -fr $TMP_DIR && mkdir -p $TMP_DIR
  get_upd_component "$filename" | xzcat | tar -C $TMP_DIR -xf -

  local ENVPAD=$TMP_DIR/envpad-arm
  local ENV=$TMP_DIR/all.env
  local ENV_OFFSET=$( ${ENVPAD} --info | sed 's/.*offset=\([0-9]*\).*/\1/' )
  local FLASH_ERASE=$TMP_DIR/flash_erase
  local UBOOT_BIN=$TMP_DIR/u-boot.bin
  local UBOOT_BIN_OFS=$(( 512*1024 )) # u-boot.bin, offset = CONFIG_SYS_NAND_U_BOOT_OFFS, 0x80000.
  local UBOOT_TMP=${UBOOT_BIN}.tmp
  local MLO=$TMP_DIR/MLO
  local BOOT_MTD="/dev/mtd0"
  local EB_SIZE=$(( 256*1024 )) # erase block size = 0x40000, 256KB
  local PG_SIZE=$(( 4*1024 ))   # page size = 0x1000, 4KB
  local START_OFS=0

  if [ ! -x "$ENVPAD" -o ! -f "$ENV" -o ! -s "$MLO" -o ! -s "$UBOOT_BIN" -o ! -f "$FLASH_ERASE" ]; then
    printf "Invalid bootloader component, aborting.\n" >&2
    return 1
  fi

  # Write the device specific MAC/IP into the U-Boot environment
  if ! grep -q unsupervised-update /proc/cmdline; then
    if mount /mnt && mount /mnt/etc/NOS && [ -s /mnt/etc/NOS/MAC ]; then
      local mac_addr=$(head -1 /mnt/etc/NOS/MAC)
      local ip_addr=$(awk '{
          split($1, a, ":");
          printf("10.%d.%d.%d\n", "0x"a[4], "0x"a[5], "0x"a[6]);
        }' </mnt/etc/NOS/MAC)
      sed -i '/^\(ethaddr\|ipaddr\|netmask\|gatewayip\)=.*/d' $ENV
      printf "ethaddr=%s\nipaddr=%s\nnetmask=255.255.255.255\ngatewayip=10.255.255.254\n" ${mac_addr} ${ip_addr} >> $ENV
    fi
    umount /mnt/etc/NOS || true
    umount /mnt || true
    sort $ENV > ${ENV}.sorted
    mv ${ENV}.sorted $ENV
  fi

  # If not forcing the CODENAME then preserve any existing one
  if [ -z "${CONVCODENAME:-}" ]; then
    CONVCODENAME=$(sed -n 's/.*navico_sku\.codename=\([^ ]*\).*/\1/p' /proc/cmdline)
  fi

  # Erase and write two copies of SPL
  local MLO_SZ=$(stat -Lc '%s' "$MLO")
  local MLO_EB_NUM=$(( (MLO_SZ + EB_SIZE - 1)/EB_SIZE ))
  "$FLASH_ERASE" "$BOOT_MTD" "$START_OFS" "$MLO_EB_NUM"
  dd if="$MLO" bs="$PG_SIZE" seek="$START_OFS" of="$BOOT_MTD" conv=sync
  START_OFS=$(( START_OFS + EB_SIZE*MLO_EB_NUM ))
  "$FLASH_ERASE" "$BOOT_MTD" "$START_OFS" "$MLO_EB_NUM"
  dd if="$MLO" bs="$PG_SIZE" seek=$(( EB_SIZE/PG_SIZE )) of="$BOOT_MTD" conv=sync
  START_OFS=$(( START_OFS + EB_SIZE*MLO_EB_NUM ))

  {
    dd if="$UBOOT_BIN" bs="$ENV_OFFSET" count=1 conv=sync
    ${ENVPAD} $ENV --ubootcrc32 "$UBOOT_BIN"
  } > "$UBOOT_TMP"

  local UBOOT_TMP_SZ=$(stat -Lc '%s' "$UBOOT_TMP")
  local UBOOT_EB_NUM=$(( (UBOOT_TMP_SZ + EB_SIZE - 1)/EB_SIZE ))
  # Erase and write two copies of u-boot.bin
  "$FLASH_ERASE" "$BOOT_MTD" "$START_OFS" "$UBOOT_EB_NUM"
  dd if="$UBOOT_TMP" bs="$PG_SIZE" seek=$(( START_OFS/PG_SIZE )) of="$BOOT_MTD" conv=sync
  START_OFS=$(( START_OFS + EB_SIZE*UBOOT_EB_NUM ))
  # Erase and reserve one erase block after the first u-boot
  "$FLASH_ERASE" "$BOOT_MTD" "$START_OFS" 1
  START_OFS=$(( START_OFS + EB_SIZE ))

  "$FLASH_ERASE" "$BOOT_MTD" "$START_OFS" "$UBOOT_EB_NUM"
  dd if="$UBOOT_TMP" bs="$PG_SIZE" seek=$(( START_OFS/PG_SIZE )) of="$BOOT_MTD" conv=sync
  START_OFS=$(( START_OFS + EB_SIZE*UBOOT_EB_NUM ))
  # Erase and reserve one erase block after the second u-boot
  "$FLASH_ERASE" "$BOOT_MTD" "$START_OFS" 1

  rm -fr $TMP_DIR
  return 0
}

update_boot_or_fail() {
  local filename=$1
  local version=$2
  local major_version=$(echo $version | cut -f1 -d .)
  local minor_version=$(echo $version | cut -f2 -d .)
  local update_required=0

  if [ "${version}" != "-1" ]; then
    local current_major_ver=$(cut -f1  /proc/navico_platform/boot_ver)
    local current_minor_ver=$(cut -f2  /proc/navico_platform/boot_ver)

    echo "Bootloader is version ${current_major_ver}.${current_minor_ver}"
    ${DEBUG} && splashy_update "print Bootloader is version
${current_major_ver}.${current_minor_ver}"
    if [ ${current_major_ver} -eq 0 ]; then
       echo "It is not safe to update this version, skipping."
       ${DEBUG} && {
         splashy_update "print It is not safe to update this
version, skipping."
         sleep 1
       }
       return 0
    fi

    # Check Major and minor version to decide whether the update is required or not
    if [ ${major_version} -gt ${current_major_ver} ]; then
       update_required=1
    elif [ ${major_version} -eq ${current_major_ver} ]; then
       if [ ${minor_version} -gt ${current_minor_ver} ]; then
          update_required=1
       else
	  update_required=0
       fi
    else
       update_required=0
    fi

    # Is the current version of the bootloader less than the minimum allowed bootloader?
    if [ ${update_required} -eq 1 ];
    then
      echo "Bootloader is out of date and needs updating."
      ${DEBUG} && splashy_update "print Bootloader is out of date and
needs updating."
    else
      echo "Bootloader is already up-to-date."
      ${DEBUG} && {
        splashy_update "print Bootloader is already up-to-date."
        sleep 1
      }
      return 0
    fi
  fi


  if $ISMX6; then
    update_boot_imx6 $filename
  elif [ "$ISTI" = true ]; then
    update_boot_ti $filename
  else
    update_boot_imx51 $filename
  fi
}

get_media_re() {
  # Get the device number (in decimal) of updater
  local media_dev=$(stat -c '%d' "${UPDFILE}")
  # Generate a regex to use with /proc/partitions
  local media_re="^[[:space:]]*$(($media_dev/256))[[:space:]]\+"	# major
  media_re="${media_re}$(($media_dev%256))[[:space:]]\+"	# minor
  media_re="${media_re}[[:digit:]]\+[[:space:]]\+mmcblk"	# blocks, name
  printf "%s\n" "$media_re"
}

wait_for_media() {
   local media_re=$(get_media_re)
   if grep "${media_re}" /proc/partitions; then
      set +x  # squelch annoying infinite loop.
      while grep -q "${media_re}" /proc/partitions; do
         splashy_update "noop" 2>/dev/null || true
      done
   fi
}

# If the updater is stored on an MMC device, then wait
# for the partition to disappear (card removal).  For all other
# sources, wait for the power button to be pressed.
wait_for_media_reboot() {
  # new normal behaviour: never wait
  if [ -e ~/.updater/reboot_wait ]; then
      local media_re=$(get_media_re)

      # Omit -q(uiet) flag for first grep
      if grep "${media_re}" /proc/partitions; then
	  splashy_update "print Upgrade completed.
Remove media to reboot."
      else
	splashy_update "print Upgrade completed.
Press the power key to reboot."
      fi

      wait_for_media
  else
    splashy_update "print Rebooting"
    sleep 2
  fi

  splashy_update "exit"
  echo 'reboot -f' > ~/.updater/bootaction
}



libupdate_resolvelinkpath() {
   # Find a 'real' subdirectory by resolving symlinks.
   # Unlike readlink's canonicalize options, this is designed to operate
   # on a mountpoint where absolute links are relative to the top of the filesystem
   # (ie, an inactive rootfs mounted beneath an alternate live rootfs).
   # $1= initial target to resolve.
   # $2= base directory to prepend per iteration and to the final output.  "The chroot".
   # On success, return 0 and print a string containing the absolute resolved location.
   # On failure, return 1.

   local TARGET
   local NEWTARGET
   local BASEPATH
   local LINKCWD

   TARGET="${1}"
   BASEPATH="${2:+"${2}/"}"

   while [ -h "${TARGET}" ]; do
      NEWTARGET="$(readlink "${TARGET}")"
      LINKCWD="$(cd "$(dirname "${TARGET}")" && pwd)/"

      # Was this symlink relative or absolute?
      if echo "${NEWTARGET}" | grep -q "^/" ; then
         # Absolute.  Prepend the basepath.
         NEWTARGET="${BASEPATH}${NEWTARGET}"
      else
         # Relative.  Resolve against the link's parent directory and prepend the basepath.
         NEWTARGET="${BASEPATH}${LINKCWD}${NEWTARGET}"
      fi

      # Don't get stuck.
      if [ "${TARGET}" = "${NEWTARGET}" ]; then
         echo "Symlink loop.  Failing." >&2
         return 1
      fi

      TARGET="${NEWTARGET}"
   done

   echo "${TARGET}"
   return 0
}


libupdate_findparentmountpoint() {
   # Given a directory name, traverse the tree towards / until a mountpoint is found.
   # $1= starting directory
   # On success, return 0
   # On failure, return 1

   local DIR
   DIR="${1}"

   # This is a real directory, right?
   if ! (cd "${DIR}"); then
      echo "ERROR:  Can't chdir into ${1}" >&2
      return 1
   fi

   # Climb toward the root of the tree.
   while [ "${DIR}" != "/" -a -n "${DIR}" ] && \
      ! mountpoint -q "${DIR}" ;
   do
      DIR="$(echo "${DIR}" | rev | cut -f 2- -d '/' | rev)"
   done
   echo "${DIR:-/}"
   return 0
}



libupdate_sims_delete() {
   # Remove all files in the simulator directory.
   # Requires CODENAME to be set.
   # On success, returns 0
   # On failure, returns 1
   local simstarget
   local simsmntpt

   simstarget="$(libupdate_resolvelinkpath /mnt/usr/share/NOS/simulator /mnt)"

   # If the directory doesn't exist, don't try.
   if [ -z "${simstarget}" ]; then
      echo "ERROR: Empty simulator directory parameter."  >&2
      return 1
   fi
   if ! (cd "${simstarget}"); then
      echo "ERROR: Simulator directory '${simstarget}' doesn't exist." >&2
      return 1
   fi

   simsmntpt="$(libupdate_findparentmountpoint "${simstarget}")"

   (  cd "${simstarget}" || exit 1
      mount -o remount,rw "${simsmntpt}" || {
         echo "ERROR:  Couldn't remount ${simsmntpt} as read-write." >&2
         exit 1
      }
      rm -rf *
      mount -o remount,ro "${simsmntpt}"
   )
   return $?
}



libupdate_sims_copyfromcard() {
   # Copy in the "sims/" directory from the update media.
   # Requires CODENAME and UPDDIR to be set.
   # Status messages will be printed on stdout.
   local simssrc
   local simsdir
   local simstarget
   local simsmntpt

   simstarget="$(libupdate_resolvelinkpath /mnt/usr/share/NOS/simulator /mnt)"

   if [ -z "${simstarget}" ]; then
      echo "ERROR:  Empty simulator installation target string." >&2
      return 1
   fi
   if ! (cd "${simstarget}"); then
      echo "ERROR:  Can't chdir into ${simstarget}" >&2
   fi

   simsmntpt="$(libupdate_findparentmountpoint "${simstarget}")"
   mount -o remount,rw "${simsmntpt}" || {
      echo "ERROR:  Couldn't remount ${simsmntpt} as read-write." >&2
      return 1
   }

   simssrc="$(ls -d "${UPDDIR}"/[Ss][Ii][Mm][Ss] 2>/dev/null | head -n 1)"
   if [ -n "${simssrc}" ]; then
      splashy_update "print Copying in your simulators"
      echo "Copying in your simulators"

      # Use a fixed list of directory names.
      # FATfs is case insensitive, so the end user will probably not
      # match the case sensitive path we expect.
      # Luckily, this cuts the other way.
      # We can chdir into non-matching paths and get the files we want.
      # Note:  If your card uses a 'real' filesystem, it will need to match.
      (  cd "${simssrc}"
         for simsdir in \
            Demo/Scripts/ \
            Demo/Images/ \
            Logs/AIS/ \
            Logs/Radar/ \
            Logs/Weather/ \
            Logs/Sonar/ \
            ;
         do (
            cd "${simsdir}" 2>/dev/null || exit 0
            mkdir -p "${simstarget}/${simsdir}"
            export simstarget
            export simsdir
            find . -maxdepth 1 -type f -exec sh -c 'cat "{}" > "${simstarget}/${simsdir}/{}"' \;
         ); done
      )
      sync

      # If there are any loose files directly in simssrc, file them away appropriately.
      # It's easier to do this with a temporary script called as an argument to find -exec
      cat << _EOF > /tmp/process_sim.sh
#!/bin/sh
set -x
# arg 1 = path to simulator directory on the target
# arg 2 = filename
case "\${2}" in
*.[Ss][Ll][Gg]|*.[Ss][Ll]2)
   mkdir -p "\${1}/Logs/Sonar/"
   cat "\${2}" > "\${1}/Logs/Sonar/\${2}"
   ;;
*.[Ww][Xx])
   mkdir -p "\${1}/Logs/Weather/"
   cat "\${2}" > "\${1}/Logs/Weather/\${2}"
   ;;
*.[Rr][Ss][Ff])
   # All .rsf files can be considered radar logs because
   # there is no facility to create AIS logs.
   # If a new AIS log needs to be loaded, then it needs
   # to be placed in the fixed directory structure above.
   mkdir -p "\${2}/Logs/Radar/"
   cat "\${2}" > "\${1}/Logs/Radar/\${2}"
   ;;
*.[Pp][Nn][Gg])
   mkdir -p "\${1}/Demo/Images/"
   cat "\${2}" > "\${1}/Demo/Images/\${2}"
   ;;
*.[Tt][Xx][Tt])
   if grep -q "^DEMO SCRIPT FILE\$" "\${2}" ; then
      mkdir -p "\${1}/Demo/Scripts/"
      cat "\${2}" > "\${1}/Demo/Scripts/\${2}"
   fi
   ;;
*.[Ss][Mm][Ff])
   # TODO.  Requires R&D agreement on target location,
   # as well as implementation within the simulator.
   true
   ;;
esac
_EOF
      chmod +x /tmp/process_sim.sh
      (  cd "${simssrc}"
         export simstarget
         find . -maxdepth 1 -type f -exec sh -c '/tmp/process_sim.sh "${simstarget}" "{}"' \;
      )
      rm -f /tmp/process_sim.sh

      mount -o remount,ro "${simsmntpt}"
   fi

   return $?
}

ubifs_ls() {
    local ubi_dev=${UBI_DEV:-ubi0}
    local sysfs=/sys/class/ubi/${ubi_dev}
    local eb_size=$(cat ${sysfs}/eraseblock_size)

    for name in volumes_count eraseblock_size; do
        local val=$(cat "${sysfs}/${name}")
        printf "%-16s\t%d\n" "$name" $val
    done

    for name in reserved_for_bad bad_peb_count avail_eraseblocks total_eraseblocks; do
        local lebs=$(cat "${sysfs}/${name}")
        printf "%-16s\t%d\t%d\n" "$name" $lebs $((lebs * eb_size))
    done

    for name in ${sysfs}/${ubi_dev}_?; do
        local dev=${name##*/} lebs volname

        if lebs=$(cat "${name}/reserved_ebs" 2>/dev/null) &&
           volname=$(cat "${name}/name" 2>/dev/null);
        then
            printf "%-16s\t%d\t%d\n" "${dev}:${volname}" $lebs $((lebs * eb_size))
        fi
    done
}

ubifs_saverestore() {
    local action=$1 ubi_name=$2 # eg: save ubi0:home

    local savedir=/tmp/ubifs_saverestore
    local ubi_mnt="${savedir}/${ubi_name}-mnt"
    local ubi_img="${savedir}/${ubi_name}.img"
    local ubi_tar="${savedir}/${ubi_name}.tgz"
    local ubi_dev
    local result=0 # Success

    if [ "$action" = "cleanup" ]; then
        # Remove files, but leave mount point alone (in case something is mounted there)
        rm "$ubi_img" "$ubi_tar" || true

    elif ! ubi_dev=$(find_ubi_volume_num_from_name "$ubi_name"); then
        result=1

    else
        case $action in
        save)
            if [ ! -s "$ubi_img" ]; then
                mkdir -p "$ubi_mnt" &&
                dd if=/dev/${ubi_dev} of="$ubi_img" 2>/dev/null &&
                mount -t ubifs "$ubi_name" "$ubi_mnt" &&
                tar -C "$ubi_mnt" -czf "$ubi_tar" . ||
                result=2
            fi
            ;;

        restore-img)
            ubiupdatevol /dev/${ubi_dev} "$ubi_img" || result=3
            ;;

        restore-tar)
            # Restore volume from saved tarball.
            # Ensure we can write (then delete) a 100k temporary file.
            local tmp_file="${ubi_mnt}/.ubifs_saverestore.tmp.$$"

            ubiupdatevol /dev/${ubi_dev} -t &&
            mount -t ubifs "$ubi_name" "$ubi_mnt" &&
            tar -C "$ubi_mnt" -xzf "$ubi_tar" &&
            dd if=/dev/urandom of="$tmp_file" bs=1024 count=100 2>/dev/null &&
            sync && rm "$tmp_file" && sync ||
            result=4
            ;;

        *)
            result=5
            ;;
        esac

        mountpoint -q "$ubi_mnt" && umount "$ubi_mnt"
    fi

    [ $result -eq 0 ] || printf 'ERROR: ubifs_saverestore(%s) = %d\n' "$*" $result >&2

    return $result
}

ubifs_addvol() {
    # Usage: [new-volume-name] [size[cKM]] [resize-volume-name]
    #
    # Add new-volume of specified size, optionally resizing another volume to make it fit.
    # All parameters are optional and size can be specified with [cKM] or no suffix
    #   to indicate bytes, KiB(1024), MiB(1024*1024) or eraseblocks respectively.
    # The resize-volume contents are saved and restored.  If the resize/restore fails
    #   resize-vol is restored to it prior state/size, but new-volume may be deleted if
    #   it already existed.
    # If new-volume already exists, it will be recreated in the specifed size if required.
    #   The new-volume contents are not preserved.
    # If new-volume already exists, and size is unspecified or zero, new-volume will be removed.
    #   If the volume didn't already exist, no error is reported.
    #
    # Examples:
    # - Create/resize "logs" to 10MiB, and shrink/expand "home" as required to fill any remaining space
    #           ubifs_addvol logs 10M home
    # - Reserve 100 eraseblocks by resizing "home" as required
    #           ubifs_addvol "" 100 home
    # - Resize "home" to fill any available space
    #           ubifs_addvol "" 0 home
    # - Create new volume "test", 5 MiB in size
    #           ubifs_addvol test 5M
    # - Delete volume "test"
    #           ubifs_addvol test 0
    #
    local newvol_name=${1:-""} size_reqd=${2:-0} rszvol_name=${3:-""}
    local ubi_dev=${UBI_DEV:-ubi0}
    local result=0 sysfs="/sys/class/ubi/${ubi_dev}" eb_size

    if ! eb_size=$(cat "${sysfs}/eraseblock_size"); then
        result=1

    else
        local eb_reqd newvol_dev eb_free

        [ -n "$newvol_name" ] && newvol_name="${ubi_dev}:${newvol_name}"
        [ -n "$rszvol_name" ] && rszvol_name="${ubi_dev}:${rszvol_name}"

        case $size_reqd in
            *c)     eb_reqd=$(( ${size_reqd%c}               / eb_size )) ;;
            *K)     eb_reqd=$(( ${size_reqd%K} * 1024        / eb_size )) ;;
            *M)     eb_reqd=$(( ${size_reqd%M} * 1024 * 1024 / eb_size )) ;;
            *)      eb_reqd=$size_reqd # default unit is eraseblocks
        esac

        if [ -n "$newvol_name" ] && newvol_dev=$(find_ubi_volume_num_from_name "$newvol_name"); then
            if [ $(cat "${sysfs}/${newvol_dev}/reserved_ebs") -eq $eb_reqd ]; then
                # Size matches
                eb_reqd=0
            else
                # Size mismatch - delete volume
                ubirmvol /dev/${newvol_dev%_*} -n ${newvol_dev##*_} || result=2
            fi
        fi

        if [ $result -eq 0 ] && ! eb_free=$(cat "${sysfs}/avail_eraseblocks"); then
            result=3
        fi

        if [ $result -eq 0 -a -n "$rszvol_name" -a $eb_reqd -ne $eb_free ]; then
            local rszvol_dev rszvol_eb_cur

            if ! rszvol_dev=$(find_ubi_volume_num_from_name "$rszvol_name") ||
               ! rszvol_eb_cur=$(cat "${sysfs}/${rszvol_dev}/reserved_ebs");
            then
                result=4

            else
                local rszvol_eb_new=$((rszvol_eb_cur + eb_free - eb_reqd))

                printf 'Resizing "%s" from %d to %d eraseblocks (%d needed, %d available)\n' \
                    "$rszvol_name" $rszvol_eb_cur $rszvol_eb_new $eb_reqd $eb_free

                if ! ubifs_saverestore save "$rszvol_name"; then
                    result=5

                elif ! ubirsvol /dev/${rszvol_dev%_*} -n ${rszvol_dev##*_} -s $((rszvol_eb_new * eb_size)) ||
                     ! ubifs_saverestore restore-tar "$rszvol_name";
                then
                    # Resize/restore failed - restore previous configuration
                    ubirsvol /dev/${rszvol_dev%_*} -n ${rszvol_dev##*_} -s $((rszvol_eb_cur * eb_size)) || true
                    ubifs_saverestore restore-img "$rszvol_name" || result=$?
                    result=$((result + 10))

                else
                    # Resize sucessful
                    ubifs_saverestore cleanup "$rszvol_name" || result=6
                    eb_free=$eb_reqd
                fi
            fi
        fi

        if [ $result -eq 0 -a -n "$newvol_name" -a $eb_reqd -gt 0 ]; then
            if [ $eb_reqd -gt $eb_free ]; then
                result=7

            else
                # Create the new volume if required
                ubimkvol /dev/${ubi_dev} -N "${newvol_name#${ubi_dev}:}" -s $((eb_reqd * eb_size)) &&
                newvol_dev=$(find_ubi_volume_num_from_name "$newvol_name") &&
                ubiupdatevol /dev/${newvol_dev} -t ||
                result=8
            fi
        fi
    fi

    [ $result -eq 0 ] || printf 'ERROR: ubifs_addvol(%s) = %d\n' "$*" $result >&2

    return $result
}

showmsg() {
    local tty=/dev/tty1
    local attrs=${TITLE_ATTRS:-"1;42"}

    {
        local size_x=$(stty size|cut -d' ' -f2)
        local size_y=$(stty size|cut -d' ' -f1)
        local line_ofs=$((1+(size_y-${#})/2)) line=0

        printf '\33[0m\33[2J' # SGR reset, clear screen

        for msg; do
            msg="\033[$((line_ofs+line));$((1+(size_x-${#msg})/2))H${msg}"
            [ $line -eq 0 ] && msg="\033[${attrs}m${msg}\033[0m"
            echo -e "$msg"
            line=$((line+1))
        done
    } <$tty >$tty
}

uboot_ver_ge() {
    local major_ver=$(printf "%s" "$1" | cut -f1 -d.)
    local minor_ver=$(printf "%s" "$1" | cut -sf2 -d.)
    minor_ver=${minor_ver:-0}
    local current_major_ver=$(cut -f1 /proc/navico_platform/boot_ver)
    local current_minor_ver=$(cut -f2 /proc/navico_platform/boot_ver)

    if [ $current_major_ver -gt $major_ver -o \
         $current_major_ver -eq $major_ver -a \
         $current_minor_ver -ge $minor_ver ]; then
        return 0
    fi

    return 1
}
