| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- #!/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()
|