#!/bin/bash

# Copyright IBM Corp. 2018, 2022

VERSION="1.8.8";


function usage() {
	echo;
	echo "Usage: smc_rnics [ OPTIONS ] [ FID ]";
	echo;
	echo "List RNICs";
	echo;
	echo "   -a, --all            include disabled devices in output";
	echo "   -d, --disable <FID>  disable the specified FID";
	echo "   -e, --enable <FID>   enable the specified FID";
	echo "   -h, --help           display this message";
	echo "   -I, --IB-dev         display IB-dev instead of netdev attributes";
	echo "   -r, --rawids         display 'type' as raw vendor/device IDs";
	echo "   -v, --version        display version info";
	echo;
}

function print_header() {
        if [ $IBdev -eq 1 ] && [ $rawIDs -eq 1 ]; then
		printf "FID   Power  PCI_ID        PCHID  Type                PFT   VFN  IPrt  PNET_ID            IB-Dev\n";
		echo '-------------------------------------------------------------------------------------------------------';
	elif [ $IBdev -eq 1 ]; then
		printf "FID   Power  PCI_ID        PCHID  Type                PFT   VF   IPrt  PNET_ID            IB-Dev\n";
		echo '-------------------------------------------------------------------------------------------------------';
	elif [ $rawIDs -eq 1 ]; then
		printf "FID   Power  PCI_ID        PCHID  Type                PFT   VFN  PPrt  PNET_ID            Net-Dev\n";
		echo '-------------------------------------------------------------------------------------------------------'
	else
		printf "FID   Power  PCI_ID        PCHID  Type                PFT   VF   PPrt  PNET_ID            Net-Dev\n";
		echo '-------------------------------------------------------------------------------------------------------';
	fi
}

function get_softset_pnet_id() {
	local res="n/a";
	local line;
	local id;
	local iface;
	local dev;
	local prt;

	while read -r line; do
		read id iface dev prt <<< $line;
		if [[ ("$iface" != "n/a" && "$iface" == "$int") || \
			("$dev" != "n/a" && "$dev" == "$addr") || \
			("$ibdev_name" != "n/a" && "$dev" == "$ibdev_name") ]]; then
			if [ "$prt" != "255" ] && [ "$prt" != "$iport" ]; then
				continue;
			fi
			res="$id*";
		fi
	done <<< "$(smc_pnet)"

	echo "$res";
}

function get_pnet_from_port() {
	local idx;
	local end;
	local lport=$port;
	local iport;
	local res;

	if [ "$lport" == "" ]; then
		echo "";
		return;
	fi
	if [ "$lport" == "n/a" ] || [ "$dev_type" != "RoCE_Express" ]; then
		lport=0;
	else
		[ $IBdev -ne 0 ] && let lport=$lport-1;
	fi
	(( iport=$lport+1 ))
	(( idx=16*$lport+1 ))
	(( end=$idx+15 ))
	res="`echo "$pnetids" | cut -c $idx-$end | sed 's/ //g'`";
	if [ "$res" == "" ]; then
		res="`get_softset_pnet_id`";
	fi
	echo $res;
}

function print_rnic() {
	if [ $rawIDs -eq 1 ]; then
		printf "%4x  %-5s  %-12s  %-4s   %-18s  %-4s  %-3s  %-4s  %-17s  %s\n" \
			"$((16#$fid))" "$power" "$addr" "$pchid" "$dev_type" "$pft" "${vfn:+$((16#${vfn#0x}))}" "$port" "`get_pnet_from_port`" "$int";
	else
		printf "%4x  %-5s  %-12s  %-4s   %-18s  %-4s  %-3s  %-4s  %-17s  %s\n" \
			"$((16#$fid))" "$power" "$addr" "$pchid" "$dev_type" "$pft" "$vfn" "$port" "`get_pnet_from_port`" "$int";
	fi
	(( printed++ ));
}

function set_RoCE_pft_and_vfn() {
	local lpft=$1;
	local lvfn=$((16#${2#0x}));

	case "$lpft" in
	"0x02") pft="ROC"; vfn="y";;
	"0x05") pft="ISM"; vfn="y";;
	"0x0a") pft="ROC2"; vfn="y";;
	"0x0c") pft="NETH"; vfn="y";;
	"0x0f") pft="NETD";
		if (( $lvfn != 0 )); then
			vfn="y";
		else
			vfn="n";
		fi;;
	*)
		vfn="$lvfn";;
	esac
}
function set_RoCE_dev_and_port() {
	dev_type="$1";
	if [ -e port ]; then
		port=`cat port`;
		if [ $port -eq 0 ]; then
			port="n/a";
		else
			if [ $IBdev -eq 0 ]; then
				let port=$port-1;
			else
				port=1;
			fi
		fi
	fi;
}

function set_by_firmware_lvl() {
	local iface;
	local name;
	local lvl;

	which ethtool >/dev/null 2>&1;
	if [ $? -eq 0 ] && [ "$int" != "n/a" ] && [ -d "net" ]; then
		iface="`ls -1 net | head -1`";
		lvl="`ethtool -i $iface | grep -e "^firmware-version:" | awk '{print($2)}'`";
		if [ "${lvl%%.*}" == "22" ]; then
			name="RoCE_Express3";
		elif [ "${lvl%%.*}" == "14" ]; then
			name="RoCE_Express2";
		fi
	fi
	set_RoCE_dev_and_port $name;
}

function set_by_pciid(){

	case "$vend" in
	"0x1014" ) # IBM
		case "$id" in
		"0x04ed") dev_type="ISM";
			  int="n/a";;
		esac;;
	"0x15b3" ) # Mellanox
		case "$id" in
		"0x1003" | \
		"0x1004") dev_type="RoCE_Express";
			  multiport_f=1;;
		"0x1016") set_RoCE_dev_and_port "RoCE_Express2";;
		"0x101e") #  default to Network_Express
			  if [ "0x0127" = "$ssid" ]; then
				  set_RoCE_dev_and_port "Integrated_Network";
			  elif [ "0x0002" = "$ssid" ]; then
				  set_RoCE_dev_and_port "Network_Express";
			  else
			      set_by_firmware_lvl
			  fi;;
		*)	  set_RoCE_dev_and_port "Mlx_$id";;
		esac;;
	esac
}

function print_rnics() {
	# iterate over slots, as powered-off devices won't show elsewhere
	for fid in `ls -1 /sys/bus/pci/slots`; do
		cd /sys/bus/pci/slots/$fid;
		fid="$fid";
		if [ "$target" != "" ] && [ "$fid" != "$target" ]; then
			continue;
		fi
		power=`cat power`;
		interfaces="";
		port="n/a";
		addr="";
		int="";
		ibdev_name="";
		if [ $power -eq 0 ]; then
			# device not yet hotplugged
			if [ $all -ne 0 ]; then
				dev_type="";
				pchid="";
				pft="";
				pnet="";
				port="";
				vfn="";
				print_rnic;
			fi
			continue;
		fi
		# device is hotplugged - locate it
		for dev in `ls -1 /sys/bus/pci/devices`; do
			cd /sys/bus/pci/devices/$dev;
			if [ "`cat function_id`" == "0x$fid" ]; then
				addr=$dev;
				break;
			fi
		done
		if [ "$addr" == "" ]; then
			echo "Error: No matching device found for FID $fid" >&2;
			continue;
		fi
		cd /sys/bus/pci/devices/$addr;
		# Get IB device name for this PCI device
		if [ -d "infiniband" ]; then
			ibdev_name="`ls -1 infiniband | head -1`";
		else
			ibdev_name="n/a";
		fi
		id=`cat device`;
		vend=`cat vendor`;
		ssid=`cat subsystem_device`;
		dev_type="${vend#0x}:${id#0x}";
		pft=`cat pft`;
		vfn=`cat vfn`;
		multiport_f=0;
		if [ $rawIDs -eq 0 ]; then
			# prefer PFT for disamgibuating devices
			case "$pft" in
			"0x05") dev_type="ISM";
				int="n/a";
				set_RoCE_pft_and_vfn "$pft" "$vfn";;
			"0x02") dev_type="RoCE_Express";
				multiport_f=1;
				set_RoCE_pft_and_vfn "$pft" "$vfn";;
			"0x0a") set_by_firmware_lvl;
				set_RoCE_pft_and_vfn "$pft" "$vfn";;
			"0x0c" | \
			"0x0f")
				if [ "0x0127" = "$ssid" ]; then
					set_RoCE_dev_and_port "Integrated_Network";
				else
					set_RoCE_dev_and_port "Network_Express";
				fi
				set_RoCE_pft_and_vfn "$pft" "$vfn";;
			*)
				# but if that fails go via the pci IDs
				set_by_pciid
				# For unknown PCI vendors, determine VF flag based on VFN value.
				# This ensures consistent handling even for unrecognized vendor devices
				[ "$dev_type"  = "${vend#0x}:${id#0x}" ] && [ $all -eq 0 ] && continue
				if (( 16#${vfn#0x} != 0 )); then
					vfn="y";
				else
					vfn="n";
				fi;;
			esac
		else
			set_RoCE_dev_and_port "$dev_type";
			[ "0x15b3" = "$vend" ] && [ "0x1004" = "$id" ] &&  multiport_f=1;
		fi
		pchid="`cat pchid | sed 's/^0x//'`";
		pnetids="`cat util_string | sed 's/\x0/\x40/g' | iconv -f IBM-1047 -t ASCII`";
		if [ $IBdev -eq 0 ]; then
			if [ -d "net" ]; then
				interfaces="`ls -1 net`";
			else
				int="n/a";
				print_rnic;
				continue;
			fi
			for int in $interfaces; do
				# one device can have multiple interfaces (one per port)
				cd /sys/bus/pci/devices/$addr/net/$int;
				if [ $multiport_f -eq 1 ] && [ -e dev_port ]; then
					port=`cat dev_port`;
				fi
				print_rnic;
			done
		else
			if [ -d "infiniband" ]; then
				int="`ls -1 infiniband`";
			else
				int="n/a";
				print_rnic;
				continue;
			fi
			# only one IB interface per card
			cd /sys/bus/pci/devices/$addr/infiniband/$int
			for port in `ls -1 ports`; do
				print_rnic;
			done
		fi
	done
}

function format_fid() {
	res="${1#0x}";

	if [[ ! "$res" =~ ^[[:xdigit:]]+$ ]]; then
		printf "Error: '%s' is not a valid FID\n" "$res" >&2;
		exit 3;
	fi

	res="`printf "%08x" $((16#$res))`";
}

args=`getopt -u -o hIrvae:d: -l all,enable:,disable:,help,IB-dev,rawids,version -- $*`;
[ $? -ne 0 ] && exit 2;
set -- $args;
action="print";
rawIDs=0;
all=0;
target="";
IBdev=0;
printed=0;
while [ $# -gt 0 ]; do
	case $1 in
	"-a" | "--all" )
		all=1;;
	"-e" | "--enable" )
		action="enable";
		fid=$2;
		shift;;
	"-d" | "--disable" )
		action="disable";
		fid=$2;
		shift;;
	"-h" | "--help" )
		usage;
		exit 0;;
	"-I" | "--IB-dev" )
	        IBdev=1;;
	"-r" | "--rawids" )
		rawIDs=1;;
	"-v" | "--version" )
		echo "smc_rnics utility, smc-tools-$VERSION";
		exit 0;;
	"--" ) ;;
	* )	format_fid "$1";
		target="$res";
	esac
	shift;
done

if [ "`uname -m`" != "s390x" ] && [ "`uname -m`" != "s390" ]; then
	printf "Error: s390/s390x supported only\n" >&2;
	exit 1;
fi

if [ "$action" != "print" ]; then
	if [ "$target" != "" ]; then
		usage;
		exit 4;
	fi
	format_fid "$fid";
	fid="$res";
	ufid=`printf "%x" $((16#$fid))`;  # representation without leading zeros
	if [ ! -d /sys/bus/pci/slots/$fid ]; then
		echo "Error: FID $ufid does not exist" >&2;
		exit 5;
	fi
	power=`cat /sys/bus/pci/slots/$fid/power 2>/dev/null`;
	val=0;
	[ "$action" == "enable" ] && val=1;
	if [ $power -eq $val ]; then
		echo "Error: FID $ufid is already ${action}d" >&2;
		exit 6;
	fi
	echo $val  > /sys/bus/pci/slots/$fid/power 2>/dev/null;
	if [ $? -ne 0 ]; then
		echo "Error: Failed to $action FID $ufid" >&2;
		exit 7;
	fi
	exit 0;
fi

print_header;
print_rnics | sort -k 1;

if [ "$target" != "" ] && [ $printed -eq 0 ]; then
	exit 8;
fi

exit 0;
