[RFC PATCH 1/2] kselftest: Add test to verify probe of devices from discoverable busses

From: Nícolas F. R. A. Prado
Date: Tue Oct 24 2023 - 17:18:36 EST


Add a new test to verify that all expected devices from discoverable
busses (ie USB, PCI) on a given Devicetree-based platform have been
successfully instantiated and probed by a driver.

The per-platform list of expected devices is selected based on
compatible and stored under the boards/ directory.

The tests encode the devices to test for based on the hardware topology.
For USB devices, the format is:
usb <test_name> <controller_address>[,<additional_match>] <ports_path> <configuration> <interfaces>

The additional match field is optional and used to differentiate between
two busses (USB2 and USB3) sharing the same USB host controller.

For PCI devices, the format is:
pci <test_name> <controller_address> <device-function_pairs_path>

Signed-off-by: Nícolas F. R. A. Prado <nfraprado@xxxxxxxxxxxxx>

---

tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/devices/.gitignore | 1 +
tools/testing/selftests/devices/Makefile | 8 +
.../devices/test_discoverable_devices.sh | 165 ++++++++++++++++++
4 files changed, 175 insertions(+)
create mode 100644 tools/testing/selftests/devices/.gitignore
create mode 100644 tools/testing/selftests/devices/Makefile
create mode 100755 tools/testing/selftests/devices/test_discoverable_devices.sh

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 3b2061d1c1a5..7f5088006c3c 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -13,6 +13,7 @@ TARGETS += core
TARGETS += cpufreq
TARGETS += cpu-hotplug
TARGETS += damon
+TARGETS += devices
TARGETS += dmabuf-heaps
TARGETS += drivers/dma-buf
TARGETS += drivers/s390x/uvdevice
diff --git a/tools/testing/selftests/devices/.gitignore b/tools/testing/selftests/devices/.gitignore
new file mode 100644
index 000000000000..e3c5c04d1b19
--- /dev/null
+++ b/tools/testing/selftests/devices/.gitignore
@@ -0,0 +1 @@
+ktap_helpers.sh
diff --git a/tools/testing/selftests/devices/Makefile b/tools/testing/selftests/devices/Makefile
new file mode 100644
index 000000000000..ff2fdc8fc5e2
--- /dev/null
+++ b/tools/testing/selftests/devices/Makefile
@@ -0,0 +1,8 @@
+TEST_PROGS := test_discoverable_devices.sh
+TEST_GEN_FILES := ktap_helpers.sh
+TEST_FILES := boards
+
+include ../lib.mk
+
+$(OUTPUT)/ktap_helpers.sh:
+ cp ../dt/ktap_helpers.sh $@
diff --git a/tools/testing/selftests/devices/test_discoverable_devices.sh b/tools/testing/selftests/devices/test_discoverable_devices.sh
new file mode 100755
index 000000000000..91842b0c769f
--- /dev/null
+++ b/tools/testing/selftests/devices/test_discoverable_devices.sh
@@ -0,0 +1,165 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2023 Collabora Ltd
+#
+# This script tests for presence and driver binding of devices from discoverable
+# busses (ie USB, PCI) on Devicetree-based platforms.
+#
+# The per-platform list of devices to be tested is stored inside the boards/
+# directory and chosen based on compatible.
+#
+
+DIR="$(dirname "$(readlink -f "$0")")"
+
+source "${DIR}"/ktap_helpers.sh
+
+KSFT_FAIL=1
+KSFT_SKIP=4
+
+retval=0
+
+usb()
+{
+ name="$1"
+ controller="$2"
+ path="$3"
+ configuration="$4"
+ interfaces="$5"
+
+ # Extract additional match if present
+ if [[ "$controller" =~ , ]]; then
+ additional_match=${controller#*,}
+ address=${controller%,*}
+ else
+ address="$controller"
+ fi
+
+ for controller_uevent in /sys/bus/usb/devices/usb*/uevent; do
+ if grep -q "OF_FULLNAME=.*@$address$" "$controller_uevent"; then
+ # Look for additional match if present. It is needed to
+ # disambiguate two USB busses that share the same
+ # controller.
+ if [ -n "$additional_match" ]; then
+ if ! grep -q "$additional_match" "$controller_uevent"; then
+ continue
+ fi
+ fi
+ dir=$(basename "$(dirname "$controller_uevent")")
+ busnum=${dir#usb}
+ fi
+ done
+
+ usbdevs=/sys/bus/usb/devices/
+
+ IFS=,
+ for intf in $interfaces; do
+ devfile="$busnum"-"$path":"$configuration"."$intf"
+
+ if [ -d "$usbdevs"/"$devfile" ]; then
+ ktap_test_pass usb."$name"."$intf".device
+ else
+ ktap_test_fail usb."$name"."$intf".device
+ retval=$KSFT_FAIL
+ fi
+
+ if [ -d "$usbdevs"/"$devfile"/driver ]; then
+ ktap_test_pass usb."$name"."$intf".driver
+ else
+ ktap_test_fail usb."$name"."$intf".driver
+ retval=$KSFT_FAIL
+ fi
+ done
+}
+
+pci()
+{
+ name="$1"
+ controller="$2"
+ path="$3"
+
+ IFS=$'\n'
+ while read -r uevent; do
+ grep -q "OF_FULLNAME=.*@$controller$" "$uevent" || continue
+
+ # Ignore PCI bus directory, since it will have the same backing
+ # OF node, but not the PCI devices as subdirectories.
+ [[ "$uevent" =~ pci_bus ]] && continue
+
+ host_dir=$(dirname "$uevent")
+ done < <(find /sys/devices -name uevent)
+
+ # Add * to each level of the PCI hierarchy so we can rely on globbing to
+ # find the device directory on sysfs.
+ globbed_path=$(echo "$path" | sed -e 's|^|*|' -e 's|/|/*|')
+ device_path="$host_dir/pci*/$globbed_path"
+
+ # Intentionally left unquoted to allow the glob to expand
+ if [ -d $device_path ]; then
+ ktap_test_pass pci."$name".device
+ else
+ ktap_test_fail pci."$name".device
+ retval=$KSFT_FAIL
+ fi
+
+ if [ -d $device_path/driver ]; then
+ ktap_test_pass pci."$name".driver
+ else
+ ktap_test_fail pci."$name".driver
+ retval=$KSFT_FAIL
+ fi
+}
+
+count_tests()
+{
+ board_file="$1"
+ num_tests=0
+
+ # Each USB interface in a single USB test in the board file is a
+ # separate test
+ while read -r line; do
+ num_intfs=$(echo "$line" | tr -dc , | wc -c)
+ num_intfs=$((num_intfs + 1))
+ num_tests=$((num_tests + num_intfs))
+ done < <(grep ^usb "$board_file" | cut -d ' ' -f 6 -)
+
+ num_pci=$(grep -c ^pci "$board_file")
+ num_tests=$((num_tests + num_pci))
+
+ # Account for device and driver test for each of the tests listed in the
+ # board file.
+ num_tests=$((num_tests * 2))
+ echo $num_tests
+}
+
+ktap_print_header
+
+plat_compatible=/proc/device-tree/compatible
+
+if [ ! -f "$plat_compatible" ]; then
+ ktap_skip_all "No board compatible available"
+ exit "$KSFT_SKIP"
+fi
+
+compatibles=$(tr '\000' '\n' < "$plat_compatible")
+
+for compatible in $compatibles; do
+ if [ -f boards/"$compatible" ]; then
+ board_file=boards/"$compatible"
+ break
+ fi
+done
+
+if [ -z "$board_file" ]; then
+ ktap_skip_all "No matching board file found"
+ exit "$KSFT_SKIP"
+fi
+
+echo "# Using board file: " "$board_file"
+
+ktap_set_plan "$(count_tests "$board_file")"
+
+source "$board_file"
+
+ktap_print_totals
+exit "${retval}"
--
2.42.0