[RFC PATCH v2 2/2] kselftest: Add test to detect unprobed devices on ACPI platforms

Laura Nao <[email protected]>
Newsgroups dev.linux.lists.kernelci,org.kernel.vger.linux-acpi,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest
Message-ID <[email protected]>
Add new kselftest that tests whether devices declared in the ACPI
namespace and supported by the kernel are correctly bound
to a driver.

The test traverses the ACPI sysfs tree to get a list of all the devices
defined in the ACPI namespace and verifies whether the physical devices
linked to each ACPI object are bound to a driver.
The test relies on two lists to skip devices not expected to be bound
to a driver:
- List generated by the acpi-extract-ids script: includes the ACPI IDs
  matched by a driver
- Manual list of ignored IDs: includes the ID of devices that may be
  discovered only via the platform firmware and that don't require a
  driver or cannot be represented as platform devices

The test also examines the sysfs attributes of the target device objects
linked by physical_node* to exclude other devices that should not be
bound to a driver. This includes:
- Devices not assigned to any subsystem
- Devices that are linked to other devices
- Class devices
- Specific PCI bridges that do not require a driver

Signed-off-by: Laura Nao <[email protected]>
---
 MAINTAINERS                                   |   1 +
 tools/testing/selftests/Makefile              |   1 +
 tools/testing/selftests/acpi/.gitignore       |   1 +
 tools/testing/selftests/acpi/Makefile         |  21 +++
 tools/testing/selftests/acpi/id_ignore_list   |   3 +
 .../selftests/acpi/test_unprobed_devices.sh   | 138 ++++++++++++++++++
 6 files changed, 165 insertions(+)
 create mode 100644 tools/testing/selftests/acpi/.gitignore
 create mode 100644 tools/testing/selftests/acpi/Makefile
 create mode 100644 tools/testing/selftests/acpi/id_ignore_list
 create mode 100755 tools/testing/selftests/acpi/test_unprobed_devices.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 8333ead448c4..1f58949c9e51 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -294,6 +294,7 @@ F:	include/linux/fwnode.h
 F:	include/linux/fw_table.h
 F:	lib/fw_table.c
 F:	scripts/acpi/acpi-extract-ids
+F:	tools/testing/selftests/acpi/
 F:	tools/power/acpi/
 
 ACPI APEI
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index e1504833654d..3107301ea4f3 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
+TARGETS += acpi
 TARGETS += alsa
 TARGETS += amd-pstate
 TARGETS += arm64
diff --git a/tools/testing/selftests/acpi/.gitignore b/tools/testing/selftests/acpi/.gitignore
new file mode 100644
index 000000000000..3c520e8a1962
--- /dev/null
+++ b/tools/testing/selftests/acpi/.gitignore
@@ -0,0 +1 @@
+id_list
diff --git a/tools/testing/selftests/acpi/Makefile b/tools/testing/selftests/acpi/Makefile
new file mode 100644
index 000000000000..b80d4fb797ac
--- /dev/null
+++ b/tools/testing/selftests/acpi/Makefile
@@ -0,0 +1,21 @@
+PY3 = $(shell which python3 2>/dev/null)
+
+ifneq ($(PY3),)
+
+TEST_PROGS := test_unprobed_devices.sh
+TEST_GEN_FILES := id_list
+TEST_FILES := id_ignore_list
+
+include ../lib.mk
+
+$(OUTPUT)/id_list:
+	$(top_srcdir)/scripts/acpi/acpi-extract-ids -d $(top_srcdir) > $@
+
+else
+
+all: no_py3_warning
+
+no_py3_warning:
+	@echo "Missing python3. This test will be skipped."
+
+endif
\ No newline at end of file
diff --git a/tools/testing/selftests/acpi/id_ignore_list b/tools/testing/selftests/acpi/id_ignore_list
new file mode 100644
index 000000000000..86ddf4b0a55a
--- /dev/null
+++ b/tools/testing/selftests/acpi/id_ignore_list
@@ -0,0 +1,3 @@
+PNP0A05
+PNP0A06
+ACPI0004
\ No newline at end of file
diff --git a/tools/testing/selftests/acpi/test_unprobed_devices.sh b/tools/testing/selftests/acpi/test_unprobed_devices.sh
new file mode 100755
index 000000000000..23e52833c475
--- /dev/null
+++ b/tools/testing/selftests/acpi/test_unprobed_devices.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2023 Collabora Ltd
+#
+# Inspired by the tools/testing/selftests/dt/test_unprobed_devices.sh
+# script, adapted for the ACPI use case.
+#
+# This script checks whether devices declared in the ACPI namespace and
+# supported by the kernel are correctly bound to a driver.
+#
+# To do this, two lists are used:
+# * a list of ACPI IDs matched by existing drivers
+# * a list of IDs that should be ignored
+#
+
+DIR="$(dirname "$(readlink -f "$0")")"
+
+KTAP_HELPERS="${DIR}/../kselftest/ktap_helpers.sh"
+if ! source "$KTAP_HELPERS"; then
+	exit 4
+fi
+
+ACPI_SYSTEM_DIR="/sys/devices/LNXSYSTM:00"
+ID_IGNORE_LIST="${DIR}"/id_ignore_list
+ID_LIST="${DIR}"/id_list
+
+PCI_CLASS_BRIDGE_HOST="0x0600"
+PCI_CLASS_BRIDGE_ISA="0x0601"
+
+ktap_print_header
+
+if [[ ! -d "${ACPI_SYSTEM_DIR}" ]]; then
+	ktap_skip_all "${ACPI_SYSTEM_DIR} doesn't exist."
+	exit "${KSFT_SKIP}"
+fi
+
+# The ACPI specification mandates that ACPI objects representing devices on
+# non-enumerable and enumerable busses contain a _HID or an _ADR
+# identification object respectively. Get a list of devices of both types,
+# by searching the ACPI sysfs subtree for directories containing a hid or
+# adr attribute.
+supp_dev_paths=$(while IFS=$'\n' read -r dev_path; do
+	if [ ! -f "${dev_path}"/hid ] && [ ! -f "${dev_path}"/adr ]; then
+		continue
+	fi
+
+	# Check if the device is present, enabled, and functioning properly
+	status="${dev_path}/status"
+	if [ -f "${status}" ]; then
+		status_hex=$(($(cat "${status}")))
+
+		if [ $((status_hex & 1)) -eq 0 ] ||
+			[ $((status_hex >> 1 & 1)) -eq 0 ] ||
+			[ $((status_hex >> 3 & 1)) -eq 0 ]; then
+			continue
+		fi
+	fi
+
+	if [ -n "$(find -L "${dev_path}" -maxdepth 1 -name "physical_node*" -print -quit)" ]; then
+		for node in "${dev_path}"/physical_node*; do
+			# Ignore devices without a subsystem, devices that link to
+			# other devices, and class devices
+			if [ ! -d "${node}/subsystem" ] ||
+				[ -d "${node}/device" ] ||
+				[[ "$(readlink -f "${node}/subsystem")" == /sys/class/* ]]; then
+				continue
+			fi
+
+			echo "${node}"
+		done
+	fi
+done < <(find ${ACPI_SYSTEM_DIR} -name uevent -exec dirname {} \;))
+
+supp_dev_paths_num=$(echo "${supp_dev_paths}" | wc -w)
+ktap_set_plan "${supp_dev_paths_num}"
+
+# Iterate over ACPI devices
+for dev_path in ${supp_dev_paths}; do
+	if [ -f "${dev_path}/firmware_node/path" ]; then
+		acpi_path="$(<"${dev_path}"/firmware_node/path)"
+	fi
+
+	dev_link=$(readlink -f "${dev_path}")
+	desc="${acpi_path}-${dev_link#/sys/devices/}"
+
+	if [ -f "${dev_path}/firmware_node/hid" ]; then
+		hid="$(<"${dev_path}"/firmware_node/hid)"
+
+		if [ -f "${dev_path}/firmware_node/modalias" ]; then
+			modalias=$(<"${dev_path}/firmware_node/modalias")
+			cid=$(echo "${modalias}" | cut -d':' -f3)
+
+			# Skip devices with ignored HID/CID
+			if ignored_id=$(grep -i "${hid}" "${ID_IGNORE_LIST}" ||
+				{ [ -n "${cid}" ] && grep -i "${cid}" "${ID_IGNORE_LIST}"; }); then
+				ktap_print_msg "ID ${ignored_id} ignored [SKIP]"
+				ktap_test_skip "${desc}"
+				continue
+			fi
+			# Skip devices with unsupported HID/CID
+			if [[ "${hid}" != LNX* ]] && ! grep -x -q -i "${hid}" "${ID_LIST}"; then
+				if [ -z "${cid}" ] || ! grep -x -q -i "${cid}" "${ID_LIST}"; then
+					ktap_print_msg "no match for ${hid}${cid:+:${cid}} found \
+						in the supported IDs list [SKIP]"
+					ktap_test_skip "${desc}"
+					continue
+				fi
+			fi
+		fi
+	fi
+
+	# Skip bridges that don't require a driver
+	if [ -f "${dev_path}/class" ]; then
+		class=$(<"${dev_path}"/class)
+		if [[ ${class} == ${PCI_CLASS_BRIDGE_HOST}* ]] ||
+			[[ ${class} == ${PCI_CLASS_BRIDGE_ISA}* ]]; then
+			ktap_print_msg "device linked to ${desc} does not require a driver [SKIP]"
+			ktap_test_skip "${desc}"
+			continue
+		fi
+	fi
+
+	# Search for the driver in both the device folder and the companion's folder
+	if [ -d "${dev_path}/driver" ] || [ -d "${dev_path}/firmware_node/driver" ]; then
+		ktap_test_pass "${desc}"
+	# Skip char devices
+	elif [ -f "${dev_path}/dev" ]; then
+		ktap_print_msg "${desc} is a char device [SKIP]"
+		ktap_test_skip "${desc}"
+		continue
+	else
+		ktap_test_fail "${desc}"
+	fi
+
+done
+
+ktap_finished
-- 
2.30.2
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.