| #!/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 glob |
| import ksft |
| import os |
| import re |
| import sys |
| import yaml |
| |
| 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 = "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_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("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("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("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) |
| |
| |
| find_pci_controller_dirs() |
| find_usb_controller_dirs() |
| |
| ksft.print_header() |
| |
| board_file = "" |
| for board_filename in get_board_filenames(): |
| full_board_filename = os.path.join("boards", 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() |