#!/bin/sh
#
# Copyright (c) 2019-2022 Citrix Systems, Inc. All rights reserved.
# This software and documentation contain valuable trade
# secrets and proprietary property belonging to Citrix Systems, Inc.
# None of this software and documentation may be copied,
# duplicated or disclosed without the express
# written permission of Citrix Systems, Inc.
#
# 04/12/22
#
# This file contains shell script subroutines needed by the
# Fortville firmware update script suite.
#
# usage:  NO USAGE!  This file cannot be called separately!
#

LOG_FILE="/var/log/upgrade_fortville_nic_fw.log"

#
# Prints message, and logs it to the log file.
#
log_msg()
{
	echo -e "$*"
	echo -e "$*" >> $LOG_FILE
}

#
# Returns 0 (TRUE) if this is an 11.n system, else returns 1
#
is_11_n_system()
{
	uname -r | grep -q "11.[0-9]-NETSCALER"
	return $?
}

#
# Discards extraneous link up messages
# Messages still spew, but separate from other output
#
discard_link_up_messages()
{
	ifconfig -a > /dev/null 2>&1
}

#
# Returns a list of vendors from Fortville NICs in VALUES
#
nic_vendors()
{
	VALUES=`pciconf -lv | grep ixl | grep ":0:0:" | cut -w -f 3 | cut -c 12-15`
}

#
# Returns 0 if any of the Fortville NICs are Intel
#
has_intel_nics()
{
	VALUES=""

	nic_vendors

	for VALUE in ${VALUES}
	do
		#echo ${VALUE};

		if [ ${VALUE} -eq 8086 ]
		then
			return 0
		fi
	done

	return 1
}

#
# Returns 0 if any of the Fortville NICs are Silicom
#
has_silicom_nics()
{
	VALUES=""

	nic_vendors

	for VALUE in ${VALUES}
	do
		#echo ${VALUE};

		if [ ${VALUE} -eq 1374 ]
		then
			return 0
		fi
	done

	return 1
}

#
# Returns 0 if this ixl number is an Intel Fortville NIC, and
# if it is the first port on that NIC.  Else returns 1.
#
is_intel_nic_port_0()
{
	local vendor
	local ixl_num
	ixl_num=$1

	vendor=`pciconf -lv | grep ixl${ixl_num}@ | grep ":0:0:" | cut -w -f 3 | cut -c 12-15`

	if [ ! -n "$vendor" ]
	then
		# vendor is uninitialized (not a meaningful value)
		log_msg ">>> vendor $vendor is uninitialized"
		return 1
	fi

	if [ ${vendor} -eq 8086 ]
	then
		# is Intel
		#echo $vendor "is 8086"
		return 0
	else
		#echo $vendor "is not 8086"
	fi

	return 1
}

#
# Returns 0 if this ixl number is a Silicom Fortville NIC, and
# if it is the first port on that NIC.  Else returns 1.
#
is_silicom_nic_port_0()
{
	local vendor
	local ixl_num
	ixl_num=$1

	vendor=`pciconf -lv | grep ixl${ixl_num}@ | grep ":0:0:" | cut -w -f 3 | cut -c 12-15`

	if [ ! -n "${vendor}" ]
	then
		# vendor is uninitialized (not a meaningful value)
		log_msg ">>> vendor $vendor is uninitialized"
		return 1
	fi

	if [ ${vendor} -eq 1374 ]
	then
		# is Silicom
		#echo $vendor "is 1374"
		return 0
	else
		#echo $vendor "is not 1374"
	fi

	return 1
}

#
# Returns a list of the first ixl number for each Fortville NIC in VALUES
#
nic_ixl_numbers()
{
	VALUES=`pciconf -lv | grep ixl | grep ":0:0:" | cut -w -f 1 | cut -d @ -f 1 | cut -c 4-6`
}

#
# Returns a list of firmware version numbers and EETRACK IDs for each
# Fortville NIC in VALUES
#
nic_fw_version_eetrack_id()
{
	VALUES=`sysctl dev.ixl | grep -e fw_version | cut -d " " -f 7,9`
}

#
# Prints a list of ixl #, interface name, FW version and EETRACK ID,
# vendor/device, subvendor/subdevice and MAC address sans ":",
# for the specified Fortville NIC.
# Also, records that string in the log file.
#
output_nic_info()
{
	local VALUE
	VALUE=$1

	NS_INTF_NAME=`sysctl dev.ixl.${VALUE}.iface_name | cut -d " " -f 2`
	FW_VERS_ETID=`sysctl dev.ixl.${VALUE} | grep fw_version | \
	    cut -d " " -f 7,9`
	# sysctls are different in FreeBSD 11.4
	if  is_11_n_system
	then
		PCI=`sysctl dev.ixl.${VALUE}.%location | cut -f 4 -w | \
		    sed -e "s/dbsf=pci/pci/" | sed -e "s/:[0-9]:[0-9]$//" | \
		    sed -e "s/[0-9]://"`
	else
		PCI=`sysctl dev.ixl.${VALUE}.%parent | cut -f 2 -w`
	fi
	VENDOR=`sysctl dev.ixl.${VALUE}.%pnpinfo | cut -f 2 -w | \
	    cut -f 2 -d "=" | sed -e "s/^0x//"`
	DEVICE=`sysctl dev.ixl.${VALUE}.%pnpinfo | cut -f 3 -w | \
	    cut -f 2 -d "=" | sed -e "s/^0x//"`
	SUBVENDOR=`sysctl dev.ixl.${VALUE}.%pnpinfo | cut -f 4 -w | \
	    cut -f 2 -d "=" | sed -e "s/^0x//"`
	SUBDEVICE=`sysctl dev.ixl.${VALUE}.%pnpinfo | cut -f 5 -w | \
	    cut -f 2 -d "=" | sed -e "s/^0x//"`
	MAC=`sysctl dev.ixl.${VALUE}.pf_mac_addr | cut -f 2 -w | \
	    sed -e "s/://g"`
	log_msg "  ixl${VALUE} (${NS_INTF_NAME})\t${FW_VERS_ETID} " \
	    " ${PCI}\t${VENDOR}/${DEVICE} ${SUBVENDOR}/${SUBDEVICE} "" ${MAC}"
}

#
# Prints a list of ixl #, interface name, FW version and EETRACK ID,
# vendor/device, subvendor/subdevice and MAC address sans ":",
# for each Fortville NIC in the system.
# Also, records those strings in the log file.
#
fw_eetrack()
{
	local VALUES

	discard_link_up_messages
	sleep 2

	VALUES=""

	nic_ixl_numbers

	log_msg "  ixl#  NS if# FW ver  ETID    PCI bus  Vend/Dev  Svend/Sdev" \
	    "  MAC addr"
	log_msg "  ----- ------ ----- --------  -------  --------- ----------" \
	    "------------"
	log_msg "Silicom NICs:"

	for VALUE in ${VALUES}
	do
		is_silicom_nic_port_0 ${VALUE}
		if [ $? -eq 0 ]
		then
			output_nic_info ${VALUE}
		fi
	done

	log_msg "Intel NICs:"

	for VALUE in ${VALUES}
	do
		is_intel_nic_port_0 ${VALUE}
		if [ $? -eq 0 ]
		then
			output_nic_info ${VALUE}
		fi
	done
}

#
# Unconditionally power-cycle the NetScaler.
#
power_cycle_netscaler()
{
	# log_msg ">>> Power-cycling NetScaler!"

	# Change default power cycle "off" time to 30 seconds.
	/usr/local/sbin/ipmitool -I open raw 0x0 0x0b 0x1e 
	# Command the LOM to cycle NetScaler power off/on.
	/usr/local/sbin/ipmitool chassis power cycle
}

#
# Returns 0 if this ixl number is a Fortville NIC requiring a
# power cycle after firmware update.  Else returns 1.
# Note:  Uses string comparisons because of hex digits.
# Note:  could convert hex to decimal with $((${SUBDEVICE})) and $((0x1aa3))
#
is_nic_requiring_power_cycle()
{
	local IXL_NUM
	IXL_NUM=$1

	VENDOR=`sysctl dev.ixl.${IXL_NUM}.%pnpinfo | cut -f 2 -w | \
	    cut -f 2 -d "="`
	DEVICE=`sysctl dev.ixl.${IXL_NUM}.%pnpinfo | cut -f 3 -w | \
	    cut -f 2 -d "="`
	SUBVENDOR=`sysctl dev.ixl.${IXL_NUM}.%pnpinfo | cut -f 4 -w | \
	    cut -f 2 -d "="`
	SUBDEVICE=`sysctl dev.ixl.${IXL_NUM}.%pnpinfo | cut -f 5 -w | \
	    cut -f 2 -d "="`

	# echo "${VENDOR}/${DEVICE} ${SUBVENDOR}/${SUBDEVICE}"

	if [ -n "${VENDOR}" ] &&
	   [ ${VENDOR} == 0x8086 ] &&
	   [ ${DEVICE} == 0x1572 ] &&
	   [ ${SUBVENDOR} == 0x1374 ] &&
	   [ ${SUBDEVICE} == 0x1a80 ]
	then
		# 10G Silicom 1a80 NIC
		# echo "10G Silicom 1a80 NIC"
		return 0
	fi

	if [ -n "${VENDOR}" ] &&
	   [ ${VENDOR} == 0x8086 ] &&
	   [ ${DEVICE} == 0x1584 ] &&
	   [ ${SUBVENDOR} == 0x1374 ] &&
	   [ ${SUBDEVICE} == 0x1aa3 ]
	then
		# 40G Silicom 1aa3 NIC
		# echo "40G Silicom 1aa3 NIC"
		return 0
	fi

	if [ -n "${VENDOR}" ] &&
	   [ ${VENDOR} == 0x8086 ] &&
	   [ ${DEVICE} == 0x1584 ] &&
	   [ ${SUBVENDOR} == 0x1374 ] &&
	   [ ${SUBDEVICE} == 0x1aaf ]
	then
		# 40G Silicom 1aaf NIC
		# echo "40G Silicom 1aaf NIC"
		return 0
	fi

	return 1
}

#
# Power cycle the NetScaler if required. Else reboot NetScaler.
# This is an approximation:  If the Fortville firmware update
# scripts are invoked, and the NetScaler contains NICs that
# would require power cycle, this function power cycles.
#
reboot_or_power_cycle_netscaler()
{
	local VALUES
	VALUES=""

	nic_ixl_numbers

	for VALUE in ${VALUES}
	do
		is_nic_requiring_power_cycle ${VALUE}
		if [ $? -eq 0 ]
		then
			log_msg ">>> Power-cycling NetScaler... $(date)"
			sleep 6
			sync; sync
			power_cycle_netscaler
			sleep 14
		fi
	done

	log_msg ">>> Rebooting NetScaler... $(date)"
	sleep 1
	reboot
}

#
# Reads the parameter values from the parameter file.
#
read_parms_from_file()
{
	local parm_file

	parm_file=$1

	FW_UPDATE_VERS=-1
	FW_UPDATE_STATE=-1
	FW_UPDATE_LOOP_COUNT=-1

	#
	# If the parameter file exists and is sane, read the contained values
	#
	if [ -f ${parm_file} ] && [ -s ${parm_file} ]
	then
		grep "^fw_update_vers=" ${parm_file} >/dev/null 2>&1
		if [ $? -eq 0 ]
		then
			FW_UPDATE_VERS=`grep "^fw_update_vers=" ${parm_file} | sed -e "s/^fw_update_vers=//"`
		fi
		grep "^fw_update_state=" ${parm_file} >/dev/null 2>&1
		if [ $? -eq 0 ]
		then
			FW_UPDATE_STATE=`grep "^fw_update_state=" ${parm_file} | sed -e "s/^fw_update_state=//"`
		fi
		grep "^fw_update_loops=" ${parm_file} >/dev/null 2>&1
		if [ $? -eq 0 ]
		then
			FW_UPDATE_LOOP_COUNT=`grep "^fw_update_loops=" ${parm_file} | sed -e "s/^fw_update_loops=//"`
		fi
	fi

	#echo "subr read state = $FW_UPDATE_STATE read loops = $FW_UPDATE_LOOP_COUNT"
}

#
# Writes the parameter values to the parameter file.
#
write_parms_to_file()
{
	local parm_file

	parm_file=$1

	# clean up any pre-existing parameter temp file
	rm -f ${parm_file}

	touch ${parm_file}
	echo "fw_update_vers=${FW_UPDATE_VERS}" >> ${parm_file}
	echo "fw_update_state=${FW_UPDATE_STATE}" >> ${parm_file}
	echo "fw_update_loops=${FW_UPDATE_LOOP_COUNT}" >> ${parm_file}
}

#
# Renames NetScaler interface names to the corresponding ixlNN name.
# Example:  For "dev.ixl.10.iface_name:40/3", "40/3" becomes "ixl10".
#
rename_interfaces()
{
	NAME_STRINGS=`sysctl dev.ixl | grep iface_name | sed -e "s/ //"`

	for NAME_STR in ${NAME_STRINGS}; do
		IXL_NUM=`echo ${NAME_STR} | cut -d "." -f 3`
		NS_INTF_NAME=`echo ${NAME_STR} | cut -d ":" -f 2`
		# echo ${NAME_STR} ${IXL_NUM} ${NS_INTF_NAME}
		ifconfig ${NS_INTF_NAME} name ixl${IXL_NUM}
	done
}

