Path: blob/master/tools/testing/selftests/devices/probe/test_discoverable_devices.py
26288 views
#!/usr/bin/python31# SPDX-License-Identifier: GPL-2.02#3# Copyright (c) 2023 Collabora Ltd4#5# This script tests for presence and driver binding of devices from discoverable6# buses (ie USB, PCI).7#8# The per-platform YAML file defining the devices to be tested is stored inside9# the boards/ directory and chosen based on DT compatible or DMI IDs (sys_vendor10# and product_name).11#12# See boards/google,spherion.yaml and boards/'Dell Inc.,XPS 13 9300.yaml' for13# the description and examples of the file structure and vocabulary.14#1516import argparse17import glob18import os19import re20import sys21import yaml2223# Allow ksft module to be imported from different directory24this_dir = os.path.dirname(os.path.realpath(__file__))25sys.path.append(os.path.join(this_dir, "../../kselftest/"))2627import ksft2829pci_controllers = []30usb_controllers = []3132sysfs_usb_devices = "/sys/bus/usb/devices/"333435def find_pci_controller_dirs():36sysfs_devices = "/sys/devices"37pci_controller_sysfs_dir = "pci[0-9a-f]{4}:[0-9a-f]{2}"3839dir_regex = re.compile(pci_controller_sysfs_dir)40for path, dirs, _ in os.walk(sysfs_devices):41for d in dirs:42if dir_regex.match(d):43pci_controllers.append(os.path.join(path, d))444546def find_usb_controller_dirs():47usb_controller_sysfs_dir = r"usb[\d]+"4849dir_regex = re.compile(usb_controller_sysfs_dir)50for d in os.scandir(sysfs_usb_devices):51if dir_regex.match(d.name):52usb_controllers.append(os.path.realpath(d.path))535455def get_dt_mmio(sysfs_dev_dir):56re_dt_mmio = re.compile("OF_FULLNAME=.*@([0-9a-f]+)")57dt_mmio = None5859# PCI controllers' sysfs don't have an of_node, so have to read it from the60# parent61while not dt_mmio:62try:63with open(os.path.join(sysfs_dev_dir, "uevent")) as f:64dt_mmio = re_dt_mmio.search(f.read()).group(1)65return dt_mmio66except:67pass68sysfs_dev_dir = os.path.dirname(sysfs_dev_dir)697071def get_of_fullname(sysfs_dev_dir):72re_of_fullname = re.compile("OF_FULLNAME=(.*)")73of_full_name = None7475# PCI controllers' sysfs don't have an of_node, so have to read it from the76# parent77while not of_full_name:78try:79with open(os.path.join(sysfs_dev_dir, "uevent")) as f:80of_fullname = re_of_fullname.search(f.read()).group(1)81return of_fullname82except:83pass84sysfs_dev_dir = os.path.dirname(sysfs_dev_dir)858687def get_acpi_uid(sysfs_dev_dir):88with open(os.path.join(sysfs_dev_dir, "firmware_node", "uid")) as f:89return f.read()909192def get_usb_version(sysfs_dev_dir):93re_usb_version = re.compile(r"PRODUCT=.*/(\d)/.*")94with open(os.path.join(sysfs_dev_dir, "uevent")) as f:95return int(re_usb_version.search(f.read()).group(1))969798def get_usb_busnum(sysfs_dev_dir):99re_busnum = re.compile("BUSNUM=(.*)")100with open(os.path.join(sysfs_dev_dir, "uevent")) as f:101return int(re_busnum.search(f.read()).group(1))102103104def find_controller_in_sysfs(controller, parent_sysfs=None):105if controller["type"] == "pci-controller":106controllers = pci_controllers107elif controller["type"] == "usb-controller":108controllers = usb_controllers109110result_controllers = []111112for c in controllers:113if parent_sysfs and parent_sysfs not in c:114continue115116if controller.get("dt-mmio"):117if str(controller["dt-mmio"]) != get_dt_mmio(c):118continue119120if controller.get("of-fullname-regex"):121re_of_fullname = re.compile(str(controller["of-fullname-regex"]))122if not re_of_fullname.match(get_of_fullname(c)):123continue124125if controller.get("usb-version"):126if controller["usb-version"] != get_usb_version(c):127continue128129if controller.get("acpi-uid"):130if controller["acpi-uid"] != get_acpi_uid(c):131continue132133result_controllers.append(c)134135return result_controllers136137138def is_controller(device):139return device.get("type") and "controller" in device.get("type")140141142def path_to_dir(parent_sysfs, dev_type, path):143if dev_type == "usb-device":144usb_dev_sysfs_fmt = "{}-{}"145busnum = get_usb_busnum(parent_sysfs)146dirname = os.path.join(147sysfs_usb_devices, usb_dev_sysfs_fmt.format(busnum, path)148)149return [os.path.realpath(dirname)]150else:151pci_dev_sysfs_fmt = "????:??:{}"152path_glob = ""153for dev_func in path.split("/"):154dev_func = dev_func.zfill(4)155path_glob = os.path.join(path_glob, pci_dev_sysfs_fmt.format(dev_func))156157dir_list = glob.glob(os.path.join(parent_sysfs, path_glob))158159return dir_list160161162def find_in_sysfs(device, parent_sysfs=None):163if parent_sysfs and device.get("path"):164pathdirs = path_to_dir(165parent_sysfs, device["meta"]["type"], str(device["path"])166)167if len(pathdirs) != 1:168# Early return to report error169return pathdirs170pathdir = pathdirs[0]171sysfs_path = os.path.join(parent_sysfs, pathdir)172else:173sysfs_path = parent_sysfs174175if is_controller(device):176return find_controller_in_sysfs(device, sysfs_path)177else:178return [sysfs_path]179180181def check_driver_presence(sysfs_dir, current_node):182if current_node["meta"]["type"] == "usb-device":183usb_intf_fmt = "*-*:*.{}"184185interfaces = []186for i in current_node["interfaces"]:187interfaces.append((i, usb_intf_fmt.format(i)))188189for intf_num, intf_dir_fmt in interfaces:190test_name = f"{current_node['meta']['pathname']}.{intf_num}.driver"191192intf_dirs = glob.glob(os.path.join(sysfs_dir, intf_dir_fmt))193if len(intf_dirs) != 1:194ksft.test_result_fail(test_name)195continue196intf_dir = intf_dirs[0]197198driver_link = os.path.join(sysfs_dir, intf_dir, "driver")199ksft.test_result(os.path.isdir(driver_link), test_name)200else:201driver_link = os.path.join(sysfs_dir, "driver")202test_name = current_node["meta"]["pathname"] + ".driver"203ksft.test_result(os.path.isdir(driver_link), test_name)204205206def generate_pathname(device):207pathname = ""208209if device.get("path"):210pathname = str(device["path"])211212if device.get("type"):213dev_type = device["type"]214if device.get("usb-version"):215dev_type = dev_type.replace("usb", "usb" + str(device["usb-version"]))216if device.get("acpi-uid") is not None:217dev_type = dev_type.replace("pci", "pci" + str(device["acpi-uid"]))218pathname = pathname + "/" + dev_type219220if device.get("dt-mmio"):221pathname += "@" + str(device["dt-mmio"])222223if device.get("of-fullname-regex"):224pathname += "-" + str(device["of-fullname-regex"])225226if device.get("name"):227pathname = pathname + "/" + device["name"]228229return pathname230231232def fill_meta_keys(child, parent=None):233child["meta"] = {}234235if parent:236child["meta"]["type"] = parent["type"].replace("controller", "device")237238pathname = generate_pathname(child)239if parent:240pathname = parent["meta"]["pathname"] + "/" + pathname241child["meta"]["pathname"] = pathname242243244def parse_device_tree_node(current_node, parent_sysfs=None):245if not parent_sysfs:246fill_meta_keys(current_node)247248sysfs_dirs = find_in_sysfs(current_node, parent_sysfs)249if len(sysfs_dirs) != 1:250if len(sysfs_dirs) == 0:251ksft.test_result_fail(252f"Couldn't find in sysfs: {current_node['meta']['pathname']}"253)254else:255ksft.test_result_fail(256f"Found multiple sysfs entries for {current_node['meta']['pathname']}: {sysfs_dirs}"257)258return259sysfs_dir = sysfs_dirs[0]260261if not is_controller(current_node):262ksft.test_result(263os.path.exists(sysfs_dir), current_node["meta"]["pathname"] + ".device"264)265check_driver_presence(sysfs_dir, current_node)266else:267for child_device in current_node["devices"]:268fill_meta_keys(child_device, current_node)269parse_device_tree_node(child_device, sysfs_dir)270271272def count_tests(device_trees):273test_count = 0274275def parse_node(device):276nonlocal test_count277if device.get("devices"):278for child in device["devices"]:279parse_node(child)280else:281if device.get("interfaces"):282test_count += len(device["interfaces"])283else:284test_count += 1285test_count += 1286287for device_tree in device_trees:288parse_node(device_tree)289290return test_count291292293def get_board_filenames():294filenames = []295296platform_compatible_file = "/proc/device-tree/compatible"297if os.path.exists(platform_compatible_file):298with open(platform_compatible_file) as f:299for line in f:300filenames.extend(line.split("\0"))301else:302dmi_id_dir = "/sys/devices/virtual/dmi/id"303vendor_dmi_file = os.path.join(dmi_id_dir, "sys_vendor")304product_dmi_file = os.path.join(dmi_id_dir, "product_name")305306with open(vendor_dmi_file) as f:307vendor = f.read().replace("\n", "")308with open(product_dmi_file) as f:309product = f.read().replace("\n", "")310311filenames = [vendor + "," + product]312313return filenames314315316def run_test(yaml_file):317ksft.print_msg(f"Using board file: {yaml_file}")318319with open(yaml_file) as f:320device_trees = yaml.safe_load(f)321322ksft.set_plan(count_tests(device_trees))323324for device_tree in device_trees:325parse_device_tree_node(device_tree)326327328parser = argparse.ArgumentParser()329parser.add_argument(330"--boards-dir", default="boards", help="Directory containing the board YAML files"331)332args = parser.parse_args()333334find_pci_controller_dirs()335find_usb_controller_dirs()336337ksft.print_header()338339if not os.path.exists(args.boards_dir):340ksft.print_msg(f"Boards directory '{args.boards_dir}' doesn't exist")341ksft.exit_fail()342343board_file = ""344for board_filename in get_board_filenames():345full_board_filename = os.path.join(args.boards_dir, board_filename + ".yaml")346347if os.path.exists(full_board_filename):348board_file = full_board_filename349break350351if not board_file:352ksft.print_msg("No matching board file found")353ksft.exit_fail()354355run_test(board_file)356357ksft.finished()358359360