1#!/usr/bin/env python3
2
3# Copyright (c) 2022 The Chromium OS Authors
4# SPDX-License-Identifier: Apache-2.0
5
6"""This file contains a Python script which parses through Zephyr device tree using
7EDT.pickle generated at build and generates a XML file containing USB VIF policies"""
8
9import argparse
10import inspect
11import os
12import pickle
13import sys
14import xml.etree.ElementTree as ET
15
16from constants import dt_constants
17from constants import other_constants
18from constants import pdo_constants
19from constants import vif_element_constants
20from constants import xml_constants
21
22SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..")
23sys.path.insert(0, os.path.join(SCRIPTS_DIR, 'dts', 'python-devicetree', 'src'))
24
25
26def get_value_attribute(data):
27    if not isinstance(data, str):
28        data = str(data)
29    return {other_constants.VALUE: data}
30
31
32def get_vif_element_name(name):
33    return xml_constants.XML_ELEMENT_NAME_PREFIX + ":" + dt_constants.DT_VIF_ELEMENTS.get(
34        name, name)
35
36
37def get_root():
38    xml_root = ET.Element(
39        get_vif_element_name(xml_constants.XML_ROOT_ELEMENT_NAME))
40    add_attributes_to_xml_element(xml_root,
41                                  xml_constants.XML_NAMESPACE_ATTRIBUTES)
42    return xml_root
43
44
45def add_attributes_to_xml_element(xml_ele, attributes):
46    for key, value in attributes.items():
47        xml_ele.set(key, value)
48
49
50def add_element_to_xml(xml_ele, name, text=None, attributes=None):
51    new_xml_ele = ET.SubElement(xml_ele, get_vif_element_name(name))
52    if text:
53        new_xml_ele.text = str(text)
54    if attributes:
55        add_attributes_to_xml_element(new_xml_ele, attributes)
56    return new_xml_ele
57
58
59def add_elements_to_xml(xml_ele, elements):
60    for element_name in elements:
61        text = elements[element_name].get(other_constants.TEXT, None)
62        attributes = elements[element_name].get(other_constants.ATTRIBUTES,
63                                                None)
64        new_xml_ele = add_element_to_xml(xml_ele, element_name, text,
65                                         attributes)
66        if other_constants.CHILD in elements[element_name]:
67            add_elements_to_xml(new_xml_ele,
68                                elements[element_name][other_constants.CHILD])
69
70
71def add_vif_spec_from_source_to_xml(xml_ele, source_xml_ele):
72    prefix = '{' + xml_constants.XML_VIF_NAMESPACE + '}'
73    for child in source_xml_ele:
74        element_name = child.tag[len(prefix):]
75        if element_name in xml_constants.VIF_SPEC_ELEMENTS_FROM_SOURCE_XML:
76            add_element_to_xml(xml_ele, element_name, child.text,
77                               child.attrib)
78
79
80def is_simple_datatype(value):
81    if isinstance(value, (str, int, bool)):
82        return True
83    return False
84
85
86def get_pdo_type(pdo_value):
87    return pdo_value >> 30
88
89
90def get_xml_bool_value(value):
91    if value:
92        return other_constants.TRUE
93    return other_constants.FALSE
94
95
96def parse_and_add_sink_pdo_to_xml(xml_ele, pdo_value, pdo_info):
97    power_mw = 0
98    xml_ele = add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO)
99    pdo_type = get_pdo_type(pdo_value)
100
101    if pdo_type == pdo_constants.PDO_TYPE_FIXED:
102        # As per USB PD specification Revision 3.1, Version 1.6, Table 6-16 Fixed supply PDO - Sink
103        current = pdo_value & 0x3ff
104        current_ma = current * 10
105        voltage = (pdo_value >> 10) & 0x3ff
106        voltage_mv = voltage * 50
107        power_mw = (current_ma * voltage_mv) // 1000
108
109        if pdo_value & (1 << 28):
110            pdo_info[vif_element_constants.HIGHER_CAPABILITY_SET] = True
111        pdo_info[
112            vif_element_constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE] = pdo_value & (
113            3 << 23)
114        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_VOLTAGE,
115                           f'{voltage_mv} mV',
116                           get_value_attribute(voltage))
117        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_OP_CURRENT,
118                           f'{current_ma} mA',
119                           get_value_attribute(current))
120    elif pdo_type == pdo_constants.PDO_TYPE_BATTERY:
121        # As per USB PD specification Revision 3.1, Version 1.6, Table 6-18 Battery supply PDO - Sink
122        max_voltage = (pdo_value >> 20) & 0x3ff
123        max_voltage_mv = max_voltage * 50
124        min_voltage = (pdo_value >> 10) & 0x3ff
125        min_voltage_mv = min_voltage * 50
126        power = pdo_value & 0x3ff
127        power_mw = power * 250
128
129        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_MIN_VOLTAGE,
130                           f'{min_voltage_mv} mV',
131                           get_value_attribute(min_voltage))
132        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_MAX_VOLTAGE,
133                           f'{max_voltage_mv} mV',
134                           get_value_attribute(max_voltage))
135        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_OP_POWER,
136                           f'{power_mw} mW',
137                           get_value_attribute(power))
138    elif pdo_type == pdo_constants.PDO_TYPE_VARIABLE:
139        # As per USB PD specification Revision 3.1, Version 1.6, Table 6-17 Variable supply (non-Battery) PDO - Sink
140        max_voltage = (pdo_value >> 20) & 0x3ff
141        max_voltage_mv = max_voltage * 50
142        min_voltage = (pdo_value >> 10) & 0x3ff
143        min_voltage_mv = min_voltage * 50
144        current = pdo_value & 0x3ff
145        current_ma = current * 10
146        power_mw = (current_ma * max_voltage_mv) // 1000
147
148        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_MIN_VOLTAGE,
149                           f'{min_voltage_mv} mV',
150                           get_value_attribute(min_voltage))
151        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_MAX_VOLTAGE,
152                           f'{max_voltage_mv} mV',
153                           get_value_attribute(max_voltage))
154        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_OP_CURRENT,
155                           f'{current_ma} mA',
156                           get_value_attribute(current))
157    elif pdo_type == pdo_constants.PDO_TYPE_AUGUMENTED:
158        # As per USB PD specification Revision 3.1, Version 1.6, Table 6-19 Programmable Power supply APDO - Sink
159        pps = (pdo_value >> 28) & 0x03
160        if pps:
161            raise ValueError(f'ERROR: Invalid PDO_TYPE {pdo_value}')
162        pps_max_voltage = (pdo_value >> 17) & 0xff
163        pps_max_voltage_mv = pps_max_voltage * 100
164        pps_min_voltage = (pdo_value >> 8) & 0xff
165        pps_min_voltage_mv = pps_min_voltage * 100
166        pps_current = pdo_value & 0x7f
167        pps_current_ma = pps_current * 50
168        power_mw = (pps_current_ma * pps_max_voltage_mv) // 1000
169
170        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_MIN_VOLTAGE,
171                           f'{pps_min_voltage_mv} mV',
172                           get_value_attribute(pps_min_voltage))
173        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_MAX_VOLTAGE,
174                           f'{pps_max_voltage_mv} mV',
175                           get_value_attribute(pps_max_voltage))
176        add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_OP_CURRENT,
177                           f'{pps_current_ma} mA',
178                           get_value_attribute(pps_current))
179    else:
180        raise ValueError(f'ERROR: Invalid PDO_TYPE {pdo_value}')
181
182    add_element_to_xml(xml_ele, vif_element_constants.SINK_PDO_SUPPLY_TYPE,
183                       pdo_constants.PDO_TYPES[pdo_type],
184                       get_value_attribute(pdo_type))
185    return power_mw
186
187
188def parse_and_add_sink_pdos_to_xml(xml_ele, sink_pdos):
189    new_xml_ele = add_element_to_xml(xml_ele, dt_constants.SINK_PDOS)
190    snk_max_power = 0
191    pdo_info = dict()
192
193    for pdo_value in sink_pdos:
194        power_mv = parse_and_add_sink_pdo_to_xml(new_xml_ele, pdo_value,
195                                                 pdo_info)
196        if power_mv > snk_max_power:
197            snk_max_power = power_mv
198
199    add_element_to_xml(xml_ele, vif_element_constants.PD_POWER_AS_SINK,
200                       f'{snk_max_power} mW',
201                       get_value_attribute(snk_max_power))
202    add_element_to_xml(xml_ele, vif_element_constants.HIGHER_CAPABILITY_SET,
203                       None, get_value_attribute(get_xml_bool_value(
204                        vif_element_constants.HIGHER_CAPABILITY_SET in
205                            pdo_info)))
206    fr_swap_required_type_c_current_as_initial_source = pdo_info[
207        vif_element_constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE]
208    add_element_to_xml(xml_ele,
209                       vif_element_constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE,
210                       other_constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE_VALUES[
211                           fr_swap_required_type_c_current_as_initial_source],
212                       get_value_attribute(
213                           fr_swap_required_type_c_current_as_initial_source))
214    add_element_to_xml(xml_ele, vif_element_constants.NUM_SNK_PDOS, None,
215                       get_value_attribute(len(sink_pdos)))
216
217
218def parse_and_add_component_to_xml(xml_ele, node):
219    add_element_to_xml(xml_ele, vif_element_constants.USB_PD_SUPPORT, None,
220                       get_value_attribute(get_xml_bool_value(
221                           not node.props[dt_constants.PD_DISABLE].val)))
222
223    if not node.props[dt_constants.PD_DISABLE].val:
224        power_role = node.props[dt_constants.POWER_ROLE].val
225        add_element_to_xml(xml_ele, vif_element_constants.PD_PORT_TYPE,
226                           other_constants.PD_PORT_TYPE_VALUES[power_role][1],
227                           get_value_attribute(
228                               other_constants.PD_PORT_TYPE_VALUES[
229                                   power_role][0]))
230        add_element_to_xml(xml_ele, vif_element_constants.TYPE_C_STATE_MACHINE,
231                           other_constants.TYPE_C_STATE_MACHINE_VALUES[
232                               power_role][1],
233                           get_value_attribute(
234                               other_constants.TYPE_C_STATE_MACHINE_VALUES[
235                                   power_role][0]))
236
237    if dt_constants.SINK_PDOS in node.props:
238        parse_and_add_sink_pdos_to_xml(xml_ele,
239                                       node.props[dt_constants.SINK_PDOS].val)
240
241
242def get_source_xml_root(source_xml_file):
243    return ET.parse(source_xml_file).getroot()
244
245
246def parse_args():
247    parser = argparse.ArgumentParser(allow_abbrev=False)
248    parser.add_argument("--edt-pickle", required=True,
249                        help="path to read the pickled edtlib.EDT object from")
250    parser.add_argument("--compatible", required=True,
251                        help="device tree compatible to be parsed")
252    parser.add_argument("--vif-out", required=True,
253                        help="path to write VIF policies to")
254    parser.add_argument("--vif-source-xml", required=True,
255                        help="XML file containing high level VIF specifications")
256    return parser.parse_args()
257
258
259def main():
260    global edtlib
261
262    args = parse_args()
263    with open(args.edt_pickle, 'rb') as f:
264        edt = pickle.load(f)
265    edtlib = inspect.getmodule(edt)
266
267    xml_root = get_root()
268    source_xml_root = get_source_xml_root(args.vif_source_xml)
269    add_elements_to_xml(xml_root, xml_constants.VIF_SPEC_ELEMENTS)
270    add_vif_spec_from_source_to_xml(xml_root, source_xml_root)
271
272    for node in edt.compat2nodes[args.compatible]:
273        xml_ele = add_element_to_xml(xml_root, vif_element_constants.COMPONENT)
274        parse_and_add_component_to_xml(xml_ele, node)
275
276    tree = ET.ElementTree(xml_root)
277    tree.write(args.vif_out, xml_declaration=True,
278               encoding=xml_constants.XML_ENCODING)
279
280
281if __name__ == "__main__":
282    main()
283