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 glob 18import ksft 19import os 20import re 21import sys 22import yaml 23 24pci_controllers = [] 25usb_controllers = [] 26 27sysfs_usb_devices = "/sys/bus/usb/devices/" 28 29 30def find_pci_controller_dirs(): 31 sysfs_devices = "/sys/devices" 32 pci_controller_sysfs_dir = "pci[0-9a-f]{4}:[0-9a-f]{2}" 33 34 dir_regex = re.compile(pci_controller_sysfs_dir) 35 for path, dirs, _ in os.walk(sysfs_devices): 36 for d in dirs: 37 if dir_regex.match(d): 38 pci_controllers.append(os.path.join(path, d)) 39 40 41def find_usb_controller_dirs(): 42 usb_controller_sysfs_dir = "usb[\d]+" 43 44 dir_regex = re.compile(usb_controller_sysfs_dir) 45 for d in os.scandir(sysfs_usb_devices): 46 if dir_regex.match(d.name): 47 usb_controllers.append(os.path.realpath(d.path)) 48 49 50def get_dt_mmio(sysfs_dev_dir): 51 re_dt_mmio = re.compile("OF_FULLNAME=.*@([0-9a-f]+)") 52 dt_mmio = None 53 54 # PCI controllers' sysfs don't have an of_node, so have to read it from the 55 # parent 56 while not dt_mmio: 57 try: 58 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 59 dt_mmio = re_dt_mmio.search(f.read()).group(1) 60 return dt_mmio 61 except: 62 pass 63 sysfs_dev_dir = os.path.dirname(sysfs_dev_dir) 64 65 66def get_acpi_uid(sysfs_dev_dir): 67 with open(os.path.join(sysfs_dev_dir, "firmware_node", "uid")) as f: 68 return f.read() 69 70 71def get_usb_version(sysfs_dev_dir): 72 re_usb_version = re.compile("PRODUCT=.*/(\d)/.*") 73 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 74 return int(re_usb_version.search(f.read()).group(1)) 75 76 77def get_usb_busnum(sysfs_dev_dir): 78 re_busnum = re.compile("BUSNUM=(.*)") 79 with open(os.path.join(sysfs_dev_dir, "uevent")) as f: 80 return int(re_busnum.search(f.read()).group(1)) 81 82 83def find_controller_in_sysfs(controller, parent_sysfs=None): 84 if controller["type"] == "pci-controller": 85 controllers = pci_controllers 86 elif controller["type"] == "usb-controller": 87 controllers = usb_controllers 88 89 result_controllers = [] 90 91 for c in controllers: 92 if parent_sysfs and parent_sysfs not in c: 93 continue 94 95 if controller.get("dt-mmio"): 96 if str(controller["dt-mmio"]) != get_dt_mmio(c): 97 continue 98 99 if controller.get("usb-version"): 100 if controller["usb-version"] != get_usb_version(c): 101 continue 102 103 if controller.get("acpi-uid"): 104 if controller["acpi-uid"] != get_acpi_uid(c): 105 continue 106 107 result_controllers.append(c) 108 109 return result_controllers 110 111 112def is_controller(device): 113 return device.get("type") and "controller" in device.get("type") 114 115 116def path_to_dir(parent_sysfs, dev_type, path): 117 if dev_type == "usb-device": 118 usb_dev_sysfs_fmt = "{}-{}" 119 busnum = get_usb_busnum(parent_sysfs) 120 dirname = os.path.join( 121 sysfs_usb_devices, usb_dev_sysfs_fmt.format(busnum, path) 122 ) 123 return [os.path.realpath(dirname)] 124 else: 125 pci_dev_sysfs_fmt = "????:??:{}" 126 path_glob = "" 127 for dev_func in path.split("/"): 128 dev_func = dev_func.zfill(4) 129 path_glob = os.path.join(path_glob, pci_dev_sysfs_fmt.format(dev_func)) 130 131 dir_list = glob.glob(os.path.join(parent_sysfs, path_glob)) 132 133 return dir_list 134 135 136def find_in_sysfs(device, parent_sysfs=None): 137 if parent_sysfs and device.get("path"): 138 pathdirs = path_to_dir( 139 parent_sysfs, device["meta"]["type"], str(device["path"]) 140 ) 141 if len(pathdirs) != 1: 142 # Early return to report error 143 return pathdirs 144 pathdir = pathdirs[0] 145 sysfs_path = os.path.join(parent_sysfs, pathdir) 146 else: 147 sysfs_path = parent_sysfs 148 149 if is_controller(device): 150 return find_controller_in_sysfs(device, sysfs_path) 151 else: 152 return [sysfs_path] 153 154 155def check_driver_presence(sysfs_dir, current_node): 156 if current_node["meta"]["type"] == "usb-device": 157 usb_intf_fmt = "*-*:*.{}" 158 159 interfaces = [] 160 for i in current_node["interfaces"]: 161 interfaces.append((i, usb_intf_fmt.format(i))) 162 163 for intf_num, intf_dir_fmt in interfaces: 164 test_name = f"{current_node['meta']['pathname']}.{intf_num}.driver" 165 166 intf_dirs = glob.glob(os.path.join(sysfs_dir, intf_dir_fmt)) 167 if len(intf_dirs) != 1: 168 ksft.test_result_fail(test_name) 169 continue 170 intf_dir = intf_dirs[0] 171 172 driver_link = os.path.join(sysfs_dir, intf_dir, "driver") 173 ksft.test_result(os.path.isdir(driver_link), test_name) 174 else: 175 driver_link = os.path.join(sysfs_dir, "driver") 176 test_name = current_node["meta"]["pathname"] + ".driver" 177 ksft.test_result(os.path.isdir(driver_link), test_name) 178 179 180def generate_pathname(device): 181 pathname = "" 182 183 if device.get("path"): 184 pathname = str(device["path"]) 185 186 if device.get("type"): 187 dev_type = device["type"] 188 if device.get("usb-version"): 189 dev_type = dev_type.replace("usb", "usb" + str(device["usb-version"])) 190 if device.get("acpi-uid") is not None: 191 dev_type = dev_type.replace("pci", "pci" + str(device["acpi-uid"])) 192 pathname = pathname + "/" + dev_type 193 194 if device.get("dt-mmio"): 195 pathname += "@" + str(device["dt-mmio"]) 196 197 if device.get("name"): 198 pathname = pathname + "/" + device["name"] 199 200 return pathname 201 202 203def fill_meta_keys(child, parent=None): 204 child["meta"] = {} 205 206 if parent: 207 child["meta"]["type"] = parent["type"].replace("controller", "device") 208 209 pathname = generate_pathname(child) 210 if parent: 211 pathname = parent["meta"]["pathname"] + "/" + pathname 212 child["meta"]["pathname"] = pathname 213 214 215def parse_device_tree_node(current_node, parent_sysfs=None): 216 if not parent_sysfs: 217 fill_meta_keys(current_node) 218 219 sysfs_dirs = find_in_sysfs(current_node, parent_sysfs) 220 if len(sysfs_dirs) != 1: 221 if len(sysfs_dirs) == 0: 222 ksft.test_result_fail( 223 f"Couldn't find in sysfs: {current_node['meta']['pathname']}" 224 ) 225 else: 226 ksft.test_result_fail( 227 f"Found multiple sysfs entries for {current_node['meta']['pathname']}: {sysfs_dirs}" 228 ) 229 return 230 sysfs_dir = sysfs_dirs[0] 231 232 if not is_controller(current_node): 233 ksft.test_result( 234 os.path.exists(sysfs_dir), current_node["meta"]["pathname"] + ".device" 235 ) 236 check_driver_presence(sysfs_dir, current_node) 237 else: 238 for child_device in current_node["devices"]: 239 fill_meta_keys(child_device, current_node) 240 parse_device_tree_node(child_device, sysfs_dir) 241 242 243def count_tests(device_trees): 244 test_count = 0 245 246 def parse_node(device): 247 nonlocal test_count 248 if device.get("devices"): 249 for child in device["devices"]: 250 parse_node(child) 251 else: 252 if device.get("interfaces"): 253 test_count += len(device["interfaces"]) 254 else: 255 test_count += 1 256 test_count += 1 257 258 for device_tree in device_trees: 259 parse_node(device_tree) 260 261 return test_count 262 263 264def get_board_filenames(): 265 filenames = [] 266 267 platform_compatible_file = "/proc/device-tree/compatible" 268 if os.path.exists(platform_compatible_file): 269 with open(platform_compatible_file) as f: 270 for line in f: 271 filenames.extend(line.split("\0")) 272 else: 273 dmi_id_dir = "/sys/devices/virtual/dmi/id" 274 vendor_dmi_file = os.path.join(dmi_id_dir, "sys_vendor") 275 product_dmi_file = os.path.join(dmi_id_dir, "product_name") 276 277 with open(vendor_dmi_file) as f: 278 vendor = f.read().replace("\n", "") 279 with open(product_dmi_file) as f: 280 product = f.read().replace("\n", "") 281 282 filenames = [vendor + "," + product] 283 284 return filenames 285 286 287def run_test(yaml_file): 288 ksft.print_msg(f"Using board file: {yaml_file}") 289 290 with open(yaml_file) as f: 291 device_trees = yaml.safe_load(f) 292 293 ksft.set_plan(count_tests(device_trees)) 294 295 for device_tree in device_trees: 296 parse_device_tree_node(device_tree) 297 298 299find_pci_controller_dirs() 300find_usb_controller_dirs() 301 302ksft.print_header() 303 304board_file = "" 305for board_filename in get_board_filenames(): 306 full_board_filename = os.path.join("boards", board_filename + ".yaml") 307 308 if os.path.exists(full_board_filename): 309 board_file = full_board_filename 310 break 311 312if not board_file: 313 ksft.print_msg("No matching board file found") 314 ksft.exit_fail() 315 316run_test(board_file) 317 318ksft.finished() 319