#! /bin/bash
#
# Copyright (c) 2017 Mellanox Technologies. All rights reserved.
#
# This Software is licensed under one of the following licenses:
#
# 1) under the terms of the "Common Public License 1.0" a copy of which is
#    available from the Open Source Initiative, see
#    http://www.opensource.org/licenses/cpl.php.
#
# 2) under the terms of the "The BSD License" a copy of which is
#    available from the Open Source Initiative, see
#    http://www.opensource.org/licenses/bsd-license.php.
#
# 3) under the terms of the "GNU General Public License (GPL) Version 2" a
#    copy of which is available from the Open Source Initiative, see
#    http://www.opensource.org/licenses/gpl-license.php.
#
# Licensee has the right to choose one of the above licenses.
#
# Redistributions of source code must retain the above copyright
# notice and one of the license notices.
#
# Redistributions in binary form must reproduce both the above copyright
# notice, one of the license notices in the documentation
# and/or other materials provided with the distribution.
#

usage()
{
        echo "$(basename $0) <options>"
        echo "-h, --help                print help message"
        echo "-v, --verbose             print more info"
}

function find_pdev()
{
	pdevlist=$(ls /sys/bus/pci/devices)

	for pdev in $pdevlist; do
		if [ -d /sys/bus/pci/devices/$pdev/infiniband ]; then
			ibdlist=$(ls /sys/bus/pci/devices/$pdev/infiniband)
			for ibd in $ibdlist; do
				if [ "x$ibd" == "x$1" ]; then
					echo -n $pdev
				fi
			done
		fi
	done
}

case $1 in
        "-h" | "--help")
                usage
                exit 0
                ;;
esac

if (( $# > 1 )); then
	usage
	exit -1
fi

if (( $# == 1 )) && [ "$1" != "-v" ]; then
	usage
        exit -1
fi

IP="ip"
if [ -x /opt/mellanox/iproute2/sbin/ip ]; then
	IP="/opt/mellanox/iproute2/sbin/ip"
fi

if [ ! -d /sys/class/infiniband ]; then
	# driver is stopped
	exit -1
fi
ibdevs=$(ls /sys/class/infiniband/)

devs=
for netpath in /sys/class/net/*
do
    if (grep 0x15b3 ${netpath}/device/vendor > /dev/null 2>&1); then
        devs="$devs ${netpath##*/}"
    fi
done

if [ "x$devs" == "x" ]; then
	# no relevant devices - quit immediately
	exit
fi

for d in $devs; do
	if [ -f /sys/class/net/$d/dev_id ]; then
		oldstyle=n
		break
	fi
done

function print_verbose_info()
{
	d=$1
	port=$2
	eth=$3
	link_state=$4
	filepath_portstate=/sys/class/infiniband/$d/ports/$port/state
	filepath_deviceid=/sys/class/infiniband/$d/device/device
	filepath_fwver=/sys/class/infiniband/$d/fw_ver
	filepath_vpd=/sys/class/infiniband/$d/device/vpd

	# read port state
	if [ -f $filepath_portstate ]; then
		ibstate=$(printf "%-6s" $(cat $filepath_portstate | awk '{print $2}'))
	else
		ibstate="NA"
	fi

	# read device
	if [ -f $filepath_deviceid ]; then
		devid=$(printf "MT%d" $(cat $filepath_deviceid))
	else
		devid="NA"
	fi

	# read FW version
	if [ -f $filepath_fwver ]; then
		fwver=$(cat $filepath_fwver)
	else
		fwver="NA"
	fi

	# read device description and part ID from the VPD
	if [ -f $filepath_vpd ]; then
		tmp=$IFS
		IFS=":"
		vpd_content=`cat $filepath_vpd | strings`
		devdesc=$(printf "%-15s" $(echo $vpd_content | strings | head -1))
		partid=$(printf "%-11s" $(echo $vpd_content | strings | head -4 | tail -1 | awk '{print $1}'))
		IFS=$tmp
	else
		devdesc=""
		partid="NA"
	fi

	x=$(find_pdev $d)
	echo "$x $d ($devid - $partid) $devdesc fw $fwver port $port ($ibstate) ==> $eth ($link_state)"
}

function get_link_state()
{
	eth=$1
	filepath_devid=/sys/class/net/$eth/dev_id
	if [ -f $filepath_devid ]; then
		filepath_carrier=/sys/class/net/$eth/carrier
		if [ -f $filepath_carrier ]; then
			link_state=$(cat $filepath_carrier 2> /dev/null)
			if (( link_state == 1 )); then
				link_state="Up"
			else
				link_state="Down"
			fi
		else
			link_state="NA"
		fi
	fi
	echo -n $link_state
}

function find_matching_ndev_for_gid()
{
	gid=$1
	gid_without_prefix=${gid#"fe80:0000:0000:0000:"}
	gid_without_colon=${gid_without_prefix//:}

	ip_link_output=$($IP link show)
	IFS=$'\n' read -rd '' -a array <<<"$ip_link_output"

	for index in "${!array[@]}"
	do
		#search for infiniband address
		if [[ ${array[index]} =~ .*infiniband.* ]]; then
			# skip bonding master interfaces
			if [[ ${array[index-1]} =~ .*MASTER.* ]]; then
				continue
			fi

			ib_addr=${array[index]}
			#retain string after link/infiniband
			ib_addr=${ib_addr##*infiniband}
			#retain string before brd
			ib_addr=${ib_addr%brd*}
			#extract 8 bytes of hw address
			ib_addr=${ib_addr:37:25}
			#remove : from the address, have only alphanumeric
			ib_addr=${ib_addr//:}

			ib_ndev_name=${array[index-1]}
			#retain string before :
			ib_ndev_name=${ib_ndev_name%:*}
			#retain string after :
			ib_ndev_name=${ib_ndev_name#*:}

			if [ $gid_without_colon == $ib_addr ]; then
				# skip zero (i.e., undefined) gids
				if [ "x$gid_without_colon" == "x0000000000000000" ]; then
					continue
				fi
				echo -n $ib_ndev_name
				break
			fi
		fi
	done
}

if [ "x$oldstyle" == "xn" ]; then
	for d in $ibdevs; do
		ports=$(ls /sys/class/infiniband/$d/ports/)
		for port in $ports; do
			link_layer=$(cat /sys/class/infiniband/$d/ports/$port/link_layer)
			if [ "x$link_layer" == "xInfiniBand" ]; then
				gid=$(cat /sys/class/infiniband/$d/ports/$port/gids/0)
				ethdev=$(find_matching_ndev_for_gid $gid)
			else
				ethdev=$(cat /sys/class/infiniband/$d/ports/$port/gid_attrs/ndevs/0 2> /dev/null)
				#Honor ndev given by the kernel in the gid table for RoCE, soft RoCE
				#if kernel doesn't expose it (for IB), try the different method of
				#resource match.
			fi
			if [[ -z $ethdev ]]; then
				ibrsc=$(cat /sys/class/infiniband/$d/device/resource)
				eths=$(ls /sys/class/net/)
				for eth in $eths; do
					filepath_resource=/sys/class/net/$eth/device/resource

					if [ -f $filepath_resource ]; then
						ethrsc=$(cat $filepath_resource)
						if [ "x$ethrsc" == "x$ibrsc" ]; then
								link_state=$(get_link_state $eth)
								x=$(find_pdev $d)
								if [ "$1" == "-v" ]; then
									print_verbose_info $d $port $eth $link_state
								else
									echo "$d port $port ==> $eth ($link_state)"
								fi
								break
						fi
					fi
				done
			else
				link_state=$(get_link_state $ethdev)
				if [ "$1" == "-v" ]; then
					print_verbose_info $d $port $ethdev $link_state
				else
					echo "$d port $port ==> $ethdev ($link_state)"
				fi
			fi
		done
	done
else
##########################
### old style
##########################

function print_line()
{
	echo "$1 port $2 <===> $3"
}

function find_guid()
{
	ibdevs=$(ls /sys/class/infiniband/)
	for ibdev in $ibdevs; do
		ports=$(ls /sys/class/infiniband/$ibdev/ports/)
		for port in $ports; do
			gids=$(ls /sys/class/infiniband/$ibdev/ports/$port/gids)
			for gid in $gids; do
				pguid=$(cat /sys/class/infiniband/$ibdev/ports/$port/gids/$gid | cut -b 21- | sed -e 's/://g')
				if [ x$pguid == x$1 ]; then
					print_line $ibdev $port $2
				fi
			done
		done
	done
}

function find_mac()
{
	ibdevs=$(ls /sys/class/infiniband/)
	for ibdev in $ibdevs; do
		ports=$(ls /sys/class/infiniband/$ibdev/ports/)
		for port in $ports; do
			gids=$(ls /sys/class/infiniband/$ibdev/ports/$port/gids)
			for gid in $gids; do
				first=$(cat /sys/class/infiniband/$ibdev/ports/$port/gids/$gid | cut -b 21-22)
				first=$(( first ^ 2 ))
				first=$(printf "%02x" $first)
				second=$(cat /sys/class/infiniband/$ibdev/ports/$port/gids/$gid | cut -b 21- | sed -e 's/://g' | cut -b 3-6)
				third=$(cat /sys/class/infiniband/$ibdev/ports/$port/gids/$gid | cut -b 21- | sed -e 's/://g' | cut -b 11-)
				pmac=$first$second$third
				if [ x$pmac == x$1 ]; then
					print_line $ibdev $port $2
				fi
			done
		done
	done
}

ifcs=$($IP link | egrep '^[0-9]+: (ib|eth)' | awk '{print $2}' | tr -d :)

for ifc in $ifcs; do
	len=$(cat /sys/class/net/$ifc/addr_len)
	if (( len == 20 )); then
		guid=$(cat /sys/class/net/$ifc/address | cut -b 37- | sed -e 's/://g')
		find_guid $guid $ifc
	elif (( len == 6)); then
		mac=$(cat /sys/class/net/$ifc/address | sed -e 's/://g')
		find_mac $mac $ifc
	fi
done
fi

