test_discoverable_devices.py 10 KB

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