From 34ff636f322c6d5e4f0a52338fa63a7dff09a495 Mon Sep 17 00:00:00 2001 From: Joel Fernandes Date: Sat, 18 Jul 2026 13:15:36 -0400 Subject: scftorture: Count single_rpc offline failures in statistics output scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- .../testing/selftests/devices/error_logs/Makefile | 3 + .../devices/error_logs/test_device_error_logs.py | 85 +++++ tools/testing/selftests/devices/probe/Makefile | 4 + .../probe/boards/Dell Inc.,XPS 13 9300.yaml | 40 +++ .../devices/probe/boards/google,spherion.yaml | 54 ++++ .../devices/probe/test_discoverable_devices.py | 358 +++++++++++++++++++++ 6 files changed, 544 insertions(+) create mode 100644 tools/testing/selftests/devices/error_logs/Makefile create mode 100755 tools/testing/selftests/devices/error_logs/test_device_error_logs.py create mode 100644 tools/testing/selftests/devices/probe/Makefile create mode 100644 tools/testing/selftests/devices/probe/boards/Dell Inc.,XPS 13 9300.yaml create mode 100644 tools/testing/selftests/devices/probe/boards/google,spherion.yaml create mode 100755 tools/testing/selftests/devices/probe/test_discoverable_devices.py (limited to 'tools/testing/selftests/devices') diff --git a/tools/testing/selftests/devices/error_logs/Makefile b/tools/testing/selftests/devices/error_logs/Makefile new file mode 100644 index 000000000..d546c3fb0 --- /dev/null +++ b/tools/testing/selftests/devices/error_logs/Makefile @@ -0,0 +1,3 @@ +TEST_PROGS := test_device_error_logs.py + +include ../../lib.mk diff --git a/tools/testing/selftests/devices/error_logs/test_device_error_logs.py b/tools/testing/selftests/devices/error_logs/test_device_error_logs.py new file mode 100755 index 000000000..3dd56c8ec --- /dev/null +++ b/tools/testing/selftests/devices/error_logs/test_device_error_logs.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2024 Collabora Ltd +# +# This test checks for the presence of error (or more critical) log messages +# coming from devices in the kernel log. +# +# One failed test case is reported for each device that has outputted error +# logs. Devices with no errors do not produce a passing test case to avoid +# polluting the results, therefore a successful run will list 0 tests run. +# + +import glob +import os +import re +import sys + +# Allow ksft module to be imported from different directory +this_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(this_dir, "../../kselftest/")) + +import ksft + +kmsg = "/dev/kmsg" + +RE_log = re.compile( + r"(?P[0-9]+),(?P[0-9]+),(?P[0-9]+),(?P[^;]*)(,[^;]*)*;(?P.*)" +) +RE_tag = re.compile(r" (?P[^=]+)=(?P.*)") + +PREFIX_ERROR = 3 + +logs = [] +error_log_per_device = {} + + +def parse_kmsg(): + current_log = {} + + with open(kmsg) as f: + os.set_blocking(f.fileno(), False) + + for line in f: + tag_line = RE_tag.match(line) + log_line = RE_log.match(line) + + if log_line: + if current_log: + logs.append(current_log) # Save last log + + current_log = { + "prefix": int(log_line.group("prefix")), + "sequence": int(log_line.group("sequence")), + "timestamp": int(log_line.group("timestamp")), + "flag": log_line.group("flag"), + "message": log_line.group("message"), + } + elif tag_line: + current_log[tag_line.group("key")] = tag_line.group("value") + + +def generate_per_device_error_log(): + for log in logs: + if log.get("DEVICE") and log["prefix"] <= PREFIX_ERROR: + if not error_log_per_device.get(log["DEVICE"]): + error_log_per_device[log["DEVICE"]] = [] + error_log_per_device[log["DEVICE"]].append(log) + + +parse_kmsg() + +generate_per_device_error_log() +num_tests = len(error_log_per_device) + +ksft.print_header() +ksft.set_plan(num_tests) + +for device in error_log_per_device: + for log in error_log_per_device[device]: + ksft.print_msg(log["message"]) + ksft.test_result_fail(device) +if num_tests == 0: + ksft.print_msg("No device error logs found") +ksft.finished() diff --git a/tools/testing/selftests/devices/probe/Makefile b/tools/testing/selftests/devices/probe/Makefile new file mode 100644 index 000000000..f630108c3 --- /dev/null +++ b/tools/testing/selftests/devices/probe/Makefile @@ -0,0 +1,4 @@ +TEST_PROGS := test_discoverable_devices.py +TEST_FILES := boards + +include ../../lib.mk diff --git a/tools/testing/selftests/devices/probe/boards/Dell Inc.,XPS 13 9300.yaml b/tools/testing/selftests/devices/probe/boards/Dell Inc.,XPS 13 9300.yaml new file mode 100644 index 000000000..ff932eb19 --- /dev/null +++ b/tools/testing/selftests/devices/probe/boards/Dell Inc.,XPS 13 9300.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# This is the device definition for the XPS 13 9300. +# The filename "Dell Inc.,XPS 13 9300" was chosen following the format +# "Vendor,Product", where Vendor comes from +# /sys/devices/virtual/dmi/id/sys_vendor, and Product comes from +# /sys/devices/virtual/dmi/id/product_name. +# +# See google,spherion.yaml for more information. +# +- type: pci-controller + # This machine has a single PCI host controller so it's valid to not have any + # key to identify the controller. If it had more than one controller, the UID + # of the controller from ACPI could be used to distinguish as follows: + #acpi-uid: 0 + devices: + - path: 14.0 + type: usb-controller + usb-version: 2 + devices: + - path: 9 + name: camera + interfaces: [0, 1, 2, 3] + - path: 10 + name: bluetooth + interfaces: [0, 1] + - path: 2.0 + name: gpu + - path: 4.0 + name: thermal + - path: 12.0 + name: sensors + - path: 14.3 + name: wifi + - path: 1d.0/0.0 + name: ssd + - path: 1d.7/0.0 + name: sdcard-reader + - path: 1f.3 + name: audio diff --git a/tools/testing/selftests/devices/probe/boards/google,spherion.yaml b/tools/testing/selftests/devices/probe/boards/google,spherion.yaml new file mode 100644 index 000000000..3ea843324 --- /dev/null +++ b/tools/testing/selftests/devices/probe/boards/google,spherion.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# This is the device definition for the Google Spherion Chromebook. +# The filename "google,spherion" comes from the Devicetree compatible, so this +# file will be automatically used when the test is run on that machine. +# +# The top-level is a list of controllers, either for USB or PCI(e). +# Every controller needs to have a 'type' key set to either 'usb-controller' or +# 'pci-controller'. +# Every controller needs to be uniquely identified on the platform. To achieve +# this, several optional keys can be used: +# - dt-mmio: identify the MMIO address of the controller as defined in the +# Devicetree. +# - of-fullname-regex: regular expression to match against the OF_FULLNAME +# property. Useful when the controller's address is not unique across other +# sibling controllers. In this case, dt-mmio can't be used, and this property +# allows the matching to include parent nodes as well to make it unique. +# - usb-version: for USB controllers to differentiate between USB3 and USB2 +# buses sharing the same controller. +# - acpi-uid: _UID property of the controller as supplied by the ACPI. Useful to +# distinguish between multiple PCI host controllers. +# +# The 'devices' key defines a list of devices that are accessible under that +# controller. A device might be a leaf device or another controller (see +# 'Dell Inc.,XPS 13 9300.yaml'). +# +# The 'path' key is needed for every child device (that is, not top-level) to +# define how to reach this device from the parent controller. For USB devices it +# follows the format \d(.\d)* and denotes the port in the hub at each level in +# the USB topology. For PCI devices it follows the format \d.\d(/\d.\d)* +# denoting the device (identified by device-function pair) at each level in the +# PCI topology. +# +# The 'name' key is used in the leaf devices to name the device for clarity in +# the test output. +# +# For USB leaf devices, the 'interfaces' key should contain a list of the +# interfaces in that device that should be bound to a driver. +# +- type: usb-controller + dt-mmio: 11200000 + usb-version: 2 + devices: + - path: 1.4.1 + interfaces: [0, 1] + name: camera + - path: 1.4.2 + interfaces: [0, 1] + name: bluetooth +- type: pci-controller + dt-mmio: 11230000 + devices: + - path: 0.0/0.0 + name: wifi diff --git a/tools/testing/selftests/devices/probe/test_discoverable_devices.py b/tools/testing/selftests/devices/probe/test_discoverable_devices.py new file mode 100755 index 000000000..d7a2bb91c --- /dev/null +++ b/tools/testing/selftests/devices/probe/test_discoverable_devices.py @@ -0,0 +1,358 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 Collabora Ltd +# +# This script tests for presence and driver binding of devices from discoverable +# buses (ie USB, PCI). +# +# The per-platform YAML file defining the devices to be tested is stored inside +# the boards/ directory and chosen based on DT compatible or DMI IDs (sys_vendor +# and product_name). +# +# See boards/google,spherion.yaml and boards/'Dell Inc.,XPS 13 9300.yaml' for +# the description and examples of the file structure and vocabulary. +# + +import argparse +import glob +import os +import re +import sys +import yaml + +# Allow ksft module to be imported from different directory +this_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(this_dir, "../../kselftest/")) + +import ksft + +pci_controllers = [] +usb_controllers = [] + +sysfs_usb_devices = "/sys/bus/usb/devices/" + + +def find_pci_controller_dirs(): + sysfs_devices = "/sys/devices" + pci_controller_sysfs_dir = "pci[0-9a-f]{4}:[0-9a-f]{2}" + + dir_regex = re.compile(pci_controller_sysfs_dir) + for path, dirs, _ in os.walk(sysfs_devices): + for d in dirs: + if dir_regex.match(d): + pci_controllers.append(os.path.join(path, d)) + + +def find_usb_controller_dirs(): + usb_controller_sysfs_dir = r"usb[\d]+" + + dir_regex = re.compile(usb_controller_sysfs_dir) + for d in os.scandir(sysfs_usb_devices): + if dir_regex.match(d.name): + usb_controllers.append(os.path.realpath(d.path)) + + +def get_dt_mmio(sysfs_dev_dir): + re_dt_mmio = re.compile("OF_FULLNAME=.*@([0-9a-f]+)") + dt_mmio = None + + # PCI controllers' sysfs don't have an of_node, so have to read it from the + # parent + while not dt_mmio: + try: + with open(os.path.join(sysfs_dev_dir, "uevent")) as f: + dt_mmio = re_dt_mmio.search(f.read()).group(1) + return dt_mmio + except: + pass + sysfs_dev_dir = os.path.dirname(sysfs_dev_dir) + + +def get_of_fullname(sysfs_dev_dir): + re_of_fullname = re.compile("OF_FULLNAME=(.*)") + of_full_name = None + + # PCI controllers' sysfs don't have an of_node, so have to read it from the + # parent + while not of_full_name: + try: + with open(os.path.join(sysfs_dev_dir, "uevent")) as f: + of_fullname = re_of_fullname.search(f.read()).group(1) + return of_fullname + except: + pass + sysfs_dev_dir = os.path.dirname(sysfs_dev_dir) + + +def get_acpi_uid(sysfs_dev_dir): + with open(os.path.join(sysfs_dev_dir, "firmware_node", "uid")) as f: + return f.read() + + +def get_usb_version(sysfs_dev_dir): + re_usb_version = re.compile(r"PRODUCT=.*/(\d)/.*") + with open(os.path.join(sysfs_dev_dir, "uevent")) as f: + return int(re_usb_version.search(f.read()).group(1)) + + +def get_usb_busnum(sysfs_dev_dir): + re_busnum = re.compile("BUSNUM=(.*)") + with open(os.path.join(sysfs_dev_dir, "uevent")) as f: + return int(re_busnum.search(f.read()).group(1)) + + +def find_controller_in_sysfs(controller, parent_sysfs=None): + if controller["type"] == "pci-controller": + controllers = pci_controllers + elif controller["type"] == "usb-controller": + controllers = usb_controllers + + result_controllers = [] + + for c in controllers: + if parent_sysfs and parent_sysfs not in c: + continue + + if controller.get("dt-mmio"): + if str(controller["dt-mmio"]) != get_dt_mmio(c): + continue + + if controller.get("of-fullname-regex"): + re_of_fullname = re.compile(str(controller["of-fullname-regex"])) + if not re_of_fullname.match(get_of_fullname(c)): + continue + + if controller.get("usb-version"): + if controller["usb-version"] != get_usb_version(c): + continue + + if controller.get("acpi-uid"): + if controller["acpi-uid"] != get_acpi_uid(c): + continue + + result_controllers.append(c) + + return result_controllers + + +def is_controller(device): + return device.get("type") and "controller" in device.get("type") + + +def path_to_dir(parent_sysfs, dev_type, path): + if dev_type == "usb-device": + usb_dev_sysfs_fmt = "{}-{}" + busnum = get_usb_busnum(parent_sysfs) + dirname = os.path.join( + sysfs_usb_devices, usb_dev_sysfs_fmt.format(busnum, path) + ) + return [os.path.realpath(dirname)] + else: + pci_dev_sysfs_fmt = "????:??:{}" + path_glob = "" + for dev_func in path.split("/"): + dev_func = dev_func.zfill(4) + path_glob = os.path.join(path_glob, pci_dev_sysfs_fmt.format(dev_func)) + + dir_list = glob.glob(os.path.join(parent_sysfs, path_glob)) + + return dir_list + + +def find_in_sysfs(device, parent_sysfs=None): + if parent_sysfs and device.get("path"): + pathdirs = path_to_dir( + parent_sysfs, device["meta"]["type"], str(device["path"]) + ) + if len(pathdirs) != 1: + # Early return to report error + return pathdirs + pathdir = pathdirs[0] + sysfs_path = os.path.join(parent_sysfs, pathdir) + else: + sysfs_path = parent_sysfs + + if is_controller(device): + return find_controller_in_sysfs(device, sysfs_path) + else: + return [sysfs_path] + + +def check_driver_presence(sysfs_dir, current_node): + if current_node["meta"]["type"] == "usb-device": + usb_intf_fmt = "*-*:*.{}" + + interfaces = [] + for i in current_node["interfaces"]: + interfaces.append((i, usb_intf_fmt.format(i))) + + for intf_num, intf_dir_fmt in interfaces: + test_name = f"{current_node['meta']['pathname']}.{intf_num}.driver" + + intf_dirs = glob.glob(os.path.join(sysfs_dir, intf_dir_fmt)) + if len(intf_dirs) != 1: + ksft.test_result_fail(test_name) + continue + intf_dir = intf_dirs[0] + + driver_link = os.path.join(sysfs_dir, intf_dir, "driver") + ksft.test_result(os.path.isdir(driver_link), test_name) + else: + driver_link = os.path.join(sysfs_dir, "driver") + test_name = current_node["meta"]["pathname"] + ".driver" + ksft.test_result(os.path.isdir(driver_link), test_name) + + +def generate_pathname(device): + pathname = "" + + if device.get("path"): + pathname = str(device["path"]) + + if device.get("type"): + dev_type = device["type"] + if device.get("usb-version"): + dev_type = dev_type.replace("usb", "usb" + str(device["usb-version"])) + if device.get("acpi-uid") is not None: + dev_type = dev_type.replace("pci", "pci" + str(device["acpi-uid"])) + pathname = pathname + "/" + dev_type + + if device.get("dt-mmio"): + pathname += "@" + str(device["dt-mmio"]) + + if device.get("of-fullname-regex"): + pathname += "-" + str(device["of-fullname-regex"]) + + if device.get("name"): + pathname = pathname + "/" + device["name"] + + return pathname + + +def fill_meta_keys(child, parent=None): + child["meta"] = {} + + if parent: + child["meta"]["type"] = parent["type"].replace("controller", "device") + + pathname = generate_pathname(child) + if parent: + pathname = parent["meta"]["pathname"] + "/" + pathname + child["meta"]["pathname"] = pathname + + +def parse_device_tree_node(current_node, parent_sysfs=None): + if not parent_sysfs: + fill_meta_keys(current_node) + + sysfs_dirs = find_in_sysfs(current_node, parent_sysfs) + if len(sysfs_dirs) != 1: + if len(sysfs_dirs) == 0: + ksft.test_result_fail( + f"Couldn't find in sysfs: {current_node['meta']['pathname']}" + ) + else: + ksft.test_result_fail( + f"Found multiple sysfs entries for {current_node['meta']['pathname']}: {sysfs_dirs}" + ) + return + sysfs_dir = sysfs_dirs[0] + + if not is_controller(current_node): + ksft.test_result( + os.path.exists(sysfs_dir), current_node["meta"]["pathname"] + ".device" + ) + check_driver_presence(sysfs_dir, current_node) + else: + for child_device in current_node["devices"]: + fill_meta_keys(child_device, current_node) + parse_device_tree_node(child_device, sysfs_dir) + + +def count_tests(device_trees): + test_count = 0 + + def parse_node(device): + nonlocal test_count + if device.get("devices"): + for child in device["devices"]: + parse_node(child) + else: + if device.get("interfaces"): + test_count += len(device["interfaces"]) + else: + test_count += 1 + test_count += 1 + + for device_tree in device_trees: + parse_node(device_tree) + + return test_count + + +def get_board_filenames(): + filenames = [] + + platform_compatible_file = "/proc/device-tree/compatible" + if os.path.exists(platform_compatible_file): + with open(platform_compatible_file) as f: + for line in f: + filenames.extend(line.split("\0")) + else: + dmi_id_dir = "/sys/devices/virtual/dmi/id" + vendor_dmi_file = os.path.join(dmi_id_dir, "sys_vendor") + product_dmi_file = os.path.join(dmi_id_dir, "product_name") + + with open(vendor_dmi_file) as f: + vendor = f.read().replace("\n", "") + with open(product_dmi_file) as f: + product = f.read().replace("\n", "") + + filenames = [vendor + "," + product] + + return filenames + + +def run_test(yaml_file): + ksft.print_msg(f"Using board file: {yaml_file}") + + with open(yaml_file) as f: + device_trees = yaml.safe_load(f) + + ksft.set_plan(count_tests(device_trees)) + + for device_tree in device_trees: + parse_device_tree_node(device_tree) + + +parser = argparse.ArgumentParser() +parser.add_argument( + "--boards-dir", default="boards", help="Directory containing the board YAML files" +) +args = parser.parse_args() + +find_pci_controller_dirs() +find_usb_controller_dirs() + +ksft.print_header() + +if not os.path.exists(args.boards_dir): + ksft.print_msg(f"Boards directory '{args.boards_dir}' doesn't exist") + ksft.exit_fail() + +board_file = "" +for board_filename in get_board_filenames(): + full_board_filename = os.path.join(args.boards_dir, board_filename + ".yaml") + + if os.path.exists(full_board_filename): + board_file = full_board_filename + break + +if not board_file: + ksft.print_msg("No matching board file found") + ksft.exit_fail() + +run_test(board_file) + +ksft.finished() -- cgit v1.3.1