Add a selftest that logs the device information a driver reports through
the devlink info interface: the driver name, serial numbers and every
reported version, in the format NIPA CI consumes for device regression
tracking. This helps maintainers identify pass/fail status changes
caused by FW updates.

Every devlink info field is optional, so the test reports what a device
provides rather than requiring any particular field. devlink reports the
driver name for every registered instance, even when the driver does not
implement info_get, so a device that reports no versions and no serial
number is skipped, as are devices with no devlink instance. Whether the
driver name itself appears in the JSON depends on the installed
iproute2, so it is logged when present but not required.

The devlink 'driver' attribute names the driver bound to the parent
device, which may legitimately differ from the netdev's ethtool driver -
mlx4 reports mlx4_core and mlx4_en, and DSA user ports report dsa - so a
difference is logged rather than failed. ethtool is only used for an
optional fallback handle lookup and that comparison, so it is not
required to run the test.

NETIF is read from the environment or from drivers/net/net.config as
described in drivers/net/README.rst. net.config is sourced before
net/lib.sh so that it cannot clobber the framework's globals. The test
needs only a single local interface, so it sources net/lib.sh rather
than the forwarding library, which requires NUM_NETIFS and a configured
remote host.

Example usage:

  NETIF=eth0 ./tools/testing/selftests/drivers/net/hw/devlink_info.sh

  INFO: driver: idpf
  INFO: serial_number: 00-a0-c9-ff-ff-23-45-67
  INFO: fw.mgmt.api (running): 2.0
  TEST: devlink info                                                  [ OK ]

Link: 
https://github.com/linux-netdev/nipa/wiki/Netdev-CI-system#device-information
Signed-off-by: Paul Greenwalt <[email protected]>
Assisted-by: Claude:claude-opus-5
---
 .../testing/selftests/drivers/net/hw/Makefile |   1 +
 .../selftests/drivers/net/hw/devlink_info.sh  | 158 ++++++++++++++++++
 2 files changed, 159 insertions(+)
 create mode 100755 tools/testing/selftests/drivers/net/hw/devlink_info.sh

diff --git a/tools/testing/selftests/drivers/net/hw/Makefile 
b/tools/testing/selftests/drivers/net/hw/Makefile
index 78bb0169350b2..edd353f27a396 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -19,6 +19,7 @@ TEST_GEN_FILES := \
 
 TEST_PROGS = \
        csum.py \
+       devlink_info.sh \
        devlink_rate_cross_esw.py \
        devlink_rate_tc_bw.py \
        devmem.py \
diff --git a/tools/testing/selftests/drivers/net/hw/devlink_info.sh 
b/tools/testing/selftests/drivers/net/hw/devlink_info.sh
new file mode 100755
index 0000000000000..7516297b27d80
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/devlink_info.sh
@@ -0,0 +1,158 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test devlink info support
+#
+# This test logs the device information a driver reports through the devlink
+# info interface, in the format NIPA CI consumes for regression tracking (see
+# the Device information section at
+# https://github.com/linux-netdev/nipa/wiki/Netdev-CI-system).
+#
+# Implementing devlink info is optional, so a device whose driver reports no
+# versions and no serial number is skipped rather than failed.
+#
+# Usage:
+#   NETIF=eth0 ./devlink_info.sh
+
+ALL_TESTS="devlink_info_test"
+
+lib_dir=$(dirname "$0")
+
+# NETIF may also be provided through drivers/net/net.config, as documented in
+# drivers/net/README.rst. Source it before lib.sh so that a stray assignment
+# cannot clobber the framework's globals.
+if [[ -z "$NETIF" && -f "$lib_dir/../net.config" ]]; then
+       source "$lib_dir/../net.config"
+fi
+
+source "$lib_dir"/../../../net/lib.sh
+
+require_command devlink
+require_command jq
+
+DL_HANDLE=
+DL_INFO=
+
+setup_prepare()
+{
+       local err
+
+       if [[ -z "$NETIF" ]]; then
+               log_test_skip "devlink info" "NETIF is not configured"
+               exit "$EXIT_STATUS"
+       fi
+
+       # Try to get the devlink handle from the devlink port first.
+       DL_HANDLE=$(devlink -j port show 2>/dev/null |
+               jq -r --arg netif "$NETIF" \
+                       '.port | to_entries[] |
+                        select(.value.netdev == $netif) | .key' 2>/dev/null |
+               head -n 1 |
+               cut -d/ -f1-2)
+
+       # Fall back to the PCI address reported by ethtool. Devices on other
+       # buses are only found through the devlink port lookup above.
+       if [[ -z "$DL_HANDLE" ]] && command -v ethtool >/dev/null; then
+               local bus_info
+
+               bus_info=$(ethtool -i "$NETIF" 2>/dev/null |
+                       awk '/^bus-info:/ {print $2}')
+               if [[ -n "$bus_info" ]] &&
+                  devlink dev show "pci/$bus_info" &>/dev/null; then
+                       DL_HANDLE="pci/$bus_info"
+               fi
+       fi
+
+       if [[ -z "$DL_HANDLE" ]]; then
+               log_test_skip "devlink info" "no devlink handle for $NETIF"
+               exit "$EXIT_STATUS"
+       fi
+
+       # Query once so that a single snapshot is validated throughout.
+       DL_INFO=$(devlink -j dev info "$DL_HANDLE" 2>/dev/null)
+       err=$?
+       if ((err)); then
+               log_test_skip "devlink info" "devlink dev info failed for 
$DL_HANDLE"
+               exit "$EXIT_STATUS"
+       fi
+}
+
+# jq's "// empty" maps a missing or null field to no output, so callers get an
+# empty string rather than the literal text "null".
+info_get()
+{
+       local name=$1
+
+       jq -r --arg name "$name" '.[][][$name] // empty' <<<"$DL_INFO"
+}
+
+log_versions()
+{
+       local versions line
+
+       versions=$(jq -r '.[][].versions // {} | to_entries[] | .key as $type |
+                         .value | to_entries[] |
+                         "\(.key) (\($type)): \(.value)"' <<<"$DL_INFO" \
+                         2>/dev/null)
+
+       while IFS= read -r line; do
+               [[ -n "$line" ]] && log_info "$line"
+       done <<<"$versions"
+}
+
+has_any_version()
+{
+       jq -e '.[][].versions // {} | [.[] | to_entries[]] | length > 0' \
+               <<<"$DL_INFO" &>/dev/null
+}
+
+devlink_info_test()
+{
+       RET=0
+
+       local driver serial board_serial
+
+       driver=$(info_get "driver")
+       serial=$(info_get "serial_number")
+       board_serial=$(info_get "board.serial_number")
+
+       # devlink reports the driver name for every registered instance, even
+       # when the driver does not implement info_get. Everything else is
+       # optional, so a device with nothing further to report is not a
+       # failure.
+       if ! has_any_version && [[ -z "$serial" && -z "$board_serial" ]]; then
+               log_test_skip "devlink info" "no info reported for $DL_HANDLE"
+               return
+       fi
+
+       # Whether the driver name appears in the JSON depends on the installed
+       # iproute2, so report it when present but do not require it.
+       [[ -n "$driver" ]] && log_info "driver: $driver"
+
+       # devlink names the driver bound to the parent device, which can
+       # legitimately differ from the netdev's ethtool driver, so report a
+       # difference without failing.
+       if command -v ethtool >/dev/null; then
+               local ethtool_driver
+
+               ethtool_driver=$(ethtool -i "$NETIF" 2>/dev/null |
+                       awk '/^driver:/ {print $2}')
+               if [[ -n "$driver" && -n "$ethtool_driver" &&
+                     "$driver" != "$ethtool_driver" ]]; then
+                       log_info "driver mismatch: devlink='$driver' 
ethtool='$ethtool_driver'"
+               fi
+       fi
+
+       [[ -n "$serial" ]] && log_info "serial_number: $serial"
+       [[ -n "$board_serial" ]] && log_info "board.serial_number: 
$board_serial"
+
+       log_versions
+
+       log_test "devlink info"
+}
+
+setup_prepare
+
+tests_run
+
+exit "$EXIT_STATUS"
-- 
2.52.0

Reply via email to