#!/bin/bash

# Copyright (c) 2020, Mellanox Technologies
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.

# shellcheck disable=SC2059
true

bfcfg_version=0.3
cfg_file=/etc/bf.cfg
log_file=/tmp/bfcfg.log
dump_mode=0

efivars=/sys/firmware/efi/efivars
efi_global_var_guid=8be4df61-93ca-11d2-aa0d-00e098032b8c
efi_bfcfg_var_guid=487ff588-fb71-4b19-9100-ebe067aa1af0
efi_vlan_var_guid=9e23d768-d2f3-4366-9fc3-3a7aba864374
rshim_efi_mac_sysfs=${efivars}/RshimMacAddr-${efi_global_var_guid}
pxe_dhcp_class_id_sysfs=${efivars}/DhcpClassId-${efi_global_var_guid}

mfg_sysfs_dir=/sys/bus/platform/devices/MLNXBF04:00/driver
if [ ! -e $mfg_sysfs_dir/oob_mac ]; then
  mfg_sysfs_dir=/sys/bus/platform/devices/MLNXBF04:00
fi
oob_mac_sysfs=${mfg_sysfs_dir}/oob_mac
large_icm_sysfs=${mfg_sysfs_dir}/large_icm

ECHO=${ECHO:-echo}

log_msg()
{
  echo "$@" >> ${log_file}
}

mfg_lock()
{
  log_msg "mfg: lock the partition"
  echo 1 > ${mfg_sysfs_dir}/mfg_lock 2>>${log_file}
}

mfg_cfg()
{
  local tmp_file=/tmp/.bfcfg-mfg-data
  local opn_sysfs=${mfg_sysfs_dir}/opn
  local sku_sysfs=${mfg_sysfs_dir}/sku
  local modl_sysfs=${mfg_sysfs_dir}/modl
  local sn_sysfs=${mfg_sysfs_dir}/sn
  local uuid_sysfs=${mfg_sysfs_dir}/uuid
  local rev_sysfs=${mfg_sysfs_dir}/rev
  local oob_efi_mac_sysfs=${efivars}/OobMacAddr-${efi_global_var_guid}
  local mac_err opn_err mac

  if [ $dump_mode -eq 1 ]; then
    [ -e "${oob_mac_sysfs}" ] && echo "mfg: MFG_OOB_MAC=$(cat ${oob_mac_sysfs} 2>/dev/null)"
    [ -e "${opn_sysfs}" ] && echo "mfg: MFG_OPN=$(cat ${opn_sysfs} 2>/dev/null)"
    [ -e "${sku_sysfs}" ] && echo "mfg: MFG_SKU=$(cat ${sku_sysfs} 2>/dev/null)"
    [ -e "${modl_sysfs}" ] && echo "mfg: MFG_MODL=$(cat ${modl_sysfs} 2>/dev/null)"
    [ -e "${sn_sysfs}" ] && echo "mfg: MFG_SN=$(cat ${sn_sysfs} 2>/dev/null)"
    [ -e "${uuid_sysfs}" ] && echo "mfg: MFG_UUID=$(cat ${uuid_sysfs} 2>/dev/null)"
    [ -e "${rev_sysfs}" ] && echo "mfg: MFG_REV=$(cat ${rev_sysfs} 2>/dev/null)"
    return
  fi

  if [ -z "${MFG_OOB_MAC}" ] || [ -z "${MFG_OPN}" ] || [ -z "${MFG_SKU}" ] ||
     [ -z "${MFG_MODL}" ] || [ -z "${MFG_SN}" ] || [ -z "${MFG_UUID}" ] ||
     [ -z "${MFG_REV}" ] ; then
    log_msg "mfg: skip mfg since one or more of the following are unspecified:"
    log_msg "MFG_OOB_MAC, MFG_OPN, MFG_SKU, MFG_MODL, MFG_SN, MFG_UUID, MFG_REV"
    return
  fi

  if [ -n "${MFG_OOB_MAC}" ] && [ -e "${oob_mac_sysfs}" ]; then
    log_msg "mfg: OOB_MAC=${MFG_OOB_MAC}"
    echo "${MFG_OOB_MAC}" > ${oob_mac_sysfs} 2>>${log_file}
    mac_err=$?

    if [ ${mac_err} -eq 0 ]; then
      log_msg "mfg: oob_mac written"

      # Update the MAC address in UEFI variable.
      # Somehow 'printf' into the sysfs file doesn't always work, probably
      # due to length check of each write. A temporary file is used here
      # to workaround such issue.
      mac="${MFG_OOB_MAC//:/\\x}"
      mac="\\x07\\x00\\x00\\x00\\x${mac}"
      printf "${mac}" > ${tmp_file}
      chattr -i ${oob_efi_mac_sysfs} 2>/dev/null
      cp ${tmp_file} ${oob_efi_mac_sysfs}
      rm -f ${tmp_file}
    else
      log_msg "mfg: mac_err=${mac_err}"
      return
    fi
  fi

  if [ -n "${MFG_OPN}" ] && [ -e "${opn_sysfs}" ]; then
    log_msg "mfg: OPN=${MFG_OPN}"
    echo "${MFG_OPN}" > ${opn_sysfs} 2>>${log_file}
    opn_err=$?

    if [ ${opn_err} -eq 0 ]; then
      log_msg "mfg: opn written"
    else
      log_msg "mfg: opn_err=${opn_err}"
      return
    fi
  fi

  if [ -n "${MFG_SKU}" ] && [ -e "${sku_sysfs}" ]; then
    log_msg "mfg: SKU=${MFG_SKU}"
    echo "${MFG_SKU}" > ${sku_sysfs} 2>>${log_file}
    sku_err=$?

    if [ ${sku_err} -eq 0 ]; then
      log_msg "mfg: sku written"
    else
      log_msg "mfg: sku_err=${sku_err}"
      return
    fi
  fi

  if [ -n "${MFG_MODL}" ] && [ -e "${modl_sysfs}" ]; then
    log_msg "mfg: MODL=${MFG_MODL}"
    echo "${MFG_MODL}" > ${modl_sysfs} 2>>${log_file}
    modl_err=$?

    if [ ${modl_err} -eq 0 ]; then
      log_msg "mfg: modl written"
    else
      log_msg "mfg: modl_err=${modl_err}"
      return
    fi
  fi

  if [ -n "${MFG_SN}" ] && [ -e "${sn_sysfs}" ]; then
    log_msg "mfg: SN=${MFG_SN}"
    echo "${MFG_SN}" > ${sn_sysfs} 2>>${log_file}
    sn_err=$?

    if [ ${sn_err} -eq 0 ]; then
      log_msg "mfg: sn written"
    else
      log_msg "mfg: sn_err=${sn_err}"
      return
    fi
  fi

  if [ -n "${MFG_UUID}" ] && [ -e "${uuid_sysfs}" ]; then
    log_msg "mfg: UUID=${MFG_UUID}"
    echo "${MFG_UUID}" > ${uuid_sysfs} 2>>${log_file}
    uuid_err=$?

    if [ ${uuid_err} -eq 0 ]; then
      log_msg "mfg: uuid written"
    else
      log_msg "mfg: uuid_err=${uuid_err}"
      return
    fi
  fi

  if [ -n "${MFG_REV}" ] && [ -e "${rev_sysfs}" ]; then
    log_msg "mfg: REV=${MFG_REV}"
    echo "${MFG_REV}" > ${rev_sysfs} 2>>${log_file}
    rev_err=$?

    if [ ${rev_err} -eq 0 ]; then
      log_msg "mfg: rev written"
    else
      log_msg "mfg: rev_err=${rev_err}"
      return
    fi
  fi

  mfg_lock
}

add_trailing_null()
{
  for ((i = 0; i<${1}; i++))
  do
    printf '\0' >> ${2} 2>>${log_file}
  done
}

mfg_ext_cfg()
{
  local tmp_file=/tmp/.bfcfg-data
  local MKCAP=/usr/lib/firmware/mellanox/boot/capsule/scripts/mlx-mkcap
  local MFGEXT_CAPSULE=/tmp/BfCfgCapsule
  local BFREC=bfrec
  local MFG_LEN=256
  local sys_mfr_sysfs=${efivars}/BfCfgSysMfr-${efi_bfcfg_var_guid}
  local sys_pdn_sysfs=${efivars}/BfCfgSysPdn-${efi_bfcfg_var_guid}
  local sys_ver_sysfs=${efivars}/BfCfgSysVer-${efi_bfcfg_var_guid}
  local bsb_mfr_sysfs=${efivars}/BfCfgBsbMfr-${efi_bfcfg_var_guid}
  local bsb_pdn_sysfs=${efivars}/BfCfgBsbPdn-${efi_bfcfg_var_guid}
  local bsb_ver_sysfs=${efivars}/BfCfgBsbVer-${efi_bfcfg_var_guid}
  local bsb_sn_sysfs=${efivars}/BfCfgBsbSn-${efi_bfcfg_var_guid}
  local sys_mfr_max_len=24
  local sys_pdn_max_len=24
  local sys_ver_max_len=512
  local bsb_mfr_max_len=24
  local bsb_pdn_max_len=24
  local bsb_ver_max_len=24
  local bsb_sn_max_len=24
  local pad_len=0

  if [ $dump_mode -eq 1 ]; then
    [ -e "${sys_mfr_sysfs}" ] && echo "mfg: MFG_SYS_MFR=$(tr -d '\0' < ${sys_mfr_sysfs} 2>/dev/null)"
    [ -e "${sys_pdn_sysfs}" ] && echo "mfg: MFG_SYS_PDN=$(tr -d '\0' < ${sys_pdn_sysfs} 2>/dev/null)"
    [ -e "${sys_ver_sysfs}" ] && echo "mfg: MFG_SYS_VER=$(tr -d '\0' < ${sys_ver_sysfs} 2>/dev/null)"
    [ -e "${bsb_mfr_sysfs}" ] && echo "mfg: MFG_BSB_MFR=$(tr -d '\0' < ${bsb_mfr_sysfs} 2>/dev/null)"
    [ -e "${bsb_pdn_sysfs}" ] && echo "mfg: MFG_BSB_PDN=$(tr -d '\0' < ${bsb_pdn_sysfs} 2>/dev/null)"
    [ -e "${bsb_ver_sysfs}" ] && echo "mfg: MFG_BSB_VER=$(tr -d '\0' < ${bsb_ver_sysfs} 2>/dev/null)"
    [ -e "${bsb_sn_sysfs}" ] && echo "mfg: MFG_BSB_SN=$(tr -d '\0' < ${bsb_sn_sysfs} 2>/dev/null)"
    return
  fi

  if [ -z "${MFG_SYS_MFR}" ] && [ -z "${MFG_SYS_PDN}" ] && [ -z "${MFG_SYS_VER}" ] &&
     [ -z "${MFG_BSB_MFR}" ] && [ -z "${MFG_BSB_PDN}" ] && [ -z "${MFG_BSB_VER}" ] &&
     [ -z "${MFG_BSB_SN}" ] ; then
    log_msg "mfg: skip mfg extension since none of the following are specified:"
    log_msg "MFG_SYS_MFR, MFG_SYS_PDN, MFG_SYS_VER, MFG_BSB_MFR, MFG_BSB_PDN, MFG_BSB_VER, MFG_BSB_SN"
    return
  fi

  if [ -e ${tmp_file} ]; then
    rm -rf ${tmp_file}
  fi

  if [ -e ${MFGEXT_CAPSULE} ]; then
    rm -rf ${MFGEXT_CAPSULE}
  fi

  # Reserve MFG space for future use.
  add_trailing_null ${MFG_LEN} ${tmp_file}

  if [ -n "${MFG_SYS_MFR}" ]; then
    log_msg "mfg: SYS_MFR=${MFG_SYS_MFR}"

    if [ ${#MFG_SYS_MFR} -gt ${sys_mfr_max_len} ]; then
      log_msg "mfg: SYS_MFR exceeding max length ${sys_mfr_max_len}"
      return
    fi

    printf "${MFG_SYS_MFR}" >> ${tmp_file} 2>>${log_file}
    sys_mfr_err=$?

    if [ ${sys_mfr_err} -eq 0 ]; then
      log_msg "mfg: sys_mfr written"
    else
      log_msg "mfg: sys_mfr_err=${sys_mfr_err}"
      return
    fi
  fi
  pad_len=$((${sys_mfr_max_len}-${#MFG_SYS_MFR}))
  add_trailing_null ${pad_len} ${tmp_file}

  if [ -n "${MFG_SYS_PDN}" ]; then
    log_msg "mfg: SYS_PDN=${MFG_SYS_PDN}"

    if [ ${#MFG_SYS_PDN} -gt ${sys_pdn_max_len} ]; then
      log_msg "mfg: SYS_PDN exceeding max length ${sys_pdn_max_len}"
      return
    fi

    printf "${MFG_SYS_PDN}" >> ${tmp_file} 2>>${log_file}
    sys_pdn_err=$?

    if [ ${sys_pdn_err} -eq 0 ]; then
      log_msg "mfg: sys_pdn written"
    else
      log_msg "mfg: sys_pdn_err=${sys_pdn_err}"
      return
    fi
  fi
  pad_len=$((${sys_pdn_max_len}-${#MFG_SYS_PDN}))
  add_trailing_null ${pad_len} ${tmp_file}

  if [ -n "${MFG_SYS_VER}" ]; then
    log_msg "mfg: SYS_VER=${MFG_SYS_VER}"

    if [ ${#MFG_SYS_VER} -gt ${sys_ver_max_len} ]; then
      log_msg "mfg: SYS_VER exceeding max length ${sys_ver_max_len}"
      return
    fi

    printf "${MFG_SYS_VER}" >> ${tmp_file} 2>>${log_file}
    sys_ver_err=$?

    if [ ${sys_ver_err} -eq 0 ]; then
      log_msg "mfg: sys_ver written"
    else
      log_msg "mfg: sys_ver_err=${sys_ver_err}"
      return
    fi
  fi
  pad_len=$((${sys_ver_max_len}-${#MFG_SYS_VER}))
  add_trailing_null ${pad_len} ${tmp_file}

  if [ -n "${MFG_BSB_MFR}" ]; then
    log_msg "mfg: BSB_MFR=${MFG_BSB_MFR}"

    if [ ${#MFG_BSB_MFR} -gt ${bsb_mfr_max_len} ]; then
      log_msg "mfg: BSB_MFR exceeding max length ${bsb_mfr_max_len}"
      return
    fi

    printf "${MFG_BSB_MFR}" >> ${tmp_file} 2>>${log_file}
    bsb_mfr_err=$?

    if [ ${bsb_mfr_err} -eq 0 ]; then
      log_msg "mfg: bsb_mfr written"
    else
      log_msg "mfg: bsb_mfr_err=${bsb_mfr_err}"
      return
    fi
  fi
  pad_len=$((${bsb_mfr_max_len}-${#MFG_BSB_MFR}))
  add_trailing_null ${pad_len} ${tmp_file}

  if [ -n "${MFG_BSB_PDN}" ]; then
    log_msg "mfg: BSB_PDN=${MFG_BSB_PDN}"

    if [ ${#MFG_BSB_PDN} -gt ${bsb_pdn_max_len} ]; then
      log_msg "mfg: BSB_PDN exceeding max length ${bsb_pdn_max_len}"
      return
    fi

    printf "${MFG_BSB_PDN}" >> ${tmp_file} 2>>${log_file}
    bsb_pdn_err=$?

    if [ ${bsb_pdn_err} -eq 0 ]; then
      log_msg "mfg: bsb_pdn written"
    else
      log_msg "mfg: bsb_pdn_err=${bsb_pdn_err}"
      return
    fi
  fi
  pad_len=$((${bsb_pdn_max_len}-${#MFG_BSB_PDN}))
  add_trailing_null ${pad_len} ${tmp_file}

  if [ -n "${MFG_BSB_VER}" ]; then
    log_msg "mfg: BSB_VER=${MFG_BSB_VER}"

    if [ ${#MFG_BSB_VER} -gt ${bsb_ver_max_len} ]; then
      log_msg "mfg: BSB_VER exceeding max length ${bsb_ver_max_len}"
      return
    fi

    printf "${MFG_BSB_VER}" >> ${tmp_file} 2>>${log_file}
    bsb_ver_err=$?

    if [ ${bsb_ver_err} -eq 0 ]; then
      log_msg "mfg: bsb_ver written"
    else
      log_msg "mfg: bsb_ver_err=${bsb_ver_err}"
      return
    fi
  fi
  pad_len=$((${bsb_ver_max_len}-${#MFG_BSB_VER}))
  add_trailing_null ${pad_len} ${tmp_file}

  if [ -n "${MFG_BSB_SN}" ]; then
    log_msg "mfg: BSB_SN=${MFG_BSB_SN}"

    if [ ${#MFG_BSB_SN} -gt ${bsb_sn_max_len} ]; then
      log_msg "mfg: BSB_SN exceeding max length ${bsb_sn_max_len}"
      return
    fi

    printf "${MFG_BSB_SN}" >> ${tmp_file} 2>>${log_file}
    bsb_sn_err=$?

    if [ ${bsb_sn_err} -eq 0 ]; then
      log_msg "mfg: bsb_sn written"
    else
      log_msg "mfg: bsb_sn_err=${bsb_sn_err}"
      return
    fi
  fi
  pad_len=$((${bsb_sn_max_len}-${#MFG_BSB_SN}))
  add_trailing_null ${pad_len} ${tmp_file}

  log_msg "mfgext: bin file create at ${tmp_file}"
  $(${MKCAP} --cfg-data=${tmp_file} ${MFGEXT_CAPSULE})
  log_msg "mfgext: capsule file create at ${MFGEXT_CAPSULE}"
  (${BFREC} --capsule ${MFGEXT_CAPSULE})
}

icm_cfg()
{
  if [ $dump_mode -eq 1 ]; then
    [ -e "${large_icm_sysfs}" ] && echo "icm: LARGE_ICM_SIZE=$(cat ${large_icm_sysfs} 2>/dev/null)"
    return
  fi

  if [ -n "${LARGE_ICM_SIZE}" ] && [ -e "${large_icm_sysfs}" ]; then
    log_msg "icm: LARGE_ICM_SIZE=${LARGE_ICM_SIZE}"
    echo "${LARGE_ICM_SIZE}" > ${large_icm_sysfs} 2>>${log_file}
    large_icm_err=$?

    if [ ${large_icm_err} -eq 0 ]; then
      log_msg "icm: large_icm written"
    else
      log_msg "icm: large_icm_err=${large_icm_err}"
      return
    fi
  fi
}

#
# Set a value at the specified offset in a file.
#
sys_cfg_one_byte()
{
  local tmp_file=$1
  local name=$2
  local offset=$3
  local value=$4
  local bin_value

  if [ ${dump_mode} -eq 1 ]; then
    value=$(hexdump -s "${offset}" -n 1 -e '/1 "%d" "\n"' "${tmp_file}")
    echo "sys: ${name}=${value}"
  elif [ -n "${value}" ]; then
    bin_value=$(echo "${value}" | tr '[:lower:]' '[:upper:]')
    if [ ."$value" = ."TRUE" ]; then
      bin_value='\x01'
    else
      bin_value='\x00'
    fi
    printf "${bin_value}" | dd of="${tmp_file}" seek="${offset}" bs=1 count=1 conv=notrunc 2> /dev/null

    has_change=1
    log_msg "sys: ${name}=${value}"
  fi
}

#
# This function writes to the BfSysCfg UEFI variable directly.
# Below are the offsets defined in UEFI which is 4(fixed header) plus the offset
# of the variable within the BfSysCfg struct. These offsets are not supposed to
# change in order to be backward compatible with previous releases.
#   VARIABLE(Name)       OFFSET(Byte)  SIZE(Byte)
#   SYS_ENABLE_SMMU      24            1
#   SYS_DISABLE_SPMI     25            1
#   SYS_ENABLE_2ND_EMMC  26            1
#   SYS_BOOT_PROTECT     27            1
#   SYS_ENABLE_SPCR      32            1
#   SYS_DISABLE_PCIE     33            1
#   SYS_ENABLE_OPTEE     34            1
#   SYS_ENABLE_I2C0      36            1
#   SYS_DISABLE_FORCE_PXE_RETRY  37    1
#   SYS_ENABLE_BMC_FIELD_MODE    38    1
#
sys_cfg()
{
  local tmp_file=/tmp/.bfcfg-sysfs-data
  local sys_cfg_sysfs=${efivars}/BfSysCfg-9c759c02-e5a3-45e4-acfc-c34a500628a6

  if [ $dump_mode -eq 0 ] && [ ! -e "${sys_cfg_sysfs}" ]; then
    log_msg "sys: failed to find the EFI variable"
    return
  fi

  # shellcheck disable=SC2216
  yes | cp -f ${sys_cfg_sysfs} ${tmp_file}
  has_change=0

  sys_cfg_one_byte ${tmp_file} "ENABLE_SMMU" 24 "${SYS_ENABLE_SMMU}"
  sys_cfg_one_byte ${tmp_file} "DISABLE_SPMI" 25 "${SYS_DISABLE_SPMI}"
  sys_cfg_one_byte ${tmp_file} "ENABLE_2ND_EMMC" 26 "${SYS_ENABLE_2ND_EMMC}"
  sys_cfg_one_byte ${tmp_file} "BOOT_PROTECT" 27 "${SYS_BOOT_PROTECT}"
  sys_cfg_one_byte ${tmp_file} "ENABLE_SPCR" 32 "${SYS_ENABLE_SPCR}"
  sys_cfg_one_byte ${tmp_file} "DISABLE_PCIE" 33 "${SYS_DISABLE_PCIE}"
  sys_cfg_one_byte ${tmp_file} "ENABLE_OPTEE" 34 "${SYS_ENABLE_OPTEE}"
  sys_cfg_one_byte ${tmp_file} "ENABLE_I2C0" 36 "${SYS_ENABLE_I2C0}"
  sys_cfg_one_byte ${tmp_file} "DISABLE_FORCE_PXE_RETRY" 37 "${SYS_DISABLE_FORCE_PXE_RETRY}"
  sys_cfg_one_byte ${tmp_file} "ENABLE_BMC_FIELD_MODE" 38 "${SYS_ENABLE_BMC_FIELD_MODE}"

  if [ ${has_change} -eq 1 ]; then
    chattr -i ${sys_cfg_sysfs}
    cp ${tmp_file} ${sys_cfg_sysfs}
    sync
  fi

  rm -f ${tmp_file}
}

misc_cfg()
{
  local mac value
  local tmp_file=/tmp/.bfcfg-misc-data

  if [ $dump_mode -eq 1 ]; then
    # Rshim MAC address.
    mac=$(hexdump -v -e '/1 "%02x"' ${rshim_efi_mac_sysfs})
    mac="${mac:8:2}:${mac:10:2}:${mac:12:2}:${mac:14:2}:${mac:16:2}:${mac:18:2}"
    echo "misc: NET_RSHIM_MAC=${mac}"

    # PXE DHCP Class Identifier.
    value=""
    if [ -f "${pxe_dhcp_class_id_sysfs}" ]; then
      value=$(xxd -s 4 -p ${pxe_dhcp_class_id_sysfs} 2>/dev/null | xxd -r -p)
    fi
    echo "misc: PXE_DHCP_CLASS_ID=${value}"
  else
    # Rshim MAC address.
    if [ -n "${NET_RSHIM_MAC}" ]; then
      mac="${NET_RSHIM_MAC//:/\\x}"
      mac="\\x07\\x00\\x00\\x00\\x${mac}"
      printf "${mac}" > ${tmp_file}
      chattr -i ${rshim_efi_mac_sysfs}
      cp ${tmp_file} ${rshim_efi_mac_sysfs}
      rm -f ${tmp_file}
      log_msg "misc: NET_RSHIM_MAC=${NET_RSHIM_MAC}"
    fi

    # PXE DHCP Class Identifier.
    if [ -n "${PXE_DHCP_CLASS_ID}" ]; then
      value="\\x07\\x00\\x00\\x00${PXE_DHCP_CLASS_ID}"
      printf "${value}" > ${tmp_file}
      if [ -f "${pxe_dhcp_class_id_sysfs}" ]; then
        chattr -i ${pxe_dhcp_class_id_sysfs}
      fi
      cp ${tmp_file} ${pxe_dhcp_class_id_sysfs}
      rm -f ${tmp_file}
      log_msg "misc: PXE_DHCP_CLASS_ID=${PXE_DHCP_CLASS_ID}"
    fi
  fi
}

#
# Parse the partition configuration and export $EFI_PART, $ROOT_PART and
# $PERSIST_PART
#
part_info()
{
  local disk part value

  for disk in {0..15}; do
    eval "disk_name=\${DISK${disk}_NAME}"
    # shellcheck disable=SC2154
    [ -z "${disk_name}" ] && continue

    for part in {0..15}; do
      eval "value=\${DISK${disk}_PART${part}_MOUNT}"
      if [ ."${value}" = ."/" ]; then
        echo ROOT_PART="${disk_name}p${part}"
      fi

      eval "value=\${DISK${disk}_PART${part}_TYPE}"
      if [ ."${value}" = ."EFI" ]; then
        echo EFI_PART="${disk_name}p${part}"
      fi

      eval "value=\${DISK${disk}_PART${part}_PERSIST}"
      if [ -n "${value}" ]; then
        echo PERSIST_PART="${disk_name}p${part}"
      fi
    done
  done
}

#
# Add header into a boot entry
# $1: output file
#
boot_cfg_add_header()
{
  printf "\\x07\\x00\\x00\\x00\\x01\\x00\\x00\\x00" >> "$1"
}

#
# Add length into a boot entry
# $1: output file
# $2: length
#
boot_cfg_add_len()
{
  len=$2
  len=$(printf '%04x' "${len}")
  printf "\\x${len:2:2}\\x${len:0:2}" >> "$1"
}

#
# Add http into a boot entry
# $1: output file
#
boot_cfg_add_http()
{
  printf "\\x03\\x18\\x04\\x00" >> "$1"
}

#
# Add tail into a boot entry
# $1: output file
#
boot_cfg_add_tail()
{
  printf "\\x7f\\xff\\x04\\x00" >> "$1"
}

#
# Add name into a boot entry
# $1: output file
# $2: name
#
boot_cfg_add_name()
{
  local idx name=$2

  for ((idx=0; idx<${#name}; idx++)); do
    printf "${name:$idx:1}" >> "$1"
    printf "\\x00" >> "$1"
  done
  printf "\\x00\\x00" >> "$1"
}

#
# Add PCIe info into a boot entry
# $1: output file
# $2: port name
#
# Note: The quoting here is intentional, spaces need to be kept out
# shellcheck disable=SC2140
boot_cfg_add_pcie()
{
  local idx address dev_id func_id dev_path cnt

  # Get PCI path
  dev_path=$(lspci -t | head -n 1 | grep -o "[0-9][0-9]\.[0-9]*")
  if [ -z "${dev_path}" ]; then
    ${ECHO} "Failed to find device path"
    return
  fi

  # PciRoot
  printf "\\x02\\x01\\x0c\\x00\\xd0\\x41\\x03\\x0a\\x00\\x00\\x00\\x00" >> "$1"

  idx=1
  cnt=$(echo ${dev_path} | wc -w)
  for address in ${dev_path}; do
    # Type(1B)/SubType(1B)/Len(2B)
    printf "\\x01\\x01\\x06\\x00" >> "$1"

    # Get DevId and FuncId
    dev_id=$(echo ${address} | tr '.' ' ' | awk '{print $1}')
    func_id=$(echo ${address} | tr '.' ' ' | awk '{print $2}')

    # Adjust FuncId for P1
    if [ "$idx" -eq ${cnt} -a "$2" = "NIC_P1" ]; then
      func_id=$((func_id + 1))
    fi

    printf "\\x${func_id}\\x${dev_id}" >> "$1"
    idx=$((idx + 1))
  done
}

#
# Add Eth/MAC info into a boot entry
# $1: output file
# $2: MAC address (xx:xx:xx:xx:xx:xx)
#
boot_cfg_add_eth()
{
  local idx mac=$2

  printf "\\x03\\x0b\\x25\\x00" >> "$1"
  mac="\\x${mac//:/\\x}"
  printf "${mac}" >> "$1"
  for idx in {1..26}; do
    printf "\\x00" >> "$1"
  done
  printf "\\x01" >> "$1"
}

#
# Add VLAN info into a boot entry
# $1: output file
# $2: decimal VLAN id
#
boot_cfg_add_vlan()
{
  local vlan_id=$2

  vlan_id=$(printf '%04x' "${vlan_id}")
  printf "\\x03\\x14\\x06\\x00\\x${vlan_id:2:2}\\x${vlan_id:0:2}" >> "$1"
}

#
# Add IPv4 info into a boot entry
# $1: output file
#
boot_cfg_add_ipv4()
{
  local idx

  printf "\\x03\\x0c\\x1b\\x00" >> "$1"
  for idx in {1..23}; do
    printf "\\x00" >> "$1"
  done
}

#
# Add IPv6 info into a boot entry
# $1: output file
#
boot_cfg_add_ipv6()
{
  local idx

  printf "\\x03\\x0d\\x3c\\x00" >> "$1"
  for idx in {1..39}; do
    printf "\\x00" >> "$1"
  done
  printf "\\x40" >> "$1"
  for idx in {1..16}; do
    printf "\\x00" >> "$1"
  done
}

get_hca_p0_mac()
{
  local dev devmac devid p0mac devmac_oui

  base_mac=$(bfhcafw flint q 2>/dev/null | grep "^Base MAC" | awk '{print $3}')
  base_mac=$(echo $base_mac | cut -c1-6)
  p0mac="feffffffffff"
  for dev in /sys/class/net/*; do
    [ ! -f ${dev}/device/device ] && continue
    devid=$(cat ${dev}/device/device)
    [ ."${devid}" != ."0xa2d2" -a ."${devid}" != ."0xa2d6" -a ."${devid}" != ."0xa2dc" ] && continue
    devmac=$(cat ${dev}/address | sed 's/://g' | tr '[:upper:]' '[:lower:]')
    devmac_oui=$(echo ${devmac} | cut -c1-6)
    [ ."${base_mac}" != ."$devmac_oui" ] && continue
    if [ "${devmac}" \< "${p0mac}" ]; then
      p0mac=${devmac}
    fi
  done
  echo "0x${p0mac}"
}

#
# Boot Entry configuration
# Each entry BOOT<N> could have the following format:
#   PXE:
#     BOOT<N> = NET-<NIC_P0 | NIC_P1 | OOB | RSHIM>[.<vlan-id>]-<IPV4 | IPV6>
#   UEFI Shell:
#     BOOT<N> = UEFI_SHELL
#   DISK: boot entries created during OS installation.
#     BOOT<N> = DISK
# Example:
#   BOOT0 = NET-NIC_P1-IPV4
#   BOOT1 = DISK
#
boot_cfg()
{
  local i tmp idx entry ifname proto mac vlan vlan_len disk_entry_idx
  local shell_entry disk_entries boot_order
  local tmp_dir=/tmp/.boot_cfg
  local tmp_file=${tmp_dir}/boot
  local tmp_vlan_file=${tmp_dir}/vlan
  local value tmp_entry old_boot_order
  local l4proto l4proto_len

  [ $dump_mode -eq 1 ] && return

  rm -rf ${tmp_dir} 2>/dev/null
  mkdir -p ${tmp_dir}

  old_boot_order=$(efibootmgr 2>/dev/null | grep BootOrder | awk '{print $2}' | tr ',' ' ')

  # Check whether to preserve booting entries from disk.
  for i in {0..32}; do
    eval "entry=\${BOOT${i}}"
    entry=$(echo "${entry}" | tr '[:lower:]' '[:upper:]')
    [ -n "${entry}" ] && tmp=${entry}

    if [ "${entry}" = "DISK" ]; then
      for tmp_entry in ${old_boot_order}; do
        value=$(efibootmgr -v 2>/dev/null | grep "^Boot${tmp_entry}\*" | grep -w HD)
        if [ -n "${value}" ]; then
          disk_entries="${disk_entries} ${tmp_entry}"
        fi
      done
      break
    elif [ "${entry}" = "UEFI_SHELL" ]; then
      # Save the UEFI shell option
      shell_entry=$(efibootmgr 2>/dev/null | grep "EFI Internal Shell" | cut -c5-8)
      cp -f ${efivars}/Boot"${shell_entry}"* ${tmp_dir}/shell
    fi
  done

  # Don't continue if nothing configured.
  [ -z "${tmp}" ] && return

  # Save disk entries
  for i in ${disk_entries}; do
    cp ${efivars}/Boot"${i}"* ${tmp_dir}/
  done

  # Remove all existing entries
  rm -f ${efivars}/Boot00*

  # Scan it again to add boot entries
  idx=0
  for i in {0..32}; do
    eval "entry=\${BOOT${i}}"
    [ -z "${entry}" ] && continue
    rm -f ${tmp_file} 2>/dev/null
    entry=$(echo "${entry}" | tr '[:lower:]' '[:upper:]')

    # BOOT<N> = DISK
    if [ ."${entry}" = ."DISK" ]; then
      disk_entry_idx=64
      for j in $disk_entries; do
        tmp=$(printf '%04x' ${disk_entry_idx})
        cp "${tmp_dir}/Boot${j}-${efi_global_var_guid}" "${efivars}/Boot${tmp}-${efi_global_var_guid}"
        [ -n "${boot_order}" ] && boot_order="${boot_order},"
        boot_order="${boot_order}${tmp}"
        disk_entry_idx=$((disk_entry_idx + 1))
      done
      continue
    fi

    # BOOT<N> = UEFI_SHELL
    if [ ."${entry}" = ."UEFI_SHELL" ]; then
      if [ ! -e "${tmp_dir}/shell" ]; then
        log_msg "boot: UEFI shell entry not found"
        continue
      fi
      cp -f ${tmp_dir}/shell ${tmp_file}
    else
      # BOOT<N> = NET-<NIC_P0 | NIC_P1 | OOB | RSHIM>[.<vlan-id>]-<IPV4 | IPV6>[-HTTP]
      ifname=$(echo "${entry}" | cut -d '-' -f 2)
      proto=$(echo "${entry}" | cut -d '-' -f 3)
      vlan=""
      vlan_len=0
      l4proto=$(echo "${entry}" | cut -d '-' -f 4)
      l4proto_len=0
      if [[ "${ifname}" = *"."* ]]; then
        vlan=$(echo "${ifname}" | cut -d '.' -f 2)
        ifname=$(echo "${ifname}" | cut -d '.' -f 1)
        vlan_len=6
      fi
      if [ -z "${ifname}" ] || [ -z "${proto}" ]; then
        log_msg "boot: invalid format ${entry}"
        continue
      fi
      if [ "${proto}" != "IPV4" ] && [ "${proto}" != "IPV6" ]; then
        log_msg "boot: invalid format ${entry}, need IPV4 or IPV6"
        continue
      fi

      if [ "${l4proto}" = "HTTP" ]; then
        l4proto_len=4
      fi

      boot_cfg_add_header "${tmp_file}"

      case "${ifname}" in
      OOB)
        mac=$(cat ${oob_mac_sysfs} 2>/dev/null)
        if [ -z "${mac}" ]; then
          log_msg "boot: failed to get MAC for ${entry}"
          continue
        fi
        if [ "${proto}" = "IPV4" ]; then
          boot_cfg_add_len "${tmp_file}" $((68 + vlan_len + l4proto_len))
        else
          boot_cfg_add_len "${tmp_file}" $((101 + vlan_len + l4proto_len))
        fi
        boot_cfg_add_name "${tmp_file}" "${entry}"
        ;;

      RSHIM)
        mac=$(hexdump -v -e '/1 " %02x"' ${rshim_efi_mac_sysfs} 2>/dev/null)
        if [ -z "${mac}" ]; then
          log_msg "boot: failed to get MAC for ${entry}"
          continue
        fi
        mac="${mac// /:}"
        mac=${mac: -17}
        if [ "${proto}" = "IPV4" ]; then
          boot_cfg_add_len "${tmp_file}" $((68 + vlan_len + l4proto_len))
        else
          boot_cfg_add_len "${tmp_file}" $((101 + vlan_len + l4proto_len))
        fi
        boot_cfg_add_name "${tmp_file}" "${entry}"
        ;;

      NIC_P0|NIC_P1)
        mac=$(get_hca_p0_mac)
        if [ -z "${mac}" ] || [ ."${mac}" = ."N/A" ]; then
          log_msg "boot: failed to get MAC for ${entry}"
          continue
        fi
        if [ "${ifname}" = "NIC_P1" ]; then
          mac=$((mac + 1))
        fi
        mac=$(printf '%012x' ${mac})
        # shellcheck disable=SC2116,SC2096,SC2086
        mac=$(echo ${mac:0:2}:${mac:2:2}:${mac:4:2}:${mac:6:2}:${mac:8:2}:${mac:10:2})

        if [ "${proto}" = "IPV4" ]; then
          boot_cfg_add_len "${tmp_file}" $((104 + vlan_len + l4proto_len))
        else
          boot_cfg_add_len "${tmp_file}" $((137 + vlan_len + l4proto_len))
        fi
        boot_cfg_add_name ${tmp_file} "${entry}"
        boot_cfg_add_pcie "${tmp_file}" "${ifname}"
        ;;

      *)
        continue
        ;;
      esac

      boot_cfg_add_eth "${tmp_file}" "${mac}"

      # Remove old VLAN configuration
      mac=$(echo "${mac}" | tr '[:lower:]' '[:upper:]')
      mac="${mac//:/}"
      eval "tmp=\${${ifname}_VLAN_SET}"
      if [ -z "${tmp}" ]; then
        eval "${ifname}_VLAN_SET=1"
        chattr -i "${efivars}/${mac}-${efi_vlan_var_guid}" 2>/dev/null
        rm -f "${efivars}/${mac}-${efi_vlan_var_guid}" 2>/dev/null
      fi
      # Add new VLAN if specified
      if [ -n "${vlan}" ]; then
        tmp=$(printf '%04x' "${vlan}")
        if [ ! -e "${efivars}/${mac}-${efi_vlan_var_guid}" ]; then
          printf "\\x07\\x00\\x00\\x00\\x${tmp:2:2}\\x${tmp:0:2}" > \
            "${efivars}/${mac}-${efi_vlan_var_guid}"
        else
          chattr -i "${efivars}/${mac}-${efi_vlan_var_guid}"
          cp "${efivars}/${mac}-${efi_vlan_var_guid}" ${tmp_vlan_file}
          printf "\\x${tmp:2:2}\\x${tmp:0:2}" >> ${tmp_vlan_file}
          cp ${tmp_vlan_file} "${efivars}/${mac}-${efi_vlan_var_guid}"
        fi
        boot_cfg_add_vlan "${tmp_file}" "${vlan}"
      fi

      if [ "${proto}" = "IPV4" ]; then
        boot_cfg_add_ipv4 "${tmp_file}"
      else
        boot_cfg_add_ipv6 "${tmp_file}"
      fi

      if [ "${l4proto}" = "HTTP" ]; then
        boot_cfg_add_http "${tmp_file}"
      fi

      boot_cfg_add_tail "${tmp_file}"
    fi

    if [ -e "${tmp_file}" ]; then
      tmp=$(printf '%04x' ${idx})
      cp ${tmp_file} "${efivars}/Boot${tmp}-${efi_global_var_guid}"
      log_msg "boot: add Boot${tmp}(${entry})"
      [ -n "${boot_order}" ] && boot_order="${boot_order},"
      boot_order="${boot_order}${tmp}"
      idx=$((idx + 1))
    fi
  done

  efibootmgr -o "${boot_order}" >/dev/null
  log_msg "boot: set boot order ${boot_order}"
  rm -rf ${tmp_dir} 2>/dev/null
}

usage()
{
  echo "syntax: bfcfg [--help|-h] [--dump|-d] [--part-info|-p]"
}

# Source the configuration if exists.
# shellcheck source=/dev/null
[ -e "${cfg_file}" ] && . ${cfg_file}

# Parse the arguments.
options=$(getopt -n bfcfg -o dhp -l dump,help,part-info -- "$@")
eval set -- "$options"
while [ "$1" != -- ]; do
  case $1 in
    --dump|-d) dump_mode=1 ;;
    --help|-h) usage; exit 0 ;;
    --part-info|-p) part_info; exit 0 ;;
  esac
  shift
done
shift

# Start a new log file.
rm -f ${log_file} >/dev/null
log_msg "bfcfg (ver ${bfcfg_version})"
log_msg "$(date)"
log_msg

# Mount the efi variables.
test "$(ls -A ${efivars})" || mount -t efivarfs none ${efivars}

icm_cfg
mfg_cfg
mfg_ext_cfg
sys_cfg
misc_cfg
boot_cfg
sync
