#!/usr/bin/env bash
#  SPDX-License-Identifier: BSD-3-Clause
#  Copyright (C) 2020 Intel Corporation
#  All rights reserved.
#
[[ $(uname -s) == Linux ]] || exit 0

shopt -s extglob nullglob

declare -r rdma_rxe=/sys/module/rdma_rxe
declare -r rdma_rxe_add=$rdma_rxe/parameters/add
declare -r rdma_rxe_rm=$rdma_rxe/parameters/remove

declare -r infiniband=/sys/class/infiniband
declare -r infiniband_verbs=/sys/class/infiniband_verbs
declare -r net=/sys/class/net

declare -A net_devices
declare -A net_to_rxe
declare -A rxe_to_net

uevent() (
	[[ -e $1/uevent ]] || return 0

	source "$1/uevent"

	if [[ -v $2 ]]; then
		echo "${!2}"
	elif [[ -n $3 ]]; then
		echo "$3"
	fi
)

modprobeq() {
	modprobe -q "$@"
}

get_ipv4() {
	local ip

	# Get only the first ip
	read -r _ _ _ ip _ < <(ip -o -4 addr show dev "$1")
	if [[ -n $ip ]]; then
		echo "${ip%/*}"
	else
		echo "            "
	fi
}

get_rxe_mtu() {
	local rxe=$1
	local mtu
	local uverb

	for uverb in "$infiniband_verbs/uverbs"*; do
		if [[ $(< "$uverb/ibdev") == "$rxe" ]] \
			&& [[ -c /dev/infiniband/${uverb##*/} ]]; then
			[[ $(ibv_devinfo -d "$rxe") =~ active_mtu:(.*\ \(.*\)) ]]
			echo "${BASH_REMATCH[1]:-(?)}"
			return 0
		fi
	done
}

start() {
	local modules module

	modules=(
		"ib_core"
		"ib_uverbs"
		"rdma_ucm"
		"rdma_rxe"
	)

	for module in "${modules[@]}"; do
		[[ -e /sys/module/$module ]] && continue
		if [[ ! -e $(modinfo -F filename "$module") ]]; then
			return 0
		fi
	done 2> /dev/null

	modprobeq -a "${modules[@]}" || return 1
	add_rxe all
}

stop() {
	remove_rxe

	if ! modprobeq -r rdma_rxe \
		|| [[ -e $rdma_rxe ]]; then
		printf 'unable to unload drivers, reboot required\n'
	fi
}

status_header() {
	local header=("Name" "Link" "Driver" "Speed" "NMTU" "IPv4_addr" "RDEV" "RMTU")

	size_print_fields "${header[@]}"
}

status() {
	if [[ ! -e $rdma_rxe ]]; then
		printf 'rdma_rxe module not loaded\n' >&2
	fi

	local dev
	local link_map

	link_map[0]=no
	link_map[1]=yes

	status_header

	local name link driver speed mtu ip rxe rxe_dev active_mtu
	for dev in "${net_devices[@]}"; do
		name="" link="" driver=""
		speed="" mtu="" ip=""
		rxe_dev="" active_mtu=""

		name=${dev##*/}
		rxe_dev=${net_to_rxe["$name"]}
		active_mtu=$(get_rxe_mtu "$rxe_dev")

		link=${link_map[$(< "$dev/carrier")]}

		if [[ -e $dev/device/driver ]]; then
			driver=$(readlink -f "$dev/device/driver")
			driver=${driver##*/}
		elif [[ -e /sys/devices/virtual/net/${dev##*/} ]]; then
			# Try to be smart and get the type of the device instead
			driver=$(uevent "$dev" "DEVTYPE" "virtual")
		fi

		if [[ $link == yes ]]; then
			speed=$(< "$dev/speed")
			if ((speed >= 1000)); then
				speed=$((speed / 1000))GigE
			elif ((speed > 0)); then
				speed=${speed}Mb/s
			else
				speed=""
			fi
		fi

		mtu=$(< "$dev/mtu")
		ip=$(get_ipv4 "$name")

		size_print_fields \
			"$name" \
			"$link" \
			"$driver" \
			"$speed" \
			"$mtu" \
			"$ip" \
			"$rxe_dev" \
			"$active_mtu"
	done 2> /dev/null
	print_status
}

size_print_fields() {
	local fields=("$@") field
	local -g lengths lines lineno

	for field in "${!fields[@]}"; do
		if [[ -z ${fields[field]} ]]; then
			fields[field]="###"
		fi
		if [[ -z ${lengths[field]} ]]; then
			lengths[field]=${#fields[field]}
		else
			lengths[field]=$((lengths[field] > ${#fields[field]} ? lengths[field] : ${#fields[field]}))
		fi
	done

	eval "local -g _line_$lineno=(\"\${fields[@]}\")"
	lines+=("_line_${lineno}[@]")
	((++lineno))
}

print_status() {
	local field field_ref fieldidx
	local pad

	if [[ -n $NO_HEADER ]]; then
		unset -v "lines[0]"
	fi

	for field_ref in "${lines[@]}"; do
		printf '  '
		fieldidx=0
		for field in "${!field_ref}"; do
			if [[ -n $field ]]; then
				pad=$((lengths[fieldidx] - ${#field} + 2))
			else
				pad=$((lengths[fieldidx] + 2))
			fi
			if [[ -n $field && $field != "###" ]]; then
				printf '%s' "$field"
			else
				printf '   '
			fi
			printf '%*s' "$pad" ""
			((++fieldidx))
		done
		printf '\n'
	done
}

add_rxe() {
	local dev net_devs

	[[ -e $rdma_rxe_add ]] || return 0

	if [[ -z $1 || $1 == all ]]; then
		net_devs=("${!net_devices[@]}")
	elif [[ -n ${net_to_rxe["$1"]} ]]; then
		printf '%s interface already in use (%s)\n' \
			"$1" "${net_to_rxe["$1"]}"
		return 0
	elif [[ -n ${net_devices["$1"]} ]]; then
		net_devs=("$1")
	else
		printf '%s interface does not exist\n' "$1"
		return 1
	fi

	for dev in "${net_devs[@]}"; do
		if [[ -z ${net_to_rxe["$dev"]} ]]; then
			echo "${dev##*/}" > "$rdma_rxe_add"
		fi
		link_up "${dev##*/}"
	done 2> /dev/null
}

remove_rxe() {
	local rxes rxe

	[[ -e $rdma_rxe_rm ]] || return 0

	rxes=("${!rxe_to_net[@]}")
	if [[ -z $1 || $1 == all ]]; then
		rxes=("${!rxe_to_net[@]}")
	elif [[ -z ${rxe_to_net["$1"]} ]]; then
		printf '%s rxe interface does not exist\n' "$1"
		return 0
	elif [[ -n ${rxe_to_net["$1"]} ]]; then
		rxes=("$1")
	fi

	for rxe in "${rxes[@]}"; do
		echo "$rxe" > "$rdma_rxe_rm"
	done 2> /dev/null
}

link_up() {
	[[ -e $net/$1 ]] || return 0

	echo $(($(< "$net/$1/flags") | 0x1)) > "$net/$1/flags"
}

collect_net_devices() {
	local net_dev

	for net_dev in "$net/"!(bonding_masters); do
		(($(< "$net_dev/type") != 1)) && continue
		net_devices["${net_dev##*/}"]=$net_dev
	done
}

collect_rxe_devices() {
	local rxe_dev net_dev

	for rxe_dev in "$infiniband/"*; do
		if [[ -e $rxe_dev/parent ]]; then
			# Soft
			net_dev=$(< "$rxe_dev/parent")
		elif [[ -e $rxe_dev/device/net ]]; then
			# HW
			net_dev=$(readlink -f "$rxe_dev/device/net/"*)
			net_dev=${net_dev##*/}
		else
			continue
		fi 2> /dev/null

		[[ -n ${net_devices["$net_dev"]} ]] || continue
		net_to_rxe["$net_dev"]=${rxe_dev##*/}
		rxe_to_net["${rxe_dev##*/}"]=$net_dev
	done
}

collect_net_devices
collect_rxe_devices

case "${1:-status}" in
	start)
		start
		;;
	stop)
		stop
		;;
	add)
		add_rxe "${2:-all}"
		;;
	remove)
		remove_rxe "${2:-all}"
		;;
	status)
		IFS= read -r match < <(
			IFS="|"
			printf '%s\n' "${*:2}"
		)
		status | grep -E "${match:-.}"
		;;
	rxe-net)
		printf '%s\n' "${rxe_to_net[@]}"
		;;
	*)
		printf 'Invalid argument (%s)\n' "$1"
		;;
esac
