1#!/usr/bin/python3 2# SPDX-License-Identifier: GPL-2.0 3# 4# Copyright (c) 2023 Collabora Ltd 5# 6# This script tests for presence and driver binding of devices from discoverable 7# buses (ie USB, PCI). 8# 9# The per-platform YAML file defining the devices to be tested is stored inside 10# the boards/ directory and chosen based on DT compatible or DMI IDs (sys_vendor 11# and product_name). 12# 13# See boards/google,spherion.yaml and boards/'Dell Inc.,XPS 13 9300.yaml' for 14# the description and examples of the file structure and vocabulary. 15# 16 17import argparse 18import glob 19import os 20import re 21import sys 22import yaml 23 24# Allow ksft module to be imported from different directory 25this_dir = os.path.dirname(os.path.realpath(__file__)) 26sys.path.append(os.path.join(this_dir, "../../kselftest/")) 27 28import ksft 29 30pci_controllers = [] 31usb_controllers = [] 32 33sysfs_usb_devices = "/sys/bus/usb/devices/" 34 35 36def find_pci_controller_dirs(): 37 sysfs_devices = "/sys/devices" 38 pci_controller_sysfs_dir = "pci[0-9a-f]{4}:[0-9a-f]{2}" 39 40 dir_regex = re.compile(pci_controller_sysfs_dir) 41 for path, dirs, _ in os.walk(sysfs_devices): 42 for d in dirs: 43 if dir_regex.match(d): 44 pci_controllers.append(os.path.join(path, d)) 45 46 47def find_usb_controller_dirs(): 48 usb_controller_sysfs_dir = r"usb[\d]+" 49 50 dir_regex = re.compile(usb_controller_sysfs_dir) 51 for d in os.scandir(sysfs_usb_devices): 52 if dir_regex.match(d.name): 53 usb_controllers.append(os.path.realpath(d.path)) 54 55 56def get_dt_mmio(sysfs_dev_dir): 57 re_dt_mmio = re.compile("OF_FULLNAME=.*@([0-9a-f]+)") 58 dt_mmio = None 59 60 # PCI controllers' sysfs don't have an of_node, so have to read it from the 61 # parent 62 while not dt_mmio: 63 try: 64 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 65 dt_mmio = re_dt_mmio.search(f.read()).group(1) 66 return dt_mmio 67 except: 68 pass 69 sysfs_dev_dir = os.path.dirname(sysfs_dev_dir) 70 71 72def get_of_fullname(sysfs_dev_dir): 73 re_of_fullname = re.compile("OF_FULLNAME=(.*)") 74 of_full_name = None 75 76 # PCI controllers' sysfs don't have an of_node, so have to read it from the 77 # parent 78 while not of_full_name: 79 try: 80 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 81 of_fullname = re_of_fullname.search(f.read()).group(1) 82 return of_fullname 83 except: 84 pass 85 sysfs_dev_dir = os.path.dirname(sysfs_dev_dir) 86 87 88def get_acpi_uid(sysfs_dev_dir): 89 with open(os.path.join(sysfs_dev_dir, "firmware_node", "uid")) as f: 90 return f.read() 91 92 93def get_usb_version(sysfs_dev_dir): 94 re_usb_version = re.compile(r"PRODUCT=.*/(\d)/.*") 95 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 96 return int(re_usb_version.search(f.read()).group(1)) 97 98 99def get_usb_busnum(sysfs_dev_dir): 100 re_busnum = re.compile("BUSNUM=(.*)") 101 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 102 return int(re_busnum.search(f.read()).group(1)) 103 104 105def find_controller_in_sysfs(controller, parent_sysfs=None): 106 if controller["type"] == "pci-controller": 107 controllers = pci_controllers 108 elif controller["type"] == "usb-controller": 109 controllers = usb_controllers 110 111 result_controllers = [] 112 113 for c in controllers: 114 if parent_sysfs and parent_sysfs not in c: 115 continue 116 117 if controller.get("dt-mmio"): 118 if str(controller["dt-mmio"]) != get_dt_mmio(c): 119 continue 120 121 if controller.get("of-fullname-regex"): 122 re_of_fullname = re.compile(str(controller["of-fullname-regex"])) 123 if not re_of_fullname.match(get_of_fullname(c)): 124 continue 125 126 if controller.get("usb-version"): 127 if controller["usb-version"] != get_usb_version(c): 128 continue 129 130 if controller.get("acpi-uid"): 131 if controller["acpi-uid"] != get_acpi_uid(c): 132 continue 133 134 result_controllers.append(c) 135 136 return result_controllers 137 138 139def is_controller(device): 140 return device.get("type") and "controller" in device.get("type") 141 142 143def path_to_dir(parent_sysfs, dev_type, path): 144 if dev_type == "usb-device": 145 usb_dev_sysfs_fmt = "{}-{}" 146 busnum = get_usb_busnum(parent_sysfs) 147 dirname = os.path.join( 148 sysfs_usb_devices, usb_dev_sysfs_fmt.format(busnum, path) 149 ) 150 return [os.path.realpath(dirname)] 151 else: 152 pci_dev_sysfs_fmt = "????:??:{}" 153 path_glob = "" 154 for dev_func in path.split("/"): 155 dev_func = dev_func.zfill(4) 156 path_glob = os.path.join(path_glob, pci_dev_sysfs_fmt.format(dev_func)) 157 158 dir_list = glob.glob(os.path.join(parent_sysfs, path_glob)) 159 160 return dir_list 161 162 163def find_in_sysfs(device, parent_sysfs=None): 164 if parent_sysfs and device.get("path"): 165 pathdirs = path_to_dir( 166 parent_sysfs, device["meta"]["type"], str(device["path"]) 167 ) 168 if len(pathdirs) != 1: 169 # Early return to report error 170 return pathdirs 171 pathdir = pathdirs[0] 172 sysfs_path = os.path.join(parent_sysfs, pathdir) 173 else: 174 sysfs_path = parent_sysfs 175 176 if is_controller(device): 177 return find_controller_in_sysfs(device, sysfs_path) 178 else: 179 return [sysfs_path] 180 181 182def check_driver_presence(sysfs_dir, current_node): 183 if current_node["meta"]["type"] == "usb-device": 184 usb_intf_fmt = "*-*:*.{}" 185 186 interfaces = [] 187 for i in current_node["interfaces"]: 188 interfaces.append((i, usb_intf_fmt.format(i))) 189 190 for intf_num, intf_dir_fmt in interfaces: 191 test_name = f"{current_node['meta']['pathname']}.{intf_num}.driver" 192 193 intf_dirs = glob.glob(os.path.join(sysfs_dir, intf_dir_fmt)) 194 if len(intf_dirs) != 1: 195 ksft.test_result_fail(test_name) 196 continue 197 intf_dir = intf_dirs[0] 198 199 driver_link = os.path.join(sysfs_dir, intf_dir, "driver") 200 ksft.test_result(os.path.isdir(driver_link), test_name) 201 else: 202 driver_link = os.path.join(sysfs_dir, "driver") 203 test_name = current_node["meta"]["pathname"] + ".driver" 204 ksft.test_result(os.path.isdir(driver_link), test_name) 205 206 207def generate_pathname(device): 208 pathname = "" 209 210 if device.get("path"): 211 pathname = str(device["path"]) 212 213 if device.get("type"): 214 dev_type = device["type"] 215 if device.get("usb-version"): 216 dev_type = dev_type.replace("usb", "usb" + str(device["usb-version"])) 217 if device.get("acpi-uid") is not None: 218 dev_type = dev_type.replace("pci", "pci" + str(device["acpi-uid"])) 219 pathname = pathname + "/" + dev_type 220 221 if device.get("dt-mmio"): 222 pathname += "@" + str(device["dt-mmio"]) 223 224 if device.get("of-fullname-regex"): 225 pathname += "-" + str(device["of-fullname-regex"]) 226 227 if device.get("name"): 228 pathname = pathname + "/" + device["name"] 229 230 return pathname 231 232 233def fill_meta_keys(child, parent=None): 234 child["meta"] = {} 235 236 if parent: 237 child["meta"]["type"] = parent["type"].replace("controller", "device") 238 239 pathname = generate_pathname(child) 240 if parent: 241 pathname = parent["meta"]["pathname"] + "/" + pathname 242 child["meta"]["pathname"] = pathname 243 244 245def parse_device_tree_node(current_node, parent_sysfs=None): 246 if not parent_sysfs: 247 fill_meta_keys(current_node) 248 249 sysfs_dirs = find_in_sysfs(current_node, parent_sysfs) 250 if len(sysfs_dirs) != 1: 251 if len(sysfs_dirs) == 0: 252 ksft.test_result_fail( 253 f"Couldn't find in sysfs: {current_node['meta']['pathname']}" 254 ) 255 else: 256 ksft.test_result_fail( 257 f"Found multiple sysfs entries for {current_node['meta']['pathname']}: {sysfs_dirs}" 258 ) 259 return 260 sysfs_dir = sysfs_dirs[0] 261 262 if not is_controller(current_node): 263 ksft.test_result( 264 os.path.exists(sysfs_dir), current_node["meta"]["pathname"] + ".device" 265 ) 266 check_driver_presence(sysfs_dir, current_node) 267 else: 268 for child_device in current_node["devices"]: 269 fill_meta_keys(child_device, current_node) 270 parse_device_tree_node(child_device, sysfs_dir) 271 272 273def count_tests(device_trees): 274 test_count = 0 275 276 def parse_node(device): 277 nonlocal test_count 278 if device.get("devices"): 279 for child in device["devices"]: 280 parse_node(child) 281 else: 282 if device.get("interfaces"): 283 test_count += len(device["interfaces"]) 284 else: 285 test_count += 1 286 test_count += 1 287 288 for device_tree in device_trees: 289 parse_node(device_tree) 290 291 return test_count 292 293 294def get_board_filenames(): 295 filenames = [] 296 297 platform_compatible_file = "/proc/device-tree/compatible" 298 if os.path.exists(platform_compatible_file): 299 with open(platform_compatible_file) as f: 300 for line in f: 301 filenames.extend(line.split("\0")) 302 else: 303 dmi_id_dir = "/sys/devices/virtual/dmi/id" 304 vendor_dmi_file = os.path.join(dmi_id_dir, "sys_vendor") 305 product_dmi_file = os.path.join(dmi_id_dir, "product_name") 306 307 with open(vendor_dmi_file) as f: 308 vendor = f.read().replace("\n", "") 309 with open(product_dmi_file) as f: 310 product = f.read().replace("\n", "") 311 312 filenames = [vendor + "," + product] 313 314 return filenames 315 316 317def run_test(yaml_file): 318 ksft.print_msg(f"Using board file: {yaml_file}") 319 320 with open(yaml_file) as f: 321 device_trees = yaml.safe_load(f) 322 323 ksft.set_plan(count_tests(device_trees)) 324 325 for device_tree in device_trees: 326 parse_device_tree_node(device_tree) 327 328 329parser = argparse.ArgumentParser() 330parser.add_argument( 331 "--boards-dir", default="boards", help="Directory containing the board YAML files" 332) 333args = parser.parse_args() 334 335find_pci_controller_dirs() 336find_usb_controller_dirs() 337 338ksft.print_header() 339 340if not os.path.exists(args.boards_dir): 341 ksft.print_msg(f"Boards directory '{args.boards_dir}' doesn't exist") 342 ksft.exit_fail() 343 344board_file = "" 345for board_filename in get_board_filenames(): 346 full_board_filename = os.path.join(args.boards_dir, board_filename + ".yaml") 347 348 if os.path.exists(full_board_filename): 349 board_file = full_board_filename 350 break 351 352if not board_file: 353 ksft.print_msg("No matching board file found") 354 ksft.exit_fail() 355 356run_test(board_file) 357 358ksft.finished() 359