1# Copyright (C) 2022 Intel Corporation.
2#
3# SPDX-License-Identifier: BSD-3-Clause
4#
5
6import re, os
7import logging
8
9from extractors.helpers import add_child, get_node, get_bdf_from_realpath
10from collections import defaultdict
11from pathlib import Path
12
13SYS_INPUT_DEVICES_CLASS_PATH = "/sys/class/input/"
14SYS_TTY_DEVICES_CLASS_PATH = "/sys/class/tty/"
15SYS_DISPLAYS_INFO_PATH = "/sys/class/drm/"
16
17def add_child_with_file_contents(parent_node, tag, filepath, translations = {}):
18    try:
19        with open(filepath, "r") as f:
20            res = f.read().strip()
21            if res in translations.keys():
22                add_child(parent_node, tag, translations[res])
23            else:
24                add_child(parent_node, tag, res)
25    except Exception as e:
26        logging.warning(f"Failed to read the data from {filepath}: {e}")
27
28def get_input_ids():
29    input_ids = list()
30    root_regex = re.compile("input([0-9]+)")
31    for root in filter(lambda x: x.startswith("input"), os.listdir(SYS_INPUT_DEVICES_CLASS_PATH)):
32        m = root_regex.match(root)
33        if m:
34            input_ids.append(int(m.group(1)))
35    return sorted(input_ids)
36
37def extract_inputs(device_classes_node):
38    inputs_node = add_child(device_classes_node, "inputs", None)
39    input_ids = get_input_ids()
40    for id in input_ids:
41        input_node = add_child(inputs_node, "input", None)
42        add_child_with_file_contents(input_node, "name", f"/sys/class/input/input{id}/name")
43        add_child_with_file_contents(input_node, "phys", f"/sys/class/input/input{id}/phys")
44
45def get_serial_devs():
46    return sorted(filter(lambda x: x.startswith("ttyS"), os.listdir(SYS_TTY_DEVICES_CLASS_PATH)), key=lambda x:int(x[4:]))
47
48def extract_ttys(device_classes_node):
49    ttys_node = add_child(device_classes_node, "ttys", None)
50    for serial_dev in get_serial_devs():
51        serial_node = add_child(ttys_node, "serial")
52        add_child(serial_node, "dev_path", f"/dev/{serial_dev}")
53        add_child_with_file_contents(serial_node, "type", f"{SYS_TTY_DEVICES_CLASS_PATH}{serial_dev}/type")
54
55def extract_display(board_etree):
56    display_regex = re.compile("(card[0-9])-(DP|HDMI|VGA)-.*")
57    display_types = {
58        "DP": "DisplayPort (DP)",
59        "HDMI": "High Definition Multimedia Interface (HDMI)",
60        "VGA": "Video Graphics Array (VGA)",
61    }
62    display_ids = defaultdict(lambda: 0)
63    for root in filter(lambda x: x.startswith("card"), os.listdir(SYS_DISPLAYS_INFO_PATH)):
64        displays_path = SYS_DISPLAYS_INFO_PATH + root
65        status_path = f"{displays_path}/status"
66        device_path = f"{displays_path}/device/device"
67        m = display_regex.match(root)
68        if m:
69            try:
70                assert Path(status_path).exists(), \
71                    f"{status_path} does not exist and connection status of {root} cannot be detected. Failed to add " \
72                    f"{root} to board XML."
73                assert Path(device_path).exists(), \
74                    f"{device_path} does not exist and the graphics card which {root} is connected to cannot be detected. " \
75                    f"Failed to add {root} to board XML"
76                with open(f"{status_path}", "r") as f:
77                    if f.read().strip() == "connected":
78                        bus, device, function = \
79                            get_bdf_from_realpath(f"{device_path}")
80                        adr = hex((device << 16) + function)
81                        bus_node = get_node(board_etree, f"//bus[@type='pci' and @address='{hex(bus)}']")
82                        if bus_node is None:
83                            devices_node = get_node(board_etree, "//devices")
84                            bus_node = add_child(devices_node, "bus", type="pci", address=hex(bus))
85                        device_node = get_node(bus_node, f"./device[@address='{adr}']")
86                        if device_node is None:
87                            device_node = add_child(bus_node, "device", None, address=adr)
88                        bdf = (bus, device, function)
89                        display_id = display_ids[bdf]
90                        add_child(device_node, "display", f"{display_id}", type=display_types[m.group(2)])
91                        display_ids[bdf] += 1
92            except Exception as e:
93                logging.warning(f"{e}")
94
95def extract_topology(device_classes_node, board_etree):
96    extract_inputs(device_classes_node)
97    extract_ttys(device_classes_node)
98    extract_display(board_etree)
99
100def extract(args, board_etree):
101    device_classes_node = get_node(board_etree, "//device-classes")
102    extract_topology(device_classes_node, board_etree)
103