1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2022-2023 Texas Instruments Incorporated - https://www.ti.com/ 3# Written by Neha Malcom Francis <n-francis@ti.com> 4# 5# Entry-type module for generating schema validated TI board 6# configuration binary 7# 8 9import os 10import struct 11import yaml 12import yamllint 13 14from collections import OrderedDict 15from jsonschema import validate 16from shutil import copyfileobj 17 18from binman.entry import Entry 19from binman.etype.section import Entry_section 20from dtoc import fdt_util 21from u_boot_pylib import tools 22from yamllint import config 23 24BOARDCFG = 0xB 25BOARDCFG_SEC = 0xD 26BOARDCFG_PM = 0xE 27BOARDCFG_RM = 0xC 28BOARDCFG_NUM_ELEMS = 4 29 30class Entry_ti_board_config(Entry_section): 31 """An entry containing a TI schema validated board config binary 32 33 This etype supports generation of two kinds of board configuration 34 binaries: singular board config binary as well as combined board config 35 binary. 36 37 Properties / Entry arguments: 38 - config-file: File containing board configuration data in YAML 39 - schema-file: File containing board configuration YAML schema against 40 which the config file is validated 41 42 Output files: 43 - board config binary: File containing board configuration binary 44 45 These above parameters are used only when the generated binary is 46 intended to be a single board configuration binary. Example:: 47 48 my-ti-board-config { 49 ti-board-config { 50 config = "board-config.yaml"; 51 schema = "schema.yaml"; 52 }; 53 }; 54 55 To generate a combined board configuration binary, we pack the 56 needed individual binaries into a ti-board-config binary. In this case, 57 the available supported subnode names are board-cfg, pm-cfg, sec-cfg and 58 rm-cfg. The final binary is prepended with a header containing details about 59 the included board config binaries. Example:: 60 61 my-combined-ti-board-config { 62 ti-board-config { 63 board-cfg { 64 config = "board-cfg.yaml"; 65 schema = "schema.yaml"; 66 }; 67 sec-cfg { 68 config = "sec-cfg.yaml"; 69 schema = "schema.yaml"; 70 }; 71 } 72 } 73 """ 74 def __init__(self, section, etype, node): 75 super().__init__(section, etype, node) 76 self._config = None 77 self._schema = None 78 self._entries = OrderedDict() 79 self._num_elems = BOARDCFG_NUM_ELEMS 80 self._fmt = '<HHHBB' 81 self._index = 0 82 self._binary_offset = 0 83 self._sw_rev = 1 84 self._devgrp = 0 85 86 def ReadNode(self): 87 super().ReadNode() 88 self._config = fdt_util.GetString(self._node, 'config') 89 self._schema = fdt_util.GetString(self._node, 'schema') 90 # Depending on whether config file is present in node, we determine 91 # whether it is a combined board config binary or not 92 if self._config is None: 93 self.ReadEntries() 94 else: 95 self._config_file = tools.get_input_filename(self._config) 96 self._schema_file = tools.get_input_filename(self._schema) 97 98 def ReadEntries(self): 99 """Read the subnodes to find out what should go in this image 100 """ 101 for node in self._node.subnodes: 102 if 'type' not in node.props: 103 entry = Entry.Create(self, node, 'ti-board-config') 104 entry.ReadNode() 105 cfg_data = entry.BuildSectionData(True) 106 entry._cfg_data = cfg_data 107 self._entries[entry.name] = entry 108 self._num_elems = len(self._node.subnodes) 109 110 def _convert_to_byte_chunk(self, val, data_type): 111 """Convert value into byte array 112 113 Args: 114 val: value to convert into byte array 115 data_type: data type used in schema, supported data types are u8, 116 u16 and u32 117 118 Returns: 119 array of bytes representing value 120 """ 121 size = 0 122 br = bytearray() 123 if (data_type == '#/definitions/u8'): 124 size = 1 125 elif (data_type == '#/definitions/u16'): 126 size = 2 127 else: 128 size = 4 129 br = None 130 if type(val) == int: 131 br = val.to_bytes(size, byteorder='little') 132 return br 133 134 def _compile_yaml(self, schema_yaml, file_yaml): 135 """Convert YAML file into byte array based on YAML schema 136 137 Args: 138 schema_yaml: file containing YAML schema 139 file_yaml: file containing config to compile 140 141 Returns: 142 array of bytes repesenting YAML file against YAML schema 143 """ 144 br = bytearray() 145 for key, node in file_yaml.items(): 146 node_schema = schema_yaml['properties'][key] 147 node_type = node_schema.get('type') 148 if not 'type' in node_schema: 149 br += self._convert_to_byte_chunk(node, 150 node_schema.get('$ref')) 151 elif node_type == 'object': 152 br += self._compile_yaml(node_schema, node) 153 elif node_type == 'array': 154 for item in node: 155 if not isinstance(item, dict): 156 br += self._convert_to_byte_chunk( 157 item, schema_yaml['properties'][key]['items']['$ref']) 158 else: 159 br += self._compile_yaml(node_schema.get('items'), item) 160 return br 161 162 def _generate_binaries(self): 163 """Generate config binary artifacts from the loaded YAML configuration file 164 165 Returns: 166 byte array containing config binary artifacts 167 or None if generation fails 168 """ 169 cfg_binary = bytearray() 170 for key, node in self.file_yaml.items(): 171 node_schema = self.schema_yaml['properties'][key] 172 br = self._compile_yaml(node_schema, node) 173 cfg_binary += br 174 return cfg_binary 175 176 def _add_boardcfg(self, bcfgtype, bcfgdata): 177 """Add board config to combined board config binary 178 179 Args: 180 bcfgtype (int): board config type 181 bcfgdata (byte array): board config data 182 """ 183 size = len(bcfgdata) 184 desc = struct.pack(self._fmt, bcfgtype, 185 self._binary_offset, size, self._devgrp, 0) 186 with open(self.descfile, 'ab+') as desc_fh: 187 desc_fh.write(desc) 188 with open(self.bcfgfile, 'ab+') as bcfg_fh: 189 bcfg_fh.write(bcfgdata) 190 self._binary_offset += size 191 self._index += 1 192 193 def _finalize(self): 194 """Generate final combined board config binary 195 196 Returns: 197 byte array containing combined board config data 198 or None if unable to generate 199 """ 200 with open(self.descfile, 'rb') as desc_fh: 201 with open(self.bcfgfile, 'rb') as bcfg_fh: 202 with open(self.fh_file, 'ab+') as fh: 203 copyfileobj(desc_fh, fh) 204 copyfileobj(bcfg_fh, fh) 205 data = tools.read_file(self.fh_file) 206 return data 207 208 def BuildSectionData(self, required): 209 if self._config is None: 210 self._binary_offset = 0 211 uniq = self.GetUniqueName() 212 self.fh_file = tools.get_output_filename('fh.%s' % uniq) 213 self.descfile = tools.get_output_filename('desc.%s' % uniq) 214 self.bcfgfile = tools.get_output_filename('bcfg.%s' % uniq) 215 216 # when binman runs again make sure we start clean 217 if os.path.exists(self.fh_file): 218 os.remove(self.fh_file) 219 if os.path.exists(self.descfile): 220 os.remove(self.descfile) 221 if os.path.exists(self.bcfgfile): 222 os.remove(self.bcfgfile) 223 224 with open(self.fh_file, 'wb') as f: 225 t_bytes = f.write(struct.pack( 226 '<BB', self._num_elems, self._sw_rev)) 227 self._binary_offset += t_bytes 228 self._binary_offset += self._num_elems * struct.calcsize(self._fmt) 229 230 if 'board-cfg' in self._entries: 231 self._add_boardcfg(BOARDCFG, self._entries['board-cfg']._cfg_data) 232 233 if 'sec-cfg' in self._entries: 234 self._add_boardcfg(BOARDCFG_SEC, self._entries['sec-cfg']._cfg_data) 235 236 if 'pm-cfg' in self._entries: 237 self._add_boardcfg(BOARDCFG_PM, self._entries['pm-cfg']._cfg_data) 238 239 if 'rm-cfg' in self._entries: 240 self._add_boardcfg(BOARDCFG_RM, self._entries['rm-cfg']._cfg_data) 241 242 data = self._finalize() 243 return data 244 245 else: 246 with open(self._config_file, 'r') as f: 247 self.file_yaml = yaml.safe_load(f) 248 with open(self._schema_file, 'r') as sch: 249 self.schema_yaml = yaml.safe_load(sch) 250 251 yaml_config = config.YamlLintConfig("extends: default") 252 for p in yamllint.linter.run(open(self._config_file, "r"), yaml_config): 253 self.Raise(f"Yamllint error: Line {p.line} in {self._config_file}: {p.rule}") 254 try: 255 validate(self.file_yaml, self.schema_yaml) 256 except Exception as e: 257 self.Raise(f"Schema validation error: {e}") 258 259 data = self._generate_binaries() 260 return data 261 262 def SetImagePos(self, image_pos): 263 Entry.SetImagePos(self, image_pos) 264 265 def CheckEntries(self): 266 Entry.CheckEntries(self) 267