Commit ecca75ae authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

selftests: drv-net: replace the nsim ring test with a drv-net one



We are trying to move away from netdevsim-only tests and towards
tests which can be run both against netdevsim and real drivers.

Replace the simple bash script we have for checking ethtool -g/-G
on netdevsim with a Python test tweaking those params as well
as channel count.

The new test is not exactly equivalent to the netdevsim one,
but real drivers don't often support random ring sizes,
let alone modifying max values via debugfs.

Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20251029164930.2923448-1-kuba@kernel.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 4920abac
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ TEST_PROGS := \
	ping.py \
	psp.py \
	queues.py \
	ring_reconfig.py \
	shaper.py \
	stats.py \
	xdp.py \
+0 −1
Original line number Diff line number Diff line
@@ -8,7 +8,6 @@ TEST_PROGS := \
	ethtool-features.sh \
	ethtool-fec.sh \
	ethtool-pause.sh \
	ethtool-ring.sh \
	fib.sh \
	fib_notifications.sh \
	hw_stats_l3.sh \
+0 −85
Original line number Diff line number Diff line
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only

source ethtool-common.sh

function get_value {
    local query="${SETTINGS_MAP[$1]}"

    echo $(ethtool -g $NSIM_NETDEV | \
        tail -n +$CURR_SETT_LINE | \
        awk -F':' -v pattern="$query:" '$0 ~ pattern {gsub(/[\t ]/, "", $2); print $2}')
}

function update_current_settings {
    for key in ${!SETTINGS_MAP[@]}; do
        CURRENT_SETTINGS[$key]=$(get_value $key)
    done
    echo ${CURRENT_SETTINGS[@]}
}

if ! ethtool -h | grep -q set-ring >/dev/null; then
    echo "SKIP: No --set-ring support in ethtool"
    exit 4
fi

NSIM_NETDEV=$(make_netdev)

set -o pipefail

declare -A SETTINGS_MAP=(
    ["rx"]="RX"
    ["rx-mini"]="RX Mini"
    ["rx-jumbo"]="RX Jumbo"
    ["tx"]="TX"
)

declare -A EXPECTED_SETTINGS=(
    ["rx"]=""
    ["rx-mini"]=""
    ["rx-jumbo"]=""
    ["tx"]=""
)

declare -A CURRENT_SETTINGS=(
    ["rx"]=""
    ["rx-mini"]=""
    ["rx-jumbo"]=""
    ["tx"]=""
)

MAX_VALUE=$((RANDOM % $((2**32-1))))
RING_MAX_LIST=$(ls $NSIM_DEV_DFS/ethtool/ring/)

for ring_max_entry in $RING_MAX_LIST; do
    echo $MAX_VALUE > $NSIM_DEV_DFS/ethtool/ring/$ring_max_entry
done

CURR_SETT_LINE=$(ethtool -g $NSIM_NETDEV | grep -i -m1 -n 'Current hardware settings' | cut -f1 -d:)

# populate the expected settings map
for key in ${!SETTINGS_MAP[@]}; do
    EXPECTED_SETTINGS[$key]=$(get_value $key)
done

# test
for key in ${!SETTINGS_MAP[@]}; do
    value=$((RANDOM % $MAX_VALUE))

    ethtool -G $NSIM_NETDEV "$key" "$value"

    EXPECTED_SETTINGS[$key]="$value"
    expected=${EXPECTED_SETTINGS[@]}
    current=$(update_current_settings)

    check $? "$current" "$expected"
    set +x
done

if [ $num_errors -eq 0 ]; then
    echo "PASSED all $((num_passes)) checks"
    exit 0
else
    echo "FAILED $num_errors/$((num_errors+num_passes)) checks"
    exit 1
fi
+167 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0

"""
Test channel and ring size configuration via ethtool (-L / -G).
"""

from lib.py import ksft_run, ksft_exit, ksft_pr
from lib.py import ksft_eq
from lib.py import NetDrvEpEnv, EthtoolFamily, GenerateTraffic
from lib.py import defer, NlError


def channels(cfg) -> None:
    """
    Twiddle channel counts in various combinations of parameters.
    We're only looking for driver adhering to the requested config
    if the config is accepted and crashes.
    """
    ehdr = {'header':{'dev-index': cfg.ifindex}}
    chans = cfg.eth.channels_get(ehdr)

    all_keys = ["rx", "tx", "combined"]
    mixes = [{"combined"}, {"rx", "tx"}, {"rx", "combined"}, {"tx", "combined"},
             {"rx", "tx", "combined"},]

    # Get the set of keys that device actually supports
    restore = {}
    supported = set()
    for key in all_keys:
        if key + "-max" in chans:
            supported.add(key)
            restore |= {key + "-count": chans[key + "-count"]}

    defer(cfg.eth.channels_set, ehdr | restore)

    def test_config(config):
        try:
            cfg.eth.channels_set(ehdr | config)
            get = cfg.eth.channels_get(ehdr)
            for k, v in config.items():
                ksft_eq(get.get(k, 0), v)
        except NlError as e:
            failed.append(mix)
            ksft_pr("Can't set", config, e)
        else:
            ksft_pr("Okay", config)

    failed = []
    for mix in mixes:
        if not mix.issubset(supported):
            continue

        # Set all the values in the mix to 1, other supported to 0
        config = {}
        for key in all_keys:
            config[key + "-count"] = 1 if key in mix else 0
        test_config(config)

    for mix in mixes:
        if not mix.issubset(supported):
            continue
        if mix in failed:
            continue

        # Set all the values in the mix to max, other supported to 0
        config = {}
        for key in all_keys:
            config[key + "-count"] = chans[key + '-max'] if key in mix else 0
        test_config(config)


def _configure_min_ring_cnt(cfg) -> None:
    """ Try to configure a single Rx/Tx ring. """
    ehdr = {'header':{'dev-index': cfg.ifindex}}
    chans = cfg.eth.channels_get(ehdr)

    all_keys = ["rx-count", "tx-count", "combined-count"]
    restore = {}
    config = {}
    for key in all_keys:
        if key in chans:
            restore[key] = chans[key]
            config[key] = 0

    if chans.get('combined-count', 0) > 1:
        config['combined-count'] = 1
    elif chans.get('rx-count', 0) > 1 and chans.get('tx-count', 0) > 1:
        config['tx-count'] = 1
        config['rx-count'] = 1
    else:
        # looks like we're already on 1 channel
        return

    cfg.eth.channels_set(ehdr | config)
    defer(cfg.eth.channels_set, ehdr | restore)


def ringparam(cfg) -> None:
    """
    Tweak the ringparam configuration. Try to run some traffic over min
    ring size to make sure it actually functions.
    """
    ehdr = {'header':{'dev-index': cfg.ifindex}}
    rings = cfg.eth.rings_get(ehdr)

    restore = {}
    maxes = {}
    params = set()
    for key in rings.keys():
        if 'max' in key:
            param = key[:-4]
            maxes[param] = rings[key]
            params.add(param)
            restore[param] = rings[param]

    defer(cfg.eth.rings_set, ehdr | restore)

    # Speed up the reconfig by configuring just one ring
    _configure_min_ring_cnt(cfg)

    # Try to reach min on all settings
    for param in params:
        val = rings[param]
        while True:
            try:
                cfg.eth.rings_set({'header':{'dev-index': cfg.ifindex},
                                   param: val // 2})
                if val == 0:
                    break
                val //= 2
            except NlError:
                break

        get = cfg.eth.rings_get(ehdr)
        ksft_eq(get[param], val)

        ksft_pr(f"Reached min for '{param}' at {val} (max {rings[param]})")

    GenerateTraffic(cfg).wait_pkts_and_stop(10000)

    # Try max across all params, if the driver supports large rings
    # this may OOM so we ignore errors
    try:
        ksft_pr("Applying max settings")
        config = {p: maxes[p] for p in params}
        cfg.eth.rings_set(ehdr | config)
    except NlError as e:
        ksft_pr("Can't set max params", config, e)
    else:
        GenerateTraffic(cfg).wait_pkts_and_stop(10000)


def main() -> None:
    """ Ksft boiler plate main """

    with NetDrvEpEnv(__file__) as cfg:
        cfg.eth = EthtoolFamily()

        ksft_run([channels,
                  ringparam],
                 args=(cfg, ))
    ksft_exit()


if __name__ == "__main__":
    main()