The ti-board-config entry loads and validates a given YAML config file against a given schema, and generates the board config binary. K3 devices require these binaries to be packed into the final system firmware images.
Signed-off-by: Neha Malcom Francis <n-fran...@ti.com> Reviewed-by: Simon Glass <s...@chromium.org> --- tools/binman/entries.rst | 48 ++++ tools/binman/etype/ti_board_config.py | 259 ++++++++++++++++++ tools/binman/ftest.py | 20 ++ tools/binman/pyproject.toml | 2 +- tools/binman/test/277_ti_board_cfg.dts | 14 + .../binman/test/278_ti_board_cfg_combined.dts | 25 ++ .../binman/test/279_ti_board_cfg_no_type.dts | 11 + tools/binman/test/yaml/config.yaml | 19 ++ tools/binman/test/yaml/schema.yaml | 51 ++++ tools/binman/test/yaml/schema_notype.yaml | 40 +++ 10 files changed, 488 insertions(+), 1 deletion(-) create mode 100644 tools/binman/etype/ti_board_config.py create mode 100644 tools/binman/test/277_ti_board_cfg.dts create mode 100644 tools/binman/test/278_ti_board_cfg_combined.dts create mode 100644 tools/binman/test/279_ti_board_cfg_no_type.dts create mode 100644 tools/binman/test/yaml/config.yaml create mode 100644 tools/binman/test/yaml/schema.yaml create mode 100644 tools/binman/test/yaml/schema_notype.yaml diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index b71af801fd..14a2d03fad 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -1658,6 +1658,54 @@ by setting the size of the entry to something larger than the text. +.. _etype_ti_board_config: + +Entry: ti-board-config: An entry containing a TI schema validated board config binary +------------------------------------------------------------------------------------- + +This etype supports generation of two kinds of board configuration +binaries: singular board config binary as well as combined board config +binary. + +Properties / Entry arguments: + - config-file: File containing board configuration data in YAML + - schema-file: File containing board configuration YAML schema against + which the config file is validated + +Output files: + - board config binary: File containing board configuration binary + +These above parameters are used only when the generated binary is +intended to be a single board configuration binary. Example:: + + my-ti-board-config { + ti-board-config { + config = "board-config.yaml"; + schema = "schema.yaml"; + }; + }; + +To generate a combined board configuration binary, we pack the +needed individual binaries into a ti-board-config binary. In this case, +the available supported subnode names are board-cfg, pm-cfg, sec-cfg and +rm-cfg. The final binary is prepended with a header containing details about +the included board config binaries. Example:: + + my-combined-ti-board-config { + ti-board-config { + board-cfg { + config = "board-cfg.yaml"; + schema = "schema.yaml"; + }; + sec-cfg { + config = "sec-cfg.yaml"; + schema = "schema.yaml"; + }; + } + } + + + .. _etype_u_boot: Entry: u-boot: U-Boot flat binary diff --git a/tools/binman/etype/ti_board_config.py b/tools/binman/etype/ti_board_config.py new file mode 100644 index 0000000000..0799e5dc59 --- /dev/null +++ b/tools/binman/etype/ti_board_config.py @@ -0,0 +1,259 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2022 Texas Instruments Incorporated - https://www.ti.com/ +# Written by Neha Malcom Francis <n-fran...@ti.com> +# +# Entry-type module for generating schema validated TI board +# configuration binary +# + +import os +import struct +import yaml + +from collections import OrderedDict +from jsonschema import validate +from shutil import copyfileobj + +from binman.entry import Entry +from binman.etype.section import Entry_section +from dtoc import fdt_util +from u_boot_pylib import tools + +BOARDCFG = 0xB +BOARDCFG_SEC = 0xD +BOARDCFG_PM = 0xE +BOARDCFG_RM = 0xC +BOARDCFG_NUM_ELEMS = 4 + +class Entry_ti_board_config(Entry_section): + """An entry containing a TI schema validated board config binary + + This etype supports generation of two kinds of board configuration + binaries: singular board config binary as well as combined board config + binary. + + Properties / Entry arguments: + - config-file: File containing board configuration data in YAML + - schema-file: File containing board configuration YAML schema against + which the config file is validated + + Output files: + - board config binary: File containing board configuration binary + + These above parameters are used only when the generated binary is + intended to be a single board configuration binary. Example:: + + my-ti-board-config { + ti-board-config { + config = "board-config.yaml"; + schema = "schema.yaml"; + }; + }; + + To generate a combined board configuration binary, we pack the + needed individual binaries into a ti-board-config binary. In this case, + the available supported subnode names are board-cfg, pm-cfg, sec-cfg and + rm-cfg. The final binary is prepended with a header containing details about + the included board config binaries. Example:: + + my-combined-ti-board-config { + ti-board-config { + board-cfg { + config = "board-cfg.yaml"; + schema = "schema.yaml"; + }; + sec-cfg { + config = "sec-cfg.yaml"; + schema = "schema.yaml"; + }; + } + } + """ + def __init__(self, section, etype, node): + super().__init__(section, etype, node) + self._config = None + self._schema = None + self._entries = OrderedDict() + self._num_elems = BOARDCFG_NUM_ELEMS + self._fmt = '<HHHBB' + self._index = 0 + self._binary_offset = 0 + self._sw_rev = 1 + self._devgrp = 0 + + def ReadNode(self): + super().ReadNode() + self._config = fdt_util.GetString(self._node, 'config') + self._schema = fdt_util.GetString(self._node, 'schema') + # Depending on whether config file is present in node, we determine + # whether it is a combined board config binary or not + if self._config is None: + self.ReadEntries() + else: + self._config_file = tools.get_input_filename(self._config) + self._schema_file = tools.get_input_filename(self._schema) + + def ReadEntries(self): + """Read the subnodes to find out what should go in this image + """ + for node in self._node.subnodes: + if 'type' not in node.props: + entry = Entry.Create(self, node, 'ti-board-config') + entry.ReadNode() + cfg_data = entry.BuildSectionData(True) + entry._cfg_data = cfg_data + self._entries[entry.name] = entry + self._num_elems = len(self._node.subnodes) + + def _convert_to_byte_chunk(self, val, data_type): + """Convert value into byte array + + Args: + val: value to convert into byte array + data_type: data type used in schema, supported data types are u8, + u16 and u32 + + Returns: + array of bytes representing value + """ + size = 0 + if (data_type == '#/definitions/u8'): + size = 1 + elif (data_type == '#/definitions/u16'): + size = 2 + else: + size = 4 + if type(val) == int: + br = val.to_bytes(size, byteorder='little') + return br + + def _compile_yaml(self, schema_yaml, file_yaml): + """Convert YAML file into byte array based on YAML schema + + Args: + schema_yaml: file containing YAML schema + file_yaml: file containing config to compile + + Returns: + array of bytes repesenting YAML file against YAML schema + """ + br = bytearray() + for key, node in file_yaml.items(): + node_schema = schema_yaml['properties'][key] + node_type = node_schema.get('type') + if not 'type' in node_schema: + br += self._convert_to_byte_chunk(node, + node_schema.get('$ref')) + elif node_type == 'object': + br += self._compile_yaml(node_schema, node) + elif node_type == 'array': + for item in node: + if not isinstance(item, dict): + br += self._convert_to_byte_chunk( + item, schema_yaml['properties'][key]['items']['$ref']) + else: + br += self._compile_yaml(node_schema.get('items'), item) + return br + + def _generate_binaries(self): + """Generate config binary artifacts from the loaded YAML configuration file + + Returns: + byte array containing config binary artifacts + or None if generation fails + """ + cfg_binary = bytearray() + for key, node in self.file_yaml.items(): + node_schema = self.schema_yaml['properties'][key] + br = self._compile_yaml(node_schema, node) + cfg_binary += br + return cfg_binary + + def _add_boardcfg(self, bcfgtype, bcfgdata): + """Add board config to combined board config binary + + Args: + bcfgtype (int): board config type + bcfgdata (byte array): board config data + """ + size = len(bcfgdata) + desc = struct.pack(self._fmt, bcfgtype, + self._binary_offset, size, self._devgrp, 0) + with open(self.descfile, 'ab+') as desc_fh: + desc_fh.write(desc) + with open(self.bcfgfile, 'ab+') as bcfg_fh: + bcfg_fh.write(bcfgdata) + self._binary_offset += size + self._index += 1 + + def _finalize(self): + """Generate final combined board config binary + + Returns: + byte array containing combined board config data + or None if unable to generate + """ + with open(self.descfile, 'rb') as desc_fh: + with open(self.bcfgfile, 'rb') as bcfg_fh: + with open(self.fh_file, 'ab+') as fh: + copyfileobj(desc_fh, fh) + copyfileobj(bcfg_fh, fh) + data = tools.read_file(self.fh_file) + return data + + def BuildSectionData(self, required): + if self._config is None: + self._binary_offset = 0 + uniq = self.GetUniqueName() + self.fh_file = tools.get_output_filename('fh.%s' % uniq) + self.descfile = tools.get_output_filename('desc.%s' % uniq) + self.bcfgfile = tools.get_output_filename('bcfg.%s' % uniq) + + # when binman runs again make sure we start clean + if os.path.exists(self.fh_file): + os.remove(self.fh_file) + if os.path.exists(self.descfile): + os.remove(self.descfile) + if os.path.exists(self.bcfgfile): + os.remove(self.bcfgfile) + + with open(self.fh_file, 'wb') as f: + t_bytes = f.write(struct.pack( + '<BB', self._num_elems, self._sw_rev)) + self._binary_offset += t_bytes + self._binary_offset += self._num_elems * struct.calcsize(self._fmt) + + if 'board-cfg' in self._entries: + self._add_boardcfg(BOARDCFG, self._entries['board-cfg']._cfg_data) + + if 'sec-cfg' in self._entries: + self._add_boardcfg(BOARDCFG_SEC, self._entries['sec-cfg']._cfg_data) + + if 'pm-cfg' in self._entries: + self._add_boardcfg(BOARDCFG_PM, self._entries['pm-cfg']._cfg_data) + + if 'rm-cfg' in self._entries: + self._add_boardcfg(BOARDCFG_RM, self._entries['rm-cfg']._cfg_data) + + data = self._finalize() + return data + + else: + with open(self._config_file, 'r') as f: + self.file_yaml = yaml.safe_load(f) + with open(self._schema_file, 'r') as sch: + self.schema_yaml = yaml.safe_load(sch) + + try: + validate(self.file_yaml, self.schema_yaml) + except Exception as e: + self.Raise(f"Schema validation error: {e}") + + data = self._generate_binaries() + return data + + def SetImagePos(self, image_pos): + Entry.SetImagePos(self, image_pos) + + def CheckEntries(self): + Entry.CheckEntries(self) diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 43b4f850a6..b9a490a5bd 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -97,6 +97,7 @@ ENV_DATA = b'var1=1\nvar2="2"' PRE_LOAD_MAGIC = b'UBSH' PRE_LOAD_VERSION = 0x11223344.to_bytes(4, 'big') PRE_LOAD_HDR_SIZE = 0x00001000.to_bytes(4, 'big') +TI_BOARD_CONFIG_DATA = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Subdirectory of the input dir to use to put test FDTs TEST_FDT_SUBDIR = 'fdts' @@ -199,6 +200,9 @@ class TestFunctional(unittest.TestCase): shutil.copytree(cls.TestFile('files'), os.path.join(cls._indir, 'files')) + shutil.copytree(cls.TestFile('yaml'), + os.path.join(cls._indir, 'yaml')) + TestFunctional._MakeInputFile('compress', COMPRESS_DATA) TestFunctional._MakeInputFile('compress_big', COMPRESS_DATA_BIG) TestFunctional._MakeInputFile('bl31.bin', ATF_BL31_DATA) @@ -6676,6 +6680,22 @@ fdt fdtmap Extract the devicetree blob from the fdtmap ['fit']) self.assertIn("Node '/fit': Missing tool: 'mkimage'", str(e.exception)) + def testTIBoardConfig(self): + """Test that a schema validated board config file can be generated""" + data = self._DoReadFile('277_ti_board_cfg.dts') + self.assertEqual(TI_BOARD_CONFIG_DATA, data) + + def testTIBoardConfigCombined(self): + """Test that a schema validated combined board config file can be generated""" + data = self._DoReadFile('278_ti_board_cfg_combined.dts') + configlen_noheader = TI_BOARD_CONFIG_DATA * 4 + self.assertGreater(data, configlen_noheader) + + def testTIBoardConfigNoDataType(self): + """Test that error is thrown when data type is not supported""" + with self.assertRaises(ValueError) as e: + data = self._DoReadFile('279_ti_board_cfg_no_type.dts') + self.assertIn("Schema validation error", str(e.exception)) if __name__ == "__main__": unittest.main() diff --git a/tools/binman/pyproject.toml b/tools/binman/pyproject.toml index b4b54fbaee..289e16dce8 100644 --- a/tools/binman/pyproject.toml +++ b/tools/binman/pyproject.toml @@ -8,7 +8,7 @@ version = "0.0.2" authors = [ { name="Simon Glass", email="s...@chromium.org" }, ] -dependencies = ["pylibfdt", "u_boot_pylib", "dtoc"] +dependencies = ["pylibfdt", "u_boot_pylib", "dtoc", "jsonschema"] description = "Binman firmware-packaging tool" readme = "README.rst" requires-python = ">=3.7" diff --git a/tools/binman/test/277_ti_board_cfg.dts b/tools/binman/test/277_ti_board_cfg.dts new file mode 100644 index 0000000000..cda024c1b8 --- /dev/null +++ b/tools/binman/test/277_ti_board_cfg.dts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + ti-board-config { + config = "yaml/config.yaml"; + schema = "yaml/schema.yaml"; + }; + }; +}; diff --git a/tools/binman/test/278_ti_board_cfg_combined.dts b/tools/binman/test/278_ti_board_cfg_combined.dts new file mode 100644 index 0000000000..95ef449cbf --- /dev/null +++ b/tools/binman/test/278_ti_board_cfg_combined.dts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + binman { + ti-board-config { + board-cfg { + config = "yaml/config.yaml"; + schema = "yaml/schema.yaml"; + }; + sec-cfg { + config = "yaml/config.yaml"; + schema = "yaml/schema.yaml"; + }; + rm-cfg { + config = "yaml/config.yaml"; + schema = "yaml/schema.yaml"; + }; + pm-cfg { + config = "yaml/config.yaml"; + schema = "yaml/schema.yaml"; + }; + }; + }; +}; diff --git a/tools/binman/test/279_ti_board_cfg_no_type.dts b/tools/binman/test/279_ti_board_cfg_no_type.dts new file mode 100644 index 0000000000..584b7acc5a --- /dev/null +++ b/tools/binman/test/279_ti_board_cfg_no_type.dts @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0+ +/dts-v1/; + +/ { + binman { + ti-board-config { + config = "yaml/config.yaml"; + schema = "yaml/schema_notype.yaml"; + }; + }; +}; diff --git a/tools/binman/test/yaml/config.yaml b/tools/binman/test/yaml/config.yaml new file mode 100644 index 0000000000..79fd67c7f4 --- /dev/null +++ b/tools/binman/test/yaml/config.yaml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/ +# +# Test config +# +--- + +main-branch: + obj: + a: 0x0 + b: 0 + arr: [0, 0, 0, 0] + another-arr: + - #1 + c: 0 + d: 0 + - #2 + c: 0 + d: 0 diff --git a/tools/binman/test/yaml/schema.yaml b/tools/binman/test/yaml/schema.yaml new file mode 100644 index 0000000000..60bf56f671 --- /dev/null +++ b/tools/binman/test/yaml/schema.yaml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/ +# +# Test schema +# +--- + +definitions: + u8: + type: integer + minimum: 0 + maximum: 0xff + u16: + type: integer + minimum: 0 + maximum: 0xffff + u32: + type: integer + minimum: 0 + maximum: 0xffffffff + +type: object +properties: + main-branch: + type: object + properties: + obj: + type: object + properties: + a: + $ref: "#/definitions/u32" + b: + $ref: "#/definitions/u16" + arr: + type: array + minItems: 4 + maxItems: 4 + items: + $ref: "#/definitions/u8" + another-arr: + type: array + minItems: 2 + maxItems: 2 + items: + type: object + properties: + c: + $ref: "#/definitions/u8" + d: + $ref: "#/definitions/u8" + diff --git a/tools/binman/test/yaml/schema_notype.yaml b/tools/binman/test/yaml/schema_notype.yaml new file mode 100644 index 0000000000..d45d6cdf7e --- /dev/null +++ b/tools/binman/test/yaml/schema_notype.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/ +# +# Test schema +# +--- + +definitions: + u8: + type: integer + minimum: 0 + maximum: 0xff + u16: + type: integer + minimum: 0 + maximum: 0xffff + u32: + type: integer + minimum: 0 + maximum: 0xffffffff + +type: object +properties: + main-branch: + type: object + properties: + obj: + type: object + properties: + a: + $ref: "#/definitions/u4" + b: + $ref: "#/definitions/u16" + arr: + type: array + minItems: 4 + maxItems: 4 + items: + $ref: "#/definitions/u8" + -- 2.34.1