#!/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

${BFDBG}

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

mlxmkcap=/lib/firmware/mellanox/boot/capsule/scripts/mlx-mkcap

bfcf_acpi_table=/sys/firmware/acpi/tables/BFCF
efivars=/sys/firmware/efi/efivars
sys_cfg_sysfs=${efivars}/BfSysCfg-9c759c02-e5a3-45e4-acfc-c34a500628a6
redfish_cfg_sysfs=${efivars}/BfRedfish-ce3e5882-6770-4d5e-aa45-90f909da2446
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}
}

delete_efi_var()
{
  local in_efivarfs=$1

  [ ${dump_mode} -eq 1 ] && return

  if [ -e "${in_efivarfs}" ]; then
    log_msg "mfg: delete ${in_efivarfs}"
    chattr -i ${in_efivarfs}
    rm -f ${in_efivarfs}
  fi
}

mfg_reset_deps()
{
  # Cleanup configuration dependencies if needed
  if [ ${has_sys_cfg} -eq 0 ]; then
    # Delete SYS Config data
    delete_efi_var "${sys_cfg_sysfs}"
  fi
  if [ ${has_redfish_cfg} -eq 0 ]; then
    # Delete RDF Config
    delete_efi_var "${redfish_cfg_sysfs}"
  fi
}

mfg_sysfs_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

  [ $dump_mode -eq 1 ] && return

  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
  mfg_sysfs_cfg_done=1
  mfg_reset_deps
}

mfg_cfg_set_bytes()
{
  local out_file=$1
  local name=$2
  local offset=$3
  local size=$4
  local value=$5

  [ ${dump_mode} -eq 1 ] && return

  if [ -n "${value}" ] && [ -f "${out_file}" ]; then
    # Most of the fields are of type strings, thus check the number
    # of characters per string. Only exception for the OOB MAC.
    # The OOB MAC is stored in hex format; e.g., \xAA\xBB\xCC\xDD\xEE\xFF,
    # total of 24 charcters
    if [ ${#value} -gt ${size} ] && [ "${name}" != "MFG_OOB_MAC" ]; then
        log_msg "mfg: ${name} exceeding max length ${size}"
        return
    elif [ ${#value} -gt 24 ] && [ "${name}" == "MFG_OOB_MAC" ]; then
        log_msg "mfg: MFG_OOB_MAC exceeding max length 6 bytes"
        return
    else
      printf "${value}" | dd of="${out_file}" seek="${offset}" bs=1 count=${size} conv=notrunc 2> /dev/null
      err=$?
      if [ ${err} -eq 0 ]; then
        log_msg "mfg: ${name} written, value=${value}"
      else
        log_msg "mfg: modl_err=${err}"
        return
      fi
      has_cfg=1
    fi
  fi
}

copy_efi_var_data()
{
  local out_file=$1
  local in_efivarfs=$2
  local offset=$3
  local size=$4

  [ ${dump_mode} -eq 1 ] && return

  if [ -e "${in_efivarfs}" ] && [ -f "${out_file}" ]; then
    chattr -i ${in_efivarfs}
    # Copy the EFI variable data, skip variable header (4 bytes)
    dd if="${in_efivarfs}" of="${out_file}" seek="${offset}" skip=4 bs=1 count=${size} conv=notrunc 2> /dev/null
    err=$?
    if [ ${err} -eq 0 ]; then
      log_msg "mfg: ${in_efivarfs} data written to ${out_file}"
    else
      log_msg "mfg: failed to write ${in_efivarfs} data to ${out_file}"
      log_msg "mfg: modl_err=${err}"
      return
    fi
  fi
}

mfg_cfg_set_bf_modes()
{
  local out_file=$1
  local name=$2
  local offset=$3
  local size=$4
  local value=$5
  local bin_value

  [ ${dump_mode} -eq 1 ] && return

  if [ -z "${value}" ] || [ ! -f "${out_file}" ]; then
    return
  fi

  #
  # This must be consistent with the UEFI PMI library
  # implementation.
  #
  if [ "${name}" == "MFG_PLAT_MODE" ]; then
    if [ "${value^^}" == "DPU" ]; then
      bin_value='\x01'
    elif [ "${value^^}" == "NIC" ]; then
      bin_value='\x02'
    else
      log_msg "mfg: ${name}, unsupported value ${value}"
      return
    fi
    printf "${bin_value}" | dd of="${out_file}" seek="${offset}" bs=1 count=${size} conv=notrunc 2> /dev/null
    err=$?
    if [ ${err} -eq 0 ]; then
      log_msg "mfg: ${name}=${value}"
    else
      log_msg "mfg: modl_err=${err}"
      return
    fi
    has_cfg=1
  else
    log_msg "mfg: ${name} is not supported"
    return
  fi
}

mfg_cfg_to_bin()
{
  local bin_file=$1

  [ -f "${bin_file}" ] && rm -f ${bin_file}
  # Create empty MFG blob; it contains MFG data and MFG extended data
  # and occupies 16 pages of 256 bytes within the EEPROM
  #
  # Layout:
  #   MFG Data      (off:   0, len: 256 bytes)
  #   MFG Extension (off: 256, len:2048 bytes)
  #   SYS Config    (off:2304, len:  64 bytes)
  #   RDF Config    (off:2368, len:  32 bytes)
  #   BF Modes      (off:2400, len:   4 bytes)
  #   Reserved      (off:2404, len:1692 bytes)
  #
  dd if=/dev/zero of=${bin_file} count=16 bs=256
  has_cfg=0

  [ ! -f "${bin_file}" ] && return

  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: bin: 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"

  elif [ ${mfg_sysfs_cfg_done} -eq 0 ]; then
    #
    # MFG Layout (1x 256B)
    #
    # OOB_MAC (off:  0, len: 8 bytes)
    # OPN     (off:  8, len:24 bytes)
    # SKU     (off: 32, len:24 bytes)
    # MODL    (off: 56, len:24 bytes)
    # SN      (off: 80, len:24 bytes)
    # UUID    (off:104, len:40 bytes)
    # REV     (off:144, len: 8 bytes)

    mfg_cfg_set_bytes ${bin_file} "MFG_OOB_MAC" 0 8 "\\x${MFG_OOB_MAC//:/\\x}"
    mfg_cfg_set_bytes ${bin_file} "MFG_OPN" 8 24 "${MFG_OPN}"
    mfg_cfg_set_bytes ${bin_file} "MFG_SKU" 32 24 "${MFG_SKU}"
    mfg_cfg_set_bytes ${bin_file} "MFG_MODL" 56 24 "${MFG_MODL}" 
    mfg_cfg_set_bytes ${bin_file} "MFG_SN" 80 24 "${MFG_SN}"
    mfg_cfg_set_bytes ${bin_file} "MFG_UUID" 104 80 "${MFG_UUID}"
    mfg_cfg_set_bytes ${bin_file} "MFG_REV" 144 8 "${MFG_REV}"

    # Append MFG configuration dependenices, if needed.
    if [ ${has_cfg} -eq 1 ]; then
      if [ ${has_sys_cfg} -eq 1 ]; then
        # Copy SYS Config data
        copy_efi_var_data ${bin_file} "${sys_cfg_sysfs}" 2304 64
      fi
      if [ ${has_redfish_cfg} -eq 1 ]; then
        # Copy RDF Config
        copy_efi_var_data ${bin_file} "${redfish_cfg_sysfs}" 2368 32
      fi
    fi

  elif [ ${mfg_sysfs_cfg_done} -eq 1 ]; then
    log_msg "mfg: bin: skip mfg since configured via sysfs"
  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: bin: 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"
  else
    #
    # MFG Extension Layout (8x 256B)
    #
    # SYS_MFR (off:  0 + 256, len:24 bytes)
    # SYS_PDN (off: 24 + 256, len:24 bytes)
    # SYS_VER (off: 48 + 256, len:512 bytes)
    # BSB_MFR (off:560 + 256, len:24 bytes)
    # BSB_PDN (off:584 + 256, len:24 bytes)
    # BSB_VER (off:608 + 256, len:24 bytes)
    # BSB_SN  (off:632 + 256, len:24 bytes)

    mfg_cfg_set_bytes ${bin_file} "MFG_SYS_MFR" 256 24 "${MFG_SYS_MFR}"
    mfg_cfg_set_bytes ${bin_file} "MFG_SYS_PDN" 280 24 "${MFG_SYS_PDN}"
    mfg_cfg_set_bytes ${bin_file} "MFG_SYS_VER" 304 512 "${MFG_SYS_VER}"
    mfg_cfg_set_bytes ${bin_file} "MFG_BSB_MFR" 816 24 "${MFG_BSB_MFR}" 
    mfg_cfg_set_bytes ${bin_file} "MFG_BSB_PDN" 840 24 "${MFG_BSB_PDN}"
    mfg_cfg_set_bytes ${bin_file} "MFG_BSB_VER" 864 24 "${MFG_BSB_VER}"
    mfg_cfg_set_bytes ${bin_file} "MFG_BSB_SN" 888 24 "${MFG_BSB_SN}"
  fi

  if [ -n "${MFG_PLAT_MODE}" ]; then
    #
    # BF Modes (4 Bytes)
    #
    # SYS_IN_CPU_MODEL   (off: 2400, len:1 byte)
    # MFG_HOST_PRIV_MODE (off: 2401, len:1 byte)
    # MFG_PLAT_MODE      (off: 2402, len:1 byte)
    #
    # For now, support platform mode only. If needed enhance it
    # to support 'Internal CPU Model' and 'Host Privilege Level'.
    mfg_cfg_set_bf_modes ${bin_file} "MFG_PLAT_MODE" 2402 1 "${MFG_PLAT_MODE}"
  fi

  if [ ${has_cfg} -eq 0 ]; then
    log_msg "mfg: bin: configuration unspecified or incomplete. Skip binary generation."
    rm -f ${bin_file}
    return
  else
    log_msg "mfg: bin: configuration written to ${bin_file}"
  fi
}

mfg_cfg_bin_to_cap()
{
  local bin_file=$1
  local cap_file=$2

  mfg_cfg_to_bin ${bin_file}
  has_bin=0

  [ -f "${bin_file}" ] && has_bin=1

  if [ ${has_bin} -eq 0 ]; then
    log_msg "mfg: capsule: configuration blob not found. Skip capsule generation"
    return
  fi

  if command -v python >/dev/null ; then
    ${mlxmkcap} --cfg-data ${bin_file} ${cap_file}
    if [ $? -eq 0 ]; then
      log_msg "mfg: capsule: file created at ${cap_file}"
    else
      log_msg "mfg: capsule: failed to create capsule file"
      return
    fi

  else
    # BFB might be missing commands and modules needed to generate
    # the EFI capsule, thus push the bin file content to the payload
    # of the pregenerated capsule file.
    dummy_cap=/lib/firmware/mellanox/boot/capsule/.efi_bfcfg.cap
    bin_size=$(stat -c%s ${bin_file})
    if [ ! -f "${dummy_cap}" ]; then
      log_msg "mfg: capsule: missing artifacts. Skip capsule generation"
      return
    fi
    cp -f ${dummy_cap} ${cap_file}
    # Skip the EFI capsule header; the header remains unchanged.
    # Re-write the payload starting from offset A8 (168).
    dd if="${bin_file}" of="${cap_file}" seek=168 bs=1 count=${bin_size} conv=notrunc 2> /dev/null
    if [ $? -eq 0 ]; then
      log_msg "mfg: capsule: payload updated"
      log_msg "mfg: capsule: file created at ${cap_file}"
    else
      log_msg "mfg: capsule: failed to update capsule payload"
      rm -f ${cap_file}
      return
    fi
  fi
}

mfg_cap_cfg()
{
  local bin_file=/tmp/.bfcfg-mfg.bin
  local cap_file=/tmp/.bfcfg-mfg.cap

  [ $dump_mode -eq 1 ] && return

  mfg_cfg_bin_to_cap ${bin_file} ${cap_file}
  has_cap=0

  [ -f "${cap_file}" ] && has_cap=1

  if [ ${has_cap} -eq 0 ]; then
    log_msg "mfg: update: capsule file not found. Skip capsule update."
    return
  fi

  #
  # MFG capsule must be applied first, before secure boot capsule
  # otherwise the signature verification would fail.
  #
  # Note that capsules are sorted by name and processed in order.
  # Thus it is important to get the MFG capsule executed first and
  # the capsule file name starts with '.'; it is not expected that
  # the remaining capsule files generated outside of bfcfg command
  # would have the '.' prefix in the filename.
  #

  bfrec --capsule ${cap_file} 2>&1 > /dev/null
  if [ $? -eq 0 ]; then
    log_msg "mfg: update: MFG will be updated via capsule"
  else
    log_msg "mfg: update: failed to update MFG via capsule"
  fi

  rm -f ${bin_file} ${cap_file}
}

mfg_cfg()
{
  # Update the MFG fields with corresponding EFI variables
  local oob_mac=${efivars}/BfCfgOobMac-${efi_bfcfg_var_guid}
  local opn_sysfs=${efivars}/BfCfgOpn-${efi_bfcfg_var_guid}
  local sku_sysfs=${efivars}/BfCfgSku-${efi_bfcfg_var_guid}
  local modl_sysfs=${efivars}/BfCfgModl-${efi_bfcfg_var_guid}
  local sn_sysfs=${efivars}/BfCfgSn-${efi_bfcfg_var_guid}
  local uuid_sysfs=${efivars}/BfCfgUuid-${efi_bfcfg_var_guid}
  local rev_sysfs=${efivars}/BfCfgRev-${efi_bfcfg_var_guid}

  # The efi variables have a 4 byte attribute header we won't display.
  # If we fetch the data from the MFG, that header does not exist.
  local skip_bytes_mfg=4
  local skip_bytes_ext_mfg=4

  if [ -z "${oob_mac}" ] || [ -z "${opn_sysfs}" ] || [ -z "${sku_sysfs}" ] ||
     [ -z "${modl_sysfs}" ] || [ -z "${sn_sysfs}" ] || [ -z "${uuid_sysfs}" ] ||
     [ -z "${rev_sysfs}" ] ; then
    #
    # One or more EFI variables representing MFG fields are empty
    # Use the sysfs version of MFG fields
    #
    oob_mac=${mfg_sysfs_dir}/oob_mac
    opn_sysfs=${mfg_sysfs_dir}/opn
    sku_sysfs=${mfg_sysfs_dir}/sku
    modl_sysfs=${mfg_sysfs_dir}/modl
    sn_sysfs=${mfg_sysfs_dir}/sn
    uuid_sysfs=${mfg_sysfs_dir}/uuid
    rev_sysfs=${mfg_sysfs_dir}/rev
    skip_bytes_mfg=0
  fi

  # Use the EFI variable for all extended MFG fields
  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}

  if [ $dump_mode -eq 1 ]; then
    [ -e "${oob_mac}" ] && echo "mfg: MFG_OOB_MAC=$(cat ${oob_mac} 2>/dev/null | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${opn_sysfs}" ] && echo "mfg: MFG_OPN=$(cat ${opn_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${sku_sysfs}" ] && echo "mfg: MFG_SKU=$(cat ${sku_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${modl_sysfs}" ] && echo "mfg: MFG_MODL=$(cat ${modl_sysfs} 2>/dev/null  | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${sn_sysfs}" ] && echo "mfg: MFG_SN=$(cat ${sn_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${uuid_sysfs}" ] && echo "mfg: MFG_UUID=$(cat ${uuid_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${rev_sysfs}" ] && echo "mfg: MFG_REV=$(cat ${rev_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_mfg\}//")"
    [ -e "${sys_mfr_sysfs}" ] && echo "mfg: MFG_SYS_MFR=$(cat ${sys_mfr_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    [ -e "${sys_pdn_sysfs}" ] && echo "mfg: MFG_SYS_PDN=$(cat ${sys_pdn_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    [ -e "${sys_ver_sysfs}" ] && echo "mfg: MFG_SYS_VER=$(cat ${sys_ver_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    [ -e "${bsb_mfr_sysfs}" ] && echo "mfg: MFG_BSB_MFR=$(cat ${bsb_mfr_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    [ -e "${bsb_pdn_sysfs}" ] && echo "mfg: MFG_BSB_PDN=$(cat ${bsb_pdn_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    [ -e "${bsb_ver_sysfs}" ] && echo "mfg: MFG_BSB_VER=$(cat ${bsb_ver_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    [ -e "${bsb_sn_sysfs}" ] && echo "mfg: MFG_BSB_SN=$(cat ${bsb_sn_sysfs} 2>/dev/null | sed "s/^.\{$skip_bytes_ext_mfg\}//")"
    return
  fi

  mfg_sysfs_cfg_done=0
  mfg_sysfs_cfg
  mfg_cap_cfg
}

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=0
    if [ -e ${tmp_file} ]; then
      value=$(hexdump -s "${offset}" -n 1 -e '/1 "%d" "\n"' "${tmp_file}")
    fi
    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

  has_sys_cfg=0

  if [ ! -e "${sys_cfg_sysfs}" ]; then
    log_msg "sys: failed to find the ${sys_cfg_sysfs} 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
    has_sys_cfg=1 
  fi

  rm -f ${tmp_file}
}

#
# This function writes to the BfRedfish UEFI variable directly.
# Below are the offsets defined in UEFI which is 4(fixed header) plus the offset
# of the variable within the BfRedfish struct. These offsets are not supposed to
# change in order to be backward compatible with previous releases.
#   VARIABLE(Name)       OFFSET(Byte)  SIZE(Byte)
#   CFG_ENABLE_REDFISH      4            1
#   CFG_RTCSYNC             5            1
#
sys_redfish_cfg()
{
  local tmp_file=/tmp/.bfcfg-redfishfs-data

  has_redfish_cfg=0

  if [ ! -e "${redfish_cfg_sysfs}" ]; then
    log_msg "sys: failed to find the ${redfish_cfg_sysfs} EFI variable"
    return
  fi

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

  sys_cfg_one_byte ${tmp_file} "ENABLE_REDFISH" 4 "${SYS_ENABLE_REDFISH}"
  sys_cfg_one_byte ${tmp_file} "RTCSYNC" 5 "${SYS_RTCSYNC}"

  if [ ${has_change} -eq 1 ]; then
    chattr -i ${redfish_cfg_sysfs}
    cp ${tmp_file} ${redfish_cfg_sysfs}
    sync
    has_redfish_cfg=1
  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

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

  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 num

  num=0
  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
    num=$((num + 1))
  done
  echo "0x${p0mac}"
  echo "${num}"
}

#
# 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
  local oob_mac_addr num oob_vlan_path

  [ $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}"
        oob_mac_addr=$(echo "$mac" | tr -d :)
        ;;

      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)
        {
          read -r mac
          read -r num
        } <<< "$(get_hca_p0_mac)"
        if [ -z "${mac}" ] || [ ."${mac}" = ."N/A" ] || [ ."${mac}" = ."0xfeffffffffff" ]; then
          log_msg "boot: failed to get MAC for ${entry}"
          bfcfg_rc=1
          continue
        fi
        if [ "${ifname}" = "NIC_P1" ]; then
          [ -z "${num}" ] && continue
          [ ${num} -lt 2 ] && continue
          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

  if [ -n "$oob_mac_addr" ]; then
    oob_vlan_path=${efivars}/${oob_mac_addr}-9e23d768-d2f3-4366-9fc3-3a7aba864374
    [ -e ${oob_vlan_path} ] && chattr -i ${oob_vlan_path}
    printf "\\x07\\x00\\x00\\x00\\xc8\\x0f" > ${oob_vlan_path}
  fi

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

# Enable UEFI Secure boot with default settings.
# UEFI password is reset to default.
sb_cfg()
{
  local sb_capsule_file=/lib/firmware/mellanox/boot/capsule/EnrollKeysCap

  [ $dump_mode -eq 1 ] && return

  [ "${UEFI_SECURE_BOOT}" != "TRUE" ] && return

  if [ ! -f "${sb_capsule_file}" ]; then
    log_msg "sb: failed to find the EFI capsule"
    return
  fi

  # Enable UEFI Secure boot
  bfrec --capsule ${sb_capsule_file} 2>&1 > /dev/null
  if [ $? -eq 0 ]; then
    log_msg "sb: UEFI Secure Boot will be enabled on next boot"
  else
    log_msg "sb: failed to enable UEFI Secure Boot"
  fi
}

# Convert input configuration to a binary file. As of
# now only MFG configuration is converted.
#
# Note 1: MFG dependencies such as system attributes
#         and Redfish settings are not supported.
#
# Note 2: This function can be enhanced to support
#         additional configuration.
#
# When 'bfcfg' cannot be executed from Arm operating
# system, an alternative is to run the command on external
# host to convert the config to a binary file. This binary
# file can be used to create a capsule that is appended to
# a BFB file and applied from external host.
cfg2bin()
{
  local cfg_file=$1
  local bin_file=$(basename $1).bin
  local tmp_bin_file=/tmp/.bfcfg-mfg.bin

  # Source the configuration if exists.
  if [ -e "${cfg_file}" ]; then
    . ${cfg_file}
  else
    echo "error: missing configuration file."
    return
  fi

  # Reset variables
  mfg_sysfs_cfg_done=0
  has_redfish_cfg=0
  has_sys_cfg=0

  start_log_file

  # Export config to bin
  mfg_cfg_to_bin ${tmp_bin_file}
  has_bin=0

  [ -f "${tmp_bin_file}" ] && has_bin=1

  if [ ${has_bin} -eq 1 ]; then
    mv ${tmp_bin_file} ${bin_file}
    rm -f ${tmp_bin_file}
    echo ""
    echo "$0: configuration blob written to ${bin_file}"
  else
    echo "error: failed to write configuration."
    return
  fi
}

start_log_file()
{
  rm -f ${log_file} >/dev/null
  log_msg "bfcfg (ver ${bfcfg_version})"
  log_msg "$(date)"
  log_msg
}

get_osarg_len() {
  local i=0
  local len=0
  local bits=8
  local osarg_len_offset=45
  local osarg_len_byte_size=4

  # Length of OsArgs is present in the ACPI table at a fixed offset.
  # Length information is UINT32 and hence spread over 4-bytes.
  # Read each byte and shift by 8X times and add them together.

  tail -c+$osarg_len_offset ${bfcf_acpi_table} | hexdump -v -e '/1 "%u\n"' | while read Char; do
    shiftval=$(expr $bits \* $i)
    newval=$(($Char<<$shiftval))
    len=$(expr $len + $newval)
    i=$(expr $i + 1)
    if [ $i -eq $osarg_len_byte_size ]; then
      echo $len
      break
    fi
  done
}

print_osarg()
{
  if [ ! -e ${bfcf_acpi_table} ]; then
    return
  fi

  local osarg_len=$(get_osarg_len)
  #
  # ACPI table headers are fixed, so OsArgs always starts from
  # a fixed offset.
  #
  local osarg_offset=49

  if [[ ${osarg_len} -ne 0 ]]; then
    tail -c+$osarg_offset ${bfcf_acpi_table} | head -c$osarg_len | hexdump -v -e '/1 "%c\n"' | while read Char; do
      printf "%c" $Char
    done
    printf "\n"
  fi
}

usage()
{
  echo "syntax: bfcfg [--help|-h] [--dump|-d] [--dump-osarg|o] [--part-info|-p] [--cfg2bin|-c <cfg_file>]"
}

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

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

# Start a new log file.
start_log_file

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

icm_cfg
if [ ${dump_mode} -eq 1 ]; then
  # Backward compatibility; dump mfg_cfg prior
  # to sys_cfg and sys_redfish_cfg
  mfg_cfg
  sys_cfg
  sys_redfish_cfg
else
  # It is important to run this configuration
  # in this specific order, because sys_cfg data
  # and sys_redfish_cfg data might be appended
  # to the capsule file created to program mfg_cfg
  sys_cfg
  sys_redfish_cfg
  mfg_cfg
fi
misc_cfg
boot_cfg
sb_cfg
sync

exit $bfcfg_rc
