#!/bin/bash
# SPDX-License-Identifier: GPL-2.0

ALL_TESTS="
	manual_with_verification_h1_to_h2
	manual_with_verification_h2_to_h1
	manual_without_verification_h1_to_h2
	manual_without_verification_h2_to_h1
	manual_failed_verification_h1_to_h2
	manual_failed_verification_h2_to_h1
	lldp
"

NUM_NETIFS=2
REQUIRE_MZ=no
PREEMPTIBLE_PRIO=0
lib_dir=$(dirname "$0")
source "$lib_dir"/../../../net/forwarding/lib.sh

traffic_test()
{
	local if=$1; shift
	local src=$1; shift
	local num_pkts=10000
	local before=
	local after=
	local delta=

	if [ ${has_pmac_stats[$if]} = false ]; then
		src="aggregate"
	fi

	before=$(ethtool_std_stats_get $if "eth-mac" "FramesTransmittedOK" $src)

	$MZ $if -q -c $num_pkts -p 64 -b bcast -t ip -R $PREEMPTIBLE_PRIO

	after=$(ethtool_std_stats_get $if "eth-mac" "FramesTransmittedOK" $src)

	delta=$((after - before))

	# Allow an extra 1% tolerance for random packets sent by the stack
	[ $delta -ge $num_pkts ] && [ $delta -le $((num_pkts + 100)) ]
}

manual_with_verification()
{
	local tx=$1; shift
	local rx=$1; shift

	RET=0

	# It isn't completely clear from IEEE 802.3-2018 Figure 99-5: Transmit
	# Processing state diagram whether the "send_r" variable (send response
	# to verification frame) should be taken into consideration while the
	# MAC Merge TX direction is disabled. That being said, at least the
	# NXP ENETC does not, and requires tx-enabled on in order to respond to
	# the link partner's verification frames.
	ethtool --set-mm $rx tx-enabled on
	ethtool --set-mm $tx verify-enabled on tx-enabled on

	# Wait for verification to finish
	sleep 1

	ethtool --json --show-mm $tx | jq -r '.[]."verify-status"' | \
		grep -q 'SUCCEEDED'
	check_err "$?" "Verification did not succeed"

	ethtool --json --show-mm $tx | jq -r '.[]."tx-active"' | grep -q 'true'
	check_err "$?" "pMAC TX is not active"

	traffic_test $tx "pmac"
	check_err "$?" "Traffic did not get sent through $tx's pMAC"

	ethtool --set-mm $tx verify-enabled off tx-enabled off
	ethtool --set-mm $rx tx-enabled off

	log_test "Manual configuration with verification: $tx to $rx"
}

manual_with_verification_h1_to_h2()
{
	manual_with_verification $h1 $h2
}

manual_with_verification_h2_to_h1()
{
	manual_with_verification $h2 $h1
}

manual_without_verification()
{
	local tx=$1; shift
	local rx=$1; shift

	RET=0

	ethtool --set-mm $tx verify-enabled off tx-enabled on

	ethtool --json --show-mm $tx | jq -r '.[]."verify-status"' | \
		grep -q 'DISABLED'
	check_err "$?" "Verification is not disabled"

	ethtool --json --show-mm $tx | jq -r '.[]."tx-active"' | grep -q 'true'
	check_err "$?" "pMAC TX is not active"

	traffic_test $tx "pmac"
	check_err "$?" "Traffic did not get sent through $tx's pMAC"

	ethtool --set-mm $tx verify-enabled off tx-enabled off

	log_test "Manual configuration without verification: $tx to $rx"
}

manual_without_verification_h1_to_h2()
{
	manual_without_verification $h1 $h2
}

manual_without_verification_h2_to_h1()
{
	manual_without_verification $h2 $h1
}

manual_failed_verification()
{
	local tx=$1; shift
	local rx=$1; shift

	RET=0

	ethtool --set-mm $rx pmac-enabled off
	ethtool --set-mm $tx verify-enabled on tx-enabled on

	# Wait for verification to time out
	sleep 1

	ethtool --json --show-mm $tx | jq -r '.[]."verify-status"' | \
		grep -q 'SUCCEEDED'
	check_fail "$?" "Verification succeeded when it shouldn't have"

	ethtool --json --show-mm $tx | jq -r '.[]."tx-active"' | grep -q 'true'
	check_fail "$?" "pMAC TX is active when it shouldn't have"

	traffic_test $tx "emac"
	check_err "$?" "Traffic did not get sent through $tx's eMAC"

	ethtool --set-mm $tx verify-enabled off tx-enabled off
	ethtool --set-mm $rx pmac-enabled on

	log_test "Manual configuration with failed verification: $tx to $rx"
}

manual_failed_verification_h1_to_h2()
{
	manual_failed_verification $h1 $h2
}

manual_failed_verification_h2_to_h1()
{
	manual_failed_verification $h2 $h1
}

smallest_supported_add_frag_size()
{
	local iface=$1
	local rx_min_frag_size=

	rx_min_frag_size=$(ethtool --json --show-mm $iface | \
		jq '.[]."rx-min-frag-size"')

	if [ $rx_min_frag_size -le 60 ]; then
		echo 0
	elif [ $rx_min_frag_size -le 124 ]; then
		echo 1
	elif [ $rx_min_frag_size -le 188 ]; then
		echo 2
	elif [ $rx_min_frag_size -le 252 ]; then
		echo 3
	else
		echo "$iface: RX min frag size $rx_min_frag_size cannot be advertised over LLDP"
		exit 1
	fi
}

expected_add_frag_size()
{
	local iface=$1
	local requested=$2
	local min=$(smallest_supported_add_frag_size $iface)

	[ $requested -le $min ] && echo $min || echo $requested
}

lldp_change_add_frag_size()
{
	local add_frag_size=$1
	local pattern=

	lldptool -T -i $h1 -V addEthCaps addFragSize=$add_frag_size >/dev/null
	# Wait for TLVs to be received
	sleep 2
	pattern=$(printf "Additional fragment size: %d" \
			 $(expected_add_frag_size $h1 $add_frag_size))
	lldptool -i $h2 -t -n -V addEthCaps | grep -q "$pattern"
}

lldp()
{
	RET=0

	systemctl start lldpad

	# Configure the interfaces to receive and transmit LLDPDUs
	lldptool -L -i $h1 adminStatus=rxtx >/dev/null
	lldptool -L -i $h2 adminStatus=rxtx >/dev/null

	# Enable the transmission of Additional Ethernet Capabilities TLV
	lldptool -T -i $h1 -V addEthCaps enableTx=yes >/dev/null
	lldptool -T -i $h2 -V addEthCaps enableTx=yes >/dev/null

	# Wait for TLVs to be received
	sleep 2

	lldptool -i $h1 -t -n -V addEthCaps | \
		grep -q "Preemption capability active"
	check_err "$?" "$h1 pMAC TX is not active"

	lldptool -i $h2 -t -n -V addEthCaps | \
		grep -q "Preemption capability active"
	check_err "$?" "$h2 pMAC TX is not active"

	lldp_change_add_frag_size 3
	check_err "$?" "addFragSize 3"

	lldp_change_add_frag_size 2
	check_err "$?" "addFragSize 2"

	lldp_change_add_frag_size 1
	check_err "$?" "addFragSize 1"

	lldp_change_add_frag_size 0
	check_err "$?" "addFragSize 0"

	traffic_test $h1 "pmac"
	check_err "$?" "Traffic did not get sent through $h1's pMAC"

	traffic_test $h2 "pmac"
	check_err "$?" "Traffic did not get sent through $h2's pMAC"

	systemctl stop lldpad

	log_test "LLDP"
}

h1_create()
{
	ip link set dev $h1 up

	tc qdisc add dev $h1 root mqprio num_tc 4 map 0 1 2 3 \
		queues 1@0 1@1 1@2 1@3 \
		fp P E E E \
		hw 1

	ethtool --set-mm $h1 pmac-enabled on tx-enabled off verify-enabled off
}

h2_create()
{
	ip link set dev $h2 up

	ethtool --set-mm $h2 pmac-enabled on tx-enabled off verify-enabled off

	tc qdisc add dev $h2 root mqprio num_tc 4 map 0 1 2 3 \
		queues 1@0 1@1 1@2 1@3 \
		fp P E E E \
		hw 1
}

h1_destroy()
{
	ethtool --set-mm $h1 pmac-enabled off tx-enabled off verify-enabled off

	tc qdisc del dev $h1 root

	ip link set dev $h1 down
}

h2_destroy()
{
	tc qdisc del dev $h2 root

	ethtool --set-mm $h2 pmac-enabled off tx-enabled off verify-enabled off

	ip link set dev $h2 down
}

setup_prepare()
{
	h1=${NETIFS[p1]}
	h2=${NETIFS[p2]}

	h1_create
	h2_create
}

cleanup()
{
	pre_cleanup

	h2_destroy
	h1_destroy
}

check_ethtool_mm_support
check_tc_fp_support
require_command lldptool
bail_on_lldpad "autoconfigure the MAC Merge layer" "configure it manually"

for netif in ${NETIFS[@]}; do
	ethtool --show-mm $netif 2>&1 &> /dev/null
	if [[ $? -ne 0 ]]; then
		echo "SKIP: $netif does not support MAC Merge"
		exit $ksft_skip
	fi

	if check_ethtool_pmac_std_stats_support $netif eth-mac; then
		has_pmac_stats[$netif]=true
	else
		has_pmac_stats[$netif]=false
		echo "$netif does not report pMAC statistics, falling back to aggregate"
	fi
done

trap cleanup EXIT

setup_prepare
setup_wait

tests_run

exit $EXIT_STATUS
