#!/bin/env bash
# Call this without arguments to get help.
me=$(basename "$0")
basedir=$(dirname $0)
source ${basedir}/functions.sh

# Global variables
VERBOSE=
DEBUG=
iflist=
declare -A ipaddr rxstats txstats timestats
pktgen_pid=
target_speed=
# Interval in seconds between checking the stats
declare -i interval=10

###########################################################################
Usage() {
	echo "Usage: $me [-vx] command

On a box prepared for testing (as root):
   emctest dut|partner|partnerport1|partnerport2
Building a emcpackage tar-ball (from a linux-net checkout):
   emctest package

The -v option makes $me more verbose. Use -x for debugging output."
	exit 1
}

log() {
	info "$me: $*";
}

fail() {
	err 1 "$*"
}

# Parse command line options.
#
ParseCmdLine() {
	local rc=0

	# Parse input here
	while [ "x$1" != "x" ];
	do
		case "$1" in
		'-?'|-h|--help)
			rc=1
			;;
		-v)	VERBOSE=1
			;;
		-x)	DEBUG=1
			VERBOSE=1
			;;
		-*)	echo "ERROR: Unknown option $1."
			rc=1
			;;
		*)	Args="${Args:+$Args }$1";;
		esac
		shift
	done
	test -z "$Args" && rc=1
	[[ $rc != 0 ]] && Usage
}

make_package() {
	od=/tmp/emcpackage
	rm -rf $od
	mkdir $od

	top=$(git rev-parse --show-toplevel 2> /dev/null)
	test -z "$top" && fail "Must execute $me from within a linux-net checkout"
	make -C $top --no-print-directory clean
	cp -r $top/drivers/net/ethernet/sfc/* $od/
	cp -r $top/scripts/*.sh $top/scripts/emctest $od/
	make -C $top --no-print-directory version > $od/version.txt

	tar -cf $top/scripts/emcpackage.tar -C /tmp emcpackage
	log "Created emcpackage.tar"
	return
}

make_driver() {
	cd $basedir
	make clean
	make
	rm -rf .tmp_versions
}

install_scripts() {
	if [ -f /usr/local/bin/prep-2x50 ];
	then
		return
	fi

	prep_script='#!/bin/env sh\nsfboot -y port-mode=%s\n'

	printf "$prep_script" [2x50G]              > /usr/local/bin/prep-2x50
	printf "$prep_script" [4x10/25G]           > /usr/local/bin/prep-4x25
	printf "$prep_script" [1x40/50G][1x40/50G] > /usr/local/bin/prep-1x50_1x50
	printf "$prep_script" [2x10/25G][2x10/25G] > /usr/local/bin/prep-2x25_2x25
	chmod 755 /usr/local/bin/prep-*
}

load_driver() {
	local type=$1
	local interfacenum=$2
	cd $basedir
	test -f sfc.ko || make_driver
	./load.sh -noip -nodblog -mtu 9000 -netparm rx_ring=4096 -netparm rx_recycle_ring_size=4096 -netparm tx_ring=2048

	iflist=`grep -HEi '00:0F:53|02:00:00:00:00:0' /sys/class/net/*/address | cut -d '/' -f 5`
	if [ -n "$interfacenum" ]; then
		iflist=`echo "$iflist" | head -n $interfacenum | tail -n 1`
		echo -e "\nOnly one interface being tested"
	fi
	test -z "$iflist" && fail "Driver failed to create any interfaces"
	echo -e "\nList of interfaces being tested:\n$iflist\n"

	# Bring interface up and reset stats
	for intf in $iflist;
	do
		ifconfig $intf up
		rxstats[$intf]=
		txstats[$intf]=
		timestats[$intf]=
	done
}

cable_check() {
	local interfacenum=${2:-1}

	# Wipe the LLDP cache
	service lldpad stop
	rm -f /var/lib/lldpad/lldpad.conf
	service lldpad start
	sleep 1

	iflist=`grep -Hi '00:0F:53' /sys/class/net/*/address | cut -d '/' -f 5`
	# Enable LLDP on all interfaces
	for intf in $iflist;
	do
		lldptool -L -i $intf adminStatus=rxtx > /dev/null;
	done

	intf=`echo "$iflist" | head -n $interfacenum | tail -n 1`
	log "Checking the cable on $intf"
	# Enable MTU on 1 port only.
	lldptool -T -i $intf -V MTU enableTx=yes > /dev/null
	# Kick of an LLDP request to the other machine
	lldptool -t -n -i $intf > /dev/null
	sleep 1

	ok=
	count=0
	while [ -z "$ok" ];
	do
		carrier=$(< /sys/class/net/$intf/carrier)
		if [ "$carrier" -ne "1" ];
		then
			echo "$intf ERROR: No carrier detected, please check the cables."
			sleep 10
			continue;
		fi

		# Check if the test is running on the other side
		out=`lldptool -t -n -i $intf`
		if [ -z "$out" ];
		then
			test $count -gt 0 && echo "$intf ERROR: The EMC test is not running on the other machine. Please start it."
			let count=$count+1
			sleep 10
			continue;
		fi

		# Check the cable
		out=`lldptool -t -n -i $intf -V MTU`
		if [ -z "$out" ];
		then
			echo "$intf ERROR: The cable on port $interfacenum is connected to the wrong port on the partner machine. Please fix the cables."
			sleep 10
			continue;
		fi

		ok=1;
	done
}

Gbps_bandwidth() {
	bytes=$1
	secs=$2
	python -c "print('%3.3f' % ($bytes*8/$secs/1000000000.0))"
}

# Print progress on a network interface.
# Returns:
# 0 if progress was made,
# 1 if no new RX packets were seen,
# 2 if no new TX packets were seen.
#
intf_monitor() {
	intf=$1
	wanted_stats="port_rx_good_bytes port_tx_bytes port_rx_bytes"
	wanted_stats="$wanted_stats port_rx_bad_bytes"

	port_rx_good_bytes=
	port_tx_bytes=
	port_rx_bytes=
	port_rx_bad_bytes=0
	eval `ethtool -S $intf | grep -E "${wanted_stats// /:|}:" | sed 's/: /=/g'`
	now=`cut -d\  -f1 /proc/uptime`
	test -z "$port_rx_good_bytes" && \
		let port_rx_good_bytes=$port_rx_bytes-$port_rx_bad_bytes
	test -z "$port_tx_bytes" && \
		fail "ethtool -S $intf did not report port_tx_bytes"

	stall=0
	if [ -n "${rxstats[$intf]}" ];
	then
		let rxdiff=$port_rx_good_bytes-${rxstats[$intf]}
		let txdiff=$port_tx_bytes-${txstats[$intf]}
		tdiff=`python -c "print $now-${timestats[$intf]}"`
		rx_gbps=$(Gbps_bandwidth $rxdiff $tdiff)
		tx_gbps=$(Gbps_bandwidth $txdiff $tdiff)
		echo "$intf	RX: ${rx_gbps}Gbps		TX: ${tx_gbps}Gbps"
		test -n "$DEBUG" && info "	$rxdiff		$txdiff	${tdiff}"

		test $rxdiff -eq 0 && stall=1
		test $txdiff -eq 0 && stall=2
	fi
	rxstats[$intf]=$port_rx_good_bytes
	txstats[$intf]=$port_tx_bytes
	timestats[$intf]=$now
	return $stall
}

cleanup() {
	# Stop the packet generator
	test -n "$pktgen_pid" && kill $pktgen_pid > /dev/null 2>&1
	pktgen_pid=
	service lldpad stop
	log "Done"
	exit 0
}

intf_speed() {
	intf=$1
	carrier=$(< /sys/class/net/$intf/carrier)
	speed=0

	if [ "$carrier" -ne "1" ];
	then
		echo "$intf ERROR: No carrier detected, please check the cables"
	else
		speed=$(< /sys/class/net/$intf/speed)
		test $speed -eq 0 && echo "intf: Link is comming up..."
		# Return the speed in Gbps since return codes are only 8 bits
		let speed=$speed/1000
	fi
	return $speed
}

minimal_link_speed() {
	minspeed=
	nospeed=1
	# Loop until all links report a speed > 0
	while [ $nospeed -eq 1 ];
	do
		nospeed=0
		for intf in $iflist;
		do
			intf_speed $intf
			rc=$?
			if [ $rc -eq 0 ];
			then
				nospeed=1
			elif [ -z "$minspeed" ];
			then
				minspeed=$rc
			elif [ $rc -lt $minspeed ];
			then
				minspeed=$rc
			fi
		done
		test $nospeed=1 && cable_check
	done
	log "Minimal link speed is ${minspeed}Gbps"
	return $minspeed
}

send_traffic() {
	test -n "$pktgen_pid" && kill $pktgen_pid > /dev/null 2>&1
	pktgen_pid=
	minimal_link_speed
	target_speed=$?
	let target_speed=$target_speed*1000

	# Start sending traffic
	clist=$(echo $iflist | sed 's/ /,/g')
	trap "cleanup" SIGINT SIGTERM
	./pktgen_multidevice.sh -i $clist -s 9000 -c 0 -b 256 -r $target_speed &
	pktgen_pid=$!
	sleep 2
}

no_progress() {
	stall=$1
	direction=RX
	test $stall -eq 2 && direction=TX
	echo "ERROR: No $direction progress!"

	# Restart the packet generator if TX was stalled.
	# Check the partner if RX was stalled
	test $stall -eq 2 && send_traffic || cable_check
}

print_ok() {
	echo "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
	echo "HHHHH         HHHHHH  HHHH   HHHHH"
	echo "HHHH  HHHHHHH  HHHHH  HHH  HHHHHHH"
	echo "HHHH  HHHHHHH  HHHHH  HH  HHHHHHHH"
	echo "HHHH  HHHHHHH  HHHHH  H  HHHHHHHHH"
	echo "HHHH  HHHHHHH  HHHHH    HHHHHHHHHH"
	echo "HHHH  HHHHHHH  HHHHH  H  HHHHHHHHH"
	echo "HHHH  HHHHHHH  HHHHH  HH  HHHHHHHH"
	echo "HHHH  HHHHHHH  HHHHH  HHH  HHHHHHH"
	echo "HHHHH         HHHHHH  HHHH   HHHHH"
	echo "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
}

# Monitor progress on all interfaces
monitor_iflist() {
	while [ 1 -eq 1 ];
	do
	  stall=0
	  for loop in $(seq 6);		# Every minute
	  do
		for intf in $iflist;
		do
			intf_monitor $intf
			rc=$?
			if [ $rc -ne 0 ];
			then
				stall=$rc
				no_progress $stall;
			fi
		done
		sleep $interval;
	  done

	  test $stall -eq 0 && print_ok
	done
}

emc_start() {
	local type=$1
	setterm -powersave off -blank 0
	service NetworkManager stop 2> /dev/null
	service tuned stop 2> /dev/null
	install_scripts

	local interfacenum=
	case "$type" in
	"partnerport1") interfacenum="1";;
	"partnerport2") interfacenum="2";;
	esac

	load_driver $type $interfacenum
	cable_check $interfacenum

	# Bind this script to CPU 0.
	taskset -p 1 $$ > /dev/null
	log $iflist
	send_traffic
	# Track stats
	monitor_iflist
}

###########################################################################
ParseCmdLine $*



case "$Args" in
"package")	make_package;;
"reset")	make_driver;;
"dut"|"partner"|"partnerport1"|"partnerport2")
		root_check_run_with_sudo "$@"
		emc_start $Args;;
*)		Usage;;
esac
