#! /bin/bash
#
# Copyright(c) 2008 Intel Corporation. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms and conditions of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# This program is distributed in the hope it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
#
# Maintained at www.Open-FCoE.org

cmdname=`basename $0`

usage()
{
	echo usage: $cmdname \
		'<ethX> [--reset | --enable | --disable]' \
		'[--qos <pri>[,<pri>]...]]' >&2
	exit 1
}

#
# Tunable parameters
#
QOS_DEF=3			# default user priority
FCOE_ETHERTYPE=35078		# Ethertype (0x8906): tc filter show is base 10
FCOE_FILTER=0xfc0e		# filter handle (must be lower-case hex)
#
TC_CMD="/usr/sbin/tc"		# /sbin/tc or debug command
FCOEADM=/sbin/fcoeadm		# command to create/destroy FCoE instances
cmd=enable			# default command
qos_list=
LOGGER="logger -t fcoeplumb -s"
SERVICE_NAME=fcoe
CONFIG_DIR=/etc/sysconfig/$SERVICE_NAME

. $CONFIG_DIR/config
if [ "$USE_SYSLOG" != "yes" ] && [ "$USE_SYSLOG" != "YES" ]; then
	LOGGER="echo"
fi

[ "$#" -lt 1 ] && usage

ifname=$1
shift

while [ "$#" -ge 1 ]
do
	case "$1" in
	--reset | -r)
		cmd=reset
		;;
	--enable | -e)
		cmd=enable
		;;
	--disable | -d)
		cmd=disable
		;;
	--debug)
		LOGGER="logger -t fcoeplumb -s"
		;;
	--qos | -q)
		[ "$#" -lt 2 ] && usage
		qos_list=$2
		shift
		;;
	*)
		echo "$cmdname: unknown parameter '$1'" >&2
		usage
		;;
	esac
	shift
done

check_fcoe_if()
{
	ifname=$1

	STATUS=`$FCOEADM -i 2>&1 | awk '
	BEGIN {
		found = 0
		status = 1
	}
	/Interface Name:/ {
		if ($3 == ifname)
			found++
	}
	/State:/ {
		if ((found == 1) && ($2 == "Online"))
			status = 0
	}
	END {
		print status
	}' ifname=$ifname`

	$LOGGER "check_fcoe_if: ifname=$ifname, returns $STATUS"
	return $STATUS
}

destroy_interface()
{
	ifname=$1

	check_fcoe_if $ifname
	if [ $? -eq 0 ]; then
		$LOGGER "$FCOEADM --destroy $ifname"
		$FCOEADM --destroy $ifname
	fi

	qos_list=

        if set -- `$TC_CMD qdisc show dev $ifname` none none none
        then
                type=$2
                qdisc_id=$3
        fi

        if [ "$type" = multiq ]; then
                #
                # Remove the multiq queuing discipline., if exists
		# As a result of removing the multiq qdisc, the skbedit
		# filter is also removed.
                #
                qdisc_id=1:
		$LOGGER "$TC_CMD qdisc del dev $ifname ... multiq"
                $TC_CMD qdisc del dev $ifname root handle $qdisc_id multiq
        fi
}

$LOGGER "ifname $ifname cmd $cmd qos_list $qos_list"

if [ "$cmd" == "disable" ]; then
	destroy_interface $ifname
	exit 0
fi
## fallthrough for --reset and --enable

#
# Choose the best QOS to use for FCoE out of the listed choices.
#

# Parse QOS List
QOS_BEST=
if [ -n "$qos_list" ]; then
	OLD_IFS="$IFS"
	IFS=,"$IFS"
	set -- $qos_list
	IFS="$OLD_IFS"

	while [ "$#" -ge 1 ]
	do
		case "$1" in
		[0-7])
			;;
		*)
			echo "$cmdname: bad QOS value '$1'" >&2
			usage
			;;
		esac
		if [ -z "$QOS_BEST" ]; then
			QOS_BEST=$1
		elif [ "$1" -eq "$QOS_DEF" ]; then
			QOS_BEST=$1
		fi
		shift
	done
fi
$LOGGER "choosing FCoE QOS '$QOS_BEST'"

#
# Setup the traffic classifier for FCoE
# First see if it is already set up.
#
if [ -n "$QOS_BEST" ]; then
	qos_queue=`expr $QOS_BEST + 1`		# pri 0 is queue 1

	type=unknown
	if set -- `$TC_CMD qdisc show dev $ifname` none none none
	then
		type=$2
		qdisc_id=$3
	fi
	if [ "$type" != multiq ]; then
		#
		# Add the queuing discipline.
		#
		qdisc_id=1:
		$LOGGER "$TC_CMD qdisc add dev $ifname ... multiq"
		$TC_CMD qdisc add dev $ifname root handle $qdisc_id multiq
	fi

	#
	# Determine whether the filter for FCoE is already there.
	#
	found_filter=`$TC_CMD filter show dev $ifname |
		awk '
		BEGIN {
			in_filter = 0;
			found_filter = 0;
		}
		/^filter.*parent.*protocol 802_3.* handle '$FCOE_FILTER'/ {
			in_filter = 1;
		}
		/^[ 	]*$/ {
			in_filter = 0;
		}
		/cmp.*u16 at 12 layer 1 mask 0xffff eq '$FCOE_ETHERTYPE'.*\)/ {
			if (in_filter) {
				found_filter = 1;
			}
		}
		END {
			print found_filter
		} '`

	$LOGGER "found filter $found_filter"

	#
	# Add the filter for FCoE
	#
	if [ "$found_filter" -eq 0 ]; then
		$LOGGER "tc filter add dev $ifname ... skbedit queue_mapping 3"
		$TC_CMD filter add dev $ifname parent $qdisc_id protocol 802_3 \
			handle $FCOE_FILTER basic match 'cmp(u16' at 12 \
			layer 1 mask 0xffff eq $FCOE_ETHERTYPE')' \
			action skbedit queue_mapping 3
	fi
fi

#
# Start FCoE
#

## For --reset we only change the qdisc and filter when necessary.
## The kernel code should be doing reset. No need to reset here.
if [ "$cmd" == "enable" ]; then
	check_fcoe_if $ifname
	if [ $? -ne 0 ]; then
		$LOGGER "fcoeadm --create $ifname"
		$FCOEADM --create $ifname
	else
		$LOGGER "FCoE interface $ifname exists"
	fi
fi
exit 0

