diff --git a/CMakeLists.txt b/CMakeLists.txt index d31ad89ba66..4eb72e8fb24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1717,6 +1717,11 @@ if(CONFIG_BOOTLOADER_MCUBOOT) include(${CMAKE_CURRENT_LIST_DIR}/cmake/mcuboot.cmake) endif() +# Generate USB-C VIF policies in XML format +if (CONFIG_BUILD_OUTPUT_VIF) + include(${CMAKE_CURRENT_LIST_DIR}/cmake/vif.cmake) +endif() + get_property(extra_post_build_commands GLOBAL PROPERTY extra_post_build_commands diff --git a/CODEOWNERS b/CODEOWNERS index 203fe1fa15e..c5383bcc13e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -722,6 +722,7 @@ scripts/build/gen_image_info.py @tejlmand /scripts/list_boards.py @mbolivar-nordic /scripts/build/process_gperf.py @dcpleung @nashif /scripts/build/gen_relocate_app.py @dcpleung +/scripts/generate_usb_vif/ @madhurimaparuchuri /scripts/requirements*.txt @mbolivar-nordic @galak @nashif /scripts/tests/twister/ @aasthagr /scripts/tests/build/test_subfolder_list.py @rmstoi diff --git a/cmake/vif.cmake b/cmake/vif.cmake new file mode 100644 index 00000000000..611cabe349a --- /dev/null +++ b/cmake/vif.cmake @@ -0,0 +1,22 @@ +# Copyright (c) 2022 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +# Generates USB-C VIF policies in XML format from device tree. +set(gen_vif_script ${ZEPHYR_BASE}/scripts/generate_usb_vif/generate_vif.py) +set(dts_compatible usb-c-connector) +set(vif_xml ${PROJECT_BINARY_DIR}/vif.xml) +set(cmd_gen_vif ${PYTHON_EXECUTABLE} ${gen_vif_script} + --edt-pickle ${EDT_PICKLE} + --compatible ${dts_compatible} + --vif-out ${vif_xml} + --board ${BOARD} + ) + +add_custom_command( + OUTPUT ${vif_xml} + DEPENDS ${EDT_PICKLE} + COMMENT "Generating XML file at zephyr/vif.xml" + COMMAND ${cmd_gen_vif} +) + +add_custom_target(gen_vif ALL DEPENDS ${vif_xml}) diff --git a/samples/subsys/usb_c/sink/README.rst b/samples/subsys/usb_c/sink/README.rst index e4252a2eb95..00de60ec1ed 100644 --- a/samples/subsys/usb_c/sink/README.rst +++ b/samples/subsys/usb_c/sink/README.rst @@ -6,8 +6,9 @@ Basic USB-C SINK Overview ******** -This example demonstrates how to create a USB-C Power Delivery application -using the USB-C subsystem. The application implements a USB-C Sink device. +This example demonstrates how to create a USB-C Power Delivery application and +how to generate USB VIF policies in XML format using the USB-C subsystem. The +application implements a USB-C Sink device. After the USB-C Sink device is plugged into a Power Delivery charger, it negotiates with the charger to provide 5V@100mA and displays all diff --git a/samples/subsys/usb_c/sink/prj.conf b/samples/subsys/usb_c/sink/prj.conf index c4e4d58b997..563bfb8f3a9 100644 --- a/samples/subsys/usb_c/sink/prj.conf +++ b/samples/subsys/usb_c/sink/prj.conf @@ -2,3 +2,4 @@ CONFIG_USBC_STACK=y CONFIG_USBC_THREAD_PRIORITY=10 CONFIG_ADC=y CONFIG_LOG=y +CONFIG_BUILD_OUTPUT_VIF=y diff --git a/scripts/generate_usb_vif/constants.py b/scripts/generate_usb_vif/constants.py new file mode 100644 index 00000000000..1c9b4659cc3 --- /dev/null +++ b/scripts/generate_usb_vif/constants.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2022 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +"""This file contains all constants defined to be used by all other scripts +in this folder i.e, generate_usb_vif""" + +XML_ENCODING = "utf-8" +XML_ELEMENT_NAME_PREFIX = "vif" +XML_ROOT_ELEMENT_NAME = "VIF" +XML_NAMESPACE_ATTRIBUTES = { + "xmlns:opt": "http://compliance.usb.org/cv/VendorInfoFile/Schemas/Current/VendorInfoFileOptionalContent.xsd", + "xmlns:xsi": "http://www.w3.org/2001/XMLSchema", + "xmlns:vif": "http://compliance.usb.org/cv/VendorInfoFile/Schemas/Current/VendorInfoFile.xsd", +} + +NAME = "name" +VALUE = "value" +TEXT = "text" +ATTRIBUTES = "attributes" +CHILD = "child" +COMPONENT = "Component" +TRUE = "true" +FALSE = "false" + +SINK_PDOS = "sink-pdos" +SINK_PDO = "SnkPDO" +SINK_PDO_SUPPLY_TYPE = "Snk_PDO_Supply_Type" +SINK_PDO_VOLTAGE = "Snk_PDO_Voltage" +SINK_PDO_OP_CURRENT = "Snk_PDO_Op_Current" +SINK_PDO_MIN_VOLTAGE = "Snk_PDO_Min_Voltage" +SINK_PDO_MAX_VOLTAGE = "Snk_PDO_Max_Voltage" +SINK_PDO_OP_POWER = "Snk_PDO_Op_Power" +PD_POWER_AS_SINK = "PD_Power_As_Sink" +NUM_SINK_PDOS = "Num_Snk_PDOs" +MODEL_PART_NUMBER = "Model_Part_Number" +EPR_SUPPORTED_AS_SINK = "EPR_Supported_As_Snk" +NO_USB_SUSPEND_MAY_BE_SET = "No_USB_Suspend_May_Be_Set" +HIGHER_CAPABILITY_SET = "Higher_Capability_Set" +FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE = "FR_Swap_Reqd_Type_C_Current_As_Initial_Source" + +VIF_SPEC_ELEMENTS = { + "VIF_Specification": { + TEXT: "3.19", + }, + "VIF_App": { + CHILD: { + "Vendor": { + TEXT: "USB-IF", + }, + "Name": { + TEXT: "VIF Editor", + }, + "Version": { + TEXT: "3.2.4.0", + } + } + }, + "VIF_Product_Type": { + TEXT: "Port Product", + ATTRIBUTES: { + "value": "0", + }, + }, + "Certification_Type": { + TEXT: "End Product", + ATTRIBUTES: { + "value": "0", + }, + } +} + +VIF_ELEMENTS = ["VIF_Specification", "VIF_App", "Vendor", "Name", "Version", + "Vendor_Name", "VIF_Product_Type", "Certification_Type", + MODEL_PART_NUMBER, COMPONENT, SINK_PDOS, SINK_PDO, + SINK_PDO_SUPPLY_TYPE, SINK_PDO_VOLTAGE, SINK_PDO_OP_CURRENT, + SINK_PDO_MIN_VOLTAGE, SINK_PDO_MAX_VOLTAGE, SINK_PDO_OP_POWER, + PD_POWER_AS_SINK, PD_POWER_AS_SINK, NUM_SINK_PDOS, + EPR_SUPPORTED_AS_SINK, NO_USB_SUSPEND_MAY_BE_SET, + HIGHER_CAPABILITY_SET, ] + +DT_VIF_ELEMENTS = { + SINK_PDOS: "SnkPdoList", +} + +PDO_TYPE_FIXED = 0 +PDO_TYPE_BATTERY = 1 +PDO_TYPE_VARIABLE = 2 +PDO_TYPE_AUGUMENTED = 3 + +PDO_TYPES = { + PDO_TYPE_FIXED: "Fixed", + PDO_TYPE_BATTERY: "Battery", + PDO_TYPE_VARIABLE: "Variable", + PDO_TYPE_AUGUMENTED: "Augmented", +} diff --git a/scripts/generate_usb_vif/generate_vif.py b/scripts/generate_usb_vif/generate_vif.py new file mode 100644 index 00000000000..d27e4e6bbd6 --- /dev/null +++ b/scripts/generate_usb_vif/generate_vif.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2022 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +"""This file contains a Python script which parses through Zephyr device tree using +EDT.pickle generated at build and generates a XML file containing USB VIF policies""" + +import argparse +import os +import pickle +import sys +import xml.etree.ElementTree as ET + +import constants + +SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..") +sys.path.insert(0, os.path.join(SCRIPTS_DIR, 'dts', 'python-devicetree', 'src')) + +from devicetree import edtlib + + +def main(): + args = parse_args() + try: + with open(args.edt_pickle, 'rb') as f: + edt = pickle.load(f) + except edtlib.EDTError as err: + sys.exit(f"devicetree error: {err}") + xml_root = get_root() + add_elements_to_xml(xml_root, constants.VIF_SPEC_ELEMENTS) + add_element_to_xml(xml_root, constants.MODEL_PART_NUMBER, args.board) + for node in edt.compat2nodes[args.compatible]: + xml_ele = add_element_to_xml(xml_root, constants.COMPONENT) + parse_and_add_node_to_xml(xml_ele, node) + tree = ET.ElementTree(xml_root) + tree.write(args.vif_out, xml_declaration=True, + encoding=constants.XML_ENCODING) + + +def is_vif_element(name): + if name in constants.VIF_ELEMENTS: + return True + return False + + +def get_vif_element_name(name): + return constants.XML_ELEMENT_NAME_PREFIX + ":" + constants.DT_VIF_ELEMENTS.get( + name, name) + + +def get_root(): + xml_root = ET.Element(get_vif_element_name(constants.XML_ROOT_ELEMENT_NAME)) + add_attributes_to_xml_element(xml_root, constants.XML_NAMESPACE_ATTRIBUTES) + return xml_root + + +def add_attributes_to_xml_element(xml_ele, attributes): + for key, value in attributes.items(): + xml_ele.set(key, value) + + +def add_element_to_xml(xml_ele, name, text=None, attributes=None): + if is_vif_element(name): + new_xml_ele = ET.SubElement(xml_ele, get_vif_element_name(name)) + if text: + new_xml_ele.text = str(text) + if attributes: + add_attributes_to_xml_element(new_xml_ele, attributes) + return new_xml_ele + return xml_ele + + +def add_elements_to_xml(xml_ele, elements): + for element_name in elements: + text = elements[element_name].get(constants.TEXT, None) + attributes = elements[element_name].get(constants.ATTRIBUTES, None) + new_xml_ele = add_element_to_xml(xml_ele, element_name, text, attributes) + if constants.CHILD in elements[element_name]: + add_elements_to_xml(new_xml_ele, elements[element_name][constants.CHILD]) + + +def is_simple_datatype(value): + if isinstance(value, (str, int, bool)): + return True + return False + + +def get_pdo_type(pdo_value): + return pdo_value >> 30 + + +def get_xml_bool_value(value): + if value: + return constants.TRUE + return constants.FALSE + + +def parse_and_add_sink_pdos_to_xml(xml_ele, sink_pdos): + new_xml_ele = add_element_to_xml(xml_ele, constants.SINK_PDOS) + pdos_info = dict() + snk_max_power = 0 + for pdo_value in sink_pdos: + power_mv = parse_and_add_sink_pdo_to_xml(new_xml_ele, pdo_value, pdos_info) + if power_mv > snk_max_power: + snk_max_power = power_mv + add_element_to_xml(xml_ele, constants.NUM_SINK_PDOS, None, + {constants.VALUE: str(len(sink_pdos))}) + add_element_to_xml(xml_ele, constants.EPR_SUPPORTED_AS_SINK, + attributes={constants.VALUE: constants.FALSE}) + add_element_to_xml(xml_ele, constants.NO_USB_SUSPEND_MAY_BE_SET, + attributes={constants.VALUE: constants.TRUE}) + add_element_to_xml(xml_ele, constants.HIGHER_CAPABILITY_SET, attributes={ + constants.VALUE: get_xml_bool_value(pdos_info.get(constants.HIGHER_CAPABILITY_SET, 0))}) + add_element_to_xml(xml_ele, constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE, + "FR_Swap not supported", attributes={constants.VALUE: str( + pdos_info.get(constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE, 0))}) + add_element_to_xml(xml_ele, constants.PD_POWER_AS_SINK, f'{snk_max_power} mW', + {constants.VALUE: str(snk_max_power)}) + + +def parse_and_add_sink_pdo_to_xml(xml_ele, pdo_value, pdos_info): + power_mw = 0 + xml_ele = add_element_to_xml(xml_ele, constants.SINK_PDO) + pdo_type = get_pdo_type(pdo_value) + if pdo_type == constants.PDO_TYPE_FIXED: + current = pdo_value & 0x3ff + current_ma = current * 10 + voltage = (pdo_value >> 10) & 0x3ff + voltage_mv = voltage * 50 + power_mw = (current_ma * voltage_mv) // 1000 + pdos_info[constants.HIGHER_CAPABILITY_SET] = pdo_value & (1 << 28) + pdos_info[constants.FR_SWAP_REQD_TYPE_C_CURRENT_AS_INITIAL_SOURCE] = pdo_value & (3 << 23) + add_element_to_xml(xml_ele, constants.SINK_PDO_VOLTAGE, f'{voltage_mv} mV', + {constants.VALUE: str(voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_OP_CURRENT, + f'{current_ma} mA', + {constants.VALUE: str(current)}) + elif pdo_type == constants.PDO_TYPE_BATTERY: + max_voltage = (pdo_value >> 20) & 0x3ff + max_voltage_mv = max_voltage * 50 + min_voltage = (pdo_value >> 10) & 0x3ff + min_voltage_mv = min_voltage * 50 + power = pdo_value & 0x3ff + power_mw = power * 250 + add_element_to_xml(xml_ele, constants.SINK_PDO_MIN_VOLTAGE, + f'{min_voltage_mv} mV', + {constants.VALUE: str(min_voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_MAX_VOLTAGE, + f'{max_voltage_mv} mV', + {constants.VALUE: str(max_voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_OP_POWER, f'{power_mw} mW', + {constants.VALUE: str(power)}) + elif pdo_type == constants.PDO_TYPE_VARIABLE: + max_voltage = (pdo_value >> 20) & 0x3ff + max_voltage_mv = max_voltage * 50 + min_voltage = (pdo_value >> 10) & 0x3ff + min_voltage_mv = min_voltage * 50 + current = pdo_value & 0x3ff + current_ma = current * 10 + power_mw = (current_ma * max_voltage_mv) // 1000 + add_element_to_xml(xml_ele, constants.SINK_PDO_MIN_VOLTAGE, + f'{min_voltage_mv} mV', + {constants.VALUE: str(min_voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_MAX_VOLTAGE, + f'{max_voltage_mv} mV', + {constants.VALUE: str(max_voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_OP_CURRENT, + f'{current_ma} mA', + {constants.VALUE: str(current)}) + elif pdo_type == constants.PDO_TYPE_AUGUMENTED: + pps = (pdo_value >> 28) & 0x03 + if pps: + raise ValueError(f'ERROR: Invalid PDO_TYPE {pdo_value}') + pps_max_voltage = (pdo_value >> 17) & 0xff + pps_max_voltage_mv = pps_max_voltage * 100 + pps_min_voltage = (pdo_value >> 8) & 0xff + pps_min_voltage_mv = pps_min_voltage * 100 + pps_current = pdo_value & 0x7f + pps_current_ma = pps_current * 50 + power_mw = (pps_current_ma * pps_max_voltage_mv) // 1000 + add_element_to_xml(xml_ele, constants.SINK_PDO_MIN_VOLTAGE, + f'{pps_min_voltage_mv} mV', + {constants.VALUE: str(pps_min_voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_MAX_VOLTAGE, + f'{pps_max_voltage_mv} mV', + {constants.VALUE: str(pps_max_voltage)}) + add_element_to_xml(xml_ele, constants.SINK_PDO_OP_CURRENT, + f'{pps_current_ma} mA', + {constants.VALUE: str(pps_current)}) + else: + raise ValueError(f'ERROR: Invalid PDO_TYPE {pdo_value}') + add_element_to_xml(xml_ele, constants.SINK_PDO_SUPPLY_TYPE, + constants.PDO_TYPES[pdo_type], {constants.VALUE: str(pdo_type)}) + return power_mw + + +def parse_and_add_controller_and_data_to_xml(xml_ele, cad): + xml_ele = add_element_to_xml(xml_ele, cad.basename) + for name in cad.data: + add_element_to_xml(xml_ele, name, str(cad.data[name])) + parse_and_add_node_to_xml(xml_ele, cad.controller) + + +def parse_and_add_array_to_xml(xml_ele, prop): + for member in prop.val: + if is_simple_datatype(member): + add_element_to_xml(xml_ele, prop.name, str(member)) + elif isinstance(member, list): + new_xml_ele = add_element_to_xml(xml_ele, prop.name) + parse_and_add_array_to_xml(new_xml_ele, member) + elif isinstance(member, edtlib.Node): + new_xml_ele = add_element_to_xml(xml_ele, prop.name) + parse_and_add_node_to_xml(new_xml_ele, member) + elif isinstance(member, edtlib.ControllerAndData): + new_xml_ele = add_element_to_xml(xml_ele, prop.name) + parse_and_add_controller_and_data_to_xml(new_xml_ele, member) + else: + ValueError( + f'Noticed undefined type : {str(type(member))}, with value {str(member)}') + + +def parse_and_add_node_to_xml(xml_ele, node): + if not isinstance(node, edtlib.Node): + return + xml_ele = add_element_to_xml(xml_ele, node.name) + for prop in node.props: + if is_simple_datatype(node.props[prop].val): + add_element_to_xml(xml_ele, node.props[prop].name, + str(node.props[prop].val)) + elif node.props[prop].name == constants.SINK_PDOS: + parse_and_add_sink_pdos_to_xml(xml_ele, node.props[prop].val) + elif isinstance(node.props[prop].val, list): + parse_and_add_array_to_xml(xml_ele, node.props[prop]) + elif isinstance(node.props[prop].val, edtlib.Node): + new_xml_ele = add_element_to_xml(xml_ele, node.props[prop].name) + parse_and_add_node_to_xml(new_xml_ele, node.props[prop].val) + elif isinstance(node.props[prop].val, edtlib.ControllerAndData): + new_xml_ele = add_element_to_xml(xml_ele, node.props[prop].name) + parse_and_add_controller_and_data_to_xml(new_xml_ele, node.props[prop].val) + else: + ValueError( + f'Noticed undefined type : {str(type(node.props[prop].val))}, ' + f'with value {str(node.props[prop].val)}') + for child in node.children: + new_xml_ele = add_element_to_xml(xml_ele, child) + parse_and_add_node_to_xml(new_xml_ele, node.children[child]) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--edt-pickle", required=True, + help="path to read the pickled edtlib.EDT object from") + parser.add_argument("--compatible", required=True, + help="device tree compatible to be parsed") + parser.add_argument("--vif-out", required=True, + help="path to write VIF policies to") + parser.add_argument("--board", required=True, help="board name") + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/subsys/usb/usb_c/Kconfig b/subsys/usb/usb_c/Kconfig index 778b0f2be34..1fc637d1304 100644 --- a/subsys/usb/usb_c/Kconfig +++ b/subsys/usb/usb_c/Kconfig @@ -33,6 +33,12 @@ config USBC_STATE_MACHINE_CYCLE_TIME The USB-C state machine is run in a loop and the cycle time is the delay before running the loop again. +config BUILD_OUTPUT_VIF + bool "Generate VIF policies of USB-C in XML format" + depends on DT_HAS_USB_C_CONNECTOR_ENABLED + help + Generate XML file containing VIF policies during project build. + module = USBC_STACK module-str = usbc stack source "subsys/logging/Kconfig.template.log_config"