#!/bin/bash

# Copyright IBM Corp. 2018, 2022

VERSION="1.8.3";


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 0 ]; then
		printf "     FID  Power  PCI_ID        PCHID  Type           PPrt  PNET_ID            Net-Dev\n";
	else
		printf "     FID  Power  PCI_ID        PCHID  Type           IPrt  PNET_ID            IB-Dev\n";
	fi
	echo '------------------------------------------------------------------------------------------';
}

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") ]]; 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() {
	printf "%8x  %-5s  %-12s  %-4s   %-14s %-4s  %-17s  %s\n" "$((16#$fid))" "$power" "$addr" "$pchid" "$dev_type" "$port" "`get_pnet_from_port`" "$int";
	(( printed++ ));
}

function set_RoCE_dev_and_port() {
	dev_type="$1";
	if [ -e port ]; then
		port=`cat port`;
		if [ $IBdev -eq 0 ]; then
			let port=$port-1;
		else
			port=1;
		fi
	fi;
}

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

	name="Mlx_$id";
	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";
		fi
	fi
	set_RoCE_dev_and_port $name;
}

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="";
		if [ $power -eq 0 ]; then
			# device not yet hotplugged
			if [ $all -ne 0 ]; then
				dev_type="";
				pchid="";
				pnet="";
				port="";
				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;
		id=`cat device`;
		vend=`cat vendor`;
		dev_type="${vend#0x}:${id#0x}";
		if [ $rawIDs -eq 0 ]; then
			case "$vend" in
			"0x1014" ) # IBM
				case "$id" in
				"0x04ed") dev_type="ISM";
					  int="n/a";;
				*)
					continue;
				esac;;
			"0x15b3" ) # Mellanox
				case "$id" in
				"0x1003" | \
				"0x1004") dev_type="RoCE_Express";;
				"0x1016") set_RoCE_dev_and_port "RoCE_Express2";;
				"0x101e") set_by_firmware_lvl;;
				*)	  set_RoCE_dev_and_port "Mlx_$id";;
				esac;;
			*) [ $all -eq 0 ] && continue
			esac
		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
			# one device can have multiple interfaces (one per port)
			for int in $interfaces; do
				cd /sys/bus/pci/devices/$addr/net/$int;
				if [ "$dev_type" == "RoCE_Express" ] && [ -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;
