# @ GenYamlCfg.py # # Copyright (c) 2020 - 2021, Intel Corporation. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause-Patent # # import os import sys import re import marshal import string import operator as op import ast from datetime import date from collections import OrderedDict from CommonUtility import value_to_bytearray, value_to_bytes, \ bytes_to_value, get_bits_from_bytes, set_bits_to_bytes # Generated file copyright header __copyright_tmp__ = """/** @file Platform Configuration %s File. Copyright (c) %4d, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent This file is automatically generated. Please do NOT modify !!! **/ """ def get_copyright_header(file_type, allow_modify=False): file_description = { 'yaml': 'Boot Setting', 'dlt': 'Delta', 'inc': 'C Binary Blob', 'h': 'C Struct Header' } if file_type in ['yaml', 'dlt']: comment_char = '#' else: comment_char = '' lines = __copyright_tmp__.split('\n') if allow_modify: lines = [line for line in lines if 'Please do NOT modify' not in line] copyright_hdr = '\n'.join('%s%s' % (comment_char, line) for line in lines)[:-1] + '\n' return copyright_hdr % (file_description[file_type], date.today().year) def check_quote(text): if (text[0] == "'" and text[-1] == "'") or (text[0] == '"' and text[-1] == '"'): return True return False def strip_quote(text): new_text = text.strip() if check_quote(new_text): return new_text[1:-1] return text def strip_delimiter(text, delim): new_text = text.strip() if new_text: if new_text[0] == delim[0] and new_text[-1] == delim[-1]: return new_text[1:-1] return text def bytes_to_bracket_str(bytes): return '{ %s }' % (', '.join('0x%02x' % i for i in bytes)) def array_str_to_value(val_str): val_str = val_str.strip() val_str = strip_delimiter(val_str, '{}') val_str = strip_quote(val_str) value = 0 for each in val_str.split(',')[::-1]: each = each.strip() value = (value << 8) | int(each, 0) return value def write_lines(lines, file): fo = open(file, "w") fo.write(''.join([x[0] for x in lines])) fo.close() def read_lines(file): if not os.path.exists(file): test_file = os.path.basename(file) if os.path.exists(test_file): file = test_file fi = open(file, 'r') lines = fi.readlines() fi.close() return lines def expand_file_value(path, value_str): result = bytearray() match = re.match("\\{\\s*FILE:(.+)\\}", value_str) if match: file_list = match.group(1).split(',') for file in file_list: file = file.strip() bin_path = os.path.join(path, file) result.extend(bytearray(open(bin_path, 'rb').read())) print('\n\n result ', result) return result class ExpressionEval(ast.NodeVisitor): operators = { ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.floordiv, ast.Mod: op.mod, ast.Eq: op.eq, ast.NotEq: op.ne, ast.Gt: op.gt, ast.Lt: op.lt, ast.GtE: op.ge, ast.LtE: op.le, ast.BitXor: op.xor, ast.BitAnd: op.and_, ast.BitOr: op.or_, ast.Invert: op.invert, ast.USub: op.neg } def __init__(self): self._debug = False self._expression = '' self._namespace = {} self._get_variable = None def eval(self, expr, vars={}): self._expression = expr if type(vars) is dict: self._namespace = vars self._get_variable = None else: self._namespace = {} self._get_variable = vars node = ast.parse(self._expression, mode='eval') result = self.visit(node.body) if self._debug: print('EVAL [ %s ] = %s' % (expr, str(result))) return result def visit_Name(self, node): if self._get_variable is not None: return self._get_variable(node.id) else: return self._namespace[node.id] def visit_Num(self, node): return node.value def visit_NameConstant(self, node): return node.value def visit_BoolOp(self, node): result = False if isinstance(node.op, ast.And): for value in node.values: result = self.visit(value) if not result: break elif isinstance(node.op, ast.Or): for value in node.values: result = self.visit(value) if result: break return True if result else False def visit_UnaryOp(self, node): val = self.visit(node.operand) return ExpressionEval.operators[type(node.op)](val) def visit_BinOp(self, node): lhs = self.visit(node.left) rhs = self.visit(node.right) return ExpressionEval.operators[type(node.op)](lhs, rhs) def visit_Compare(self, node): right = self.visit(node.left) result = True for operation, comp in zip(node.ops, node.comparators): if not result: break left = right right = self.visit(comp) result = ExpressionEval.operators[type(operation)](left, right) return result def visit_Call(self, node): if node.func.id in ['ternary']: condition = self.visit(node.args[0]) val_true = self.visit(node.args[1]) val_false = self.visit(node.args[2]) return val_true if condition else val_false elif node.func.id in ['offset', 'length']: if self._get_variable is not None: return self._get_variable(node.args[0].s, node.func.id) else: raise ValueError("Unsupported function: " + repr(node)) def generic_visit(self, node): raise ValueError("malformed node or string: " + repr(node)) class CFG_YAML(): TEMPLATE = 'template' CONFIGS = 'configs' VARIABLE = 'variable' FORMSET = 'formset' def __init__(self): self.log_line = False self.allow_template = False self.cfg_tree = None self.tmp_tree = None self.var_dict = None self.def_dict = {} self.yaml_path = '' self.yaml_type = 'fsp' self.lines = [] self.full_lines = [] self.index = 0 self.re_expand = re.compile( r'(.+:\s+|\s*\-\s*)!expand\s+\{\s*(\w+_TMPL)\s*:\s*\[(.+)]\s*\}') self.re_include = re.compile(r'(.+:\s+|\s*\-\s*)!include\s+(.+)') @staticmethod def count_indent(line): return next((i for i, c in enumerate(line) if not c.isspace()), len(line)) @staticmethod def substitue_args(text, arg_dict): for arg in arg_dict: text = text.replace('$' + arg, arg_dict[arg]) return text @staticmethod def dprint(*args): pass def process_include(self, line, insert=True): match = self.re_include.match(line) if not match: raise Exception("Invalid !include format '%s' !" % line.strip()) prefix = match.group(1) include = match.group(2) if prefix.strip() == '-': prefix = '' adjust = 0 else: adjust = 2 include = strip_quote(include) request = CFG_YAML.count_indent(line) + adjust if self.log_line: # remove the include line itself del self.full_lines[-1] inc_path = os.path.join(self.yaml_path, include) if not os.path.exists(inc_path): # try relative path to project root try_path = os.path.join(os.path.dirname(os.path.realpath(__file__) ), "../..", include) if os.path.exists(try_path): inc_path = try_path else: raise Exception("ERROR: Cannot open file '%s'." % inc_path) lines = read_lines(inc_path) current = 0 same_line = False for idx, each in enumerate(lines): start = each.lstrip() if start == '' or start[0] == '#': continue if start[0] == '>': # append the content directly at the same line same_line = True start = idx current = CFG_YAML.count_indent(each) break lines = lines[start+1:] if same_line else lines[start:] leading = '' if same_line: request = len(prefix) leading = '>' lines = [prefix + '%s\n' % leading] + [' ' * request + i[current:] for i in lines] if insert: self.lines = lines + self.lines return lines def process_expand(self, line): match = self.re_expand.match(line) if not match: raise Exception("Invalid !expand format '%s' !" % line.strip()) lines = [] prefix = match.group(1) temp_name = match.group(2) args = match.group(3) if prefix.strip() == '-': indent = 0 else: indent = 2 lines = self.process_expand_template(temp_name, prefix, args, indent) self.lines = lines + self.lines def process_expand_template(self, temp_name, prefix, args, indent=2): # expand text with arg substitution if temp_name not in self.tmp_tree: raise Exception("Could not find template '%s' !" % temp_name) parts = args.split(',') parts = [i.strip() for i in parts] num = len(parts) arg_dict = dict(zip(['(%d)' % (i + 1) for i in range(num)], parts)) str_data = self.tmp_tree[temp_name] text = DefTemplate(str_data).safe_substitute(self.def_dict) text = CFG_YAML.substitue_args(text, arg_dict) target = CFG_YAML.count_indent(prefix) + indent current = CFG_YAML.count_indent(text) padding = target * ' ' if indent == 0: leading = [] else: leading = [prefix + '\n'] text = leading + [(padding + i + '\n')[current:] for i in text.splitlines()] return text def load_file(self, yaml_file): self.index = 0 self.lines = read_lines(yaml_file) def peek_line(self): if len(self.lines) == 0: return None else: return self.lines[0] def put_line(self, line): self.lines.insert(0, line) if self.log_line: del self.full_lines[-1] def get_line(self): if len(self.lines) == 0: return None else: line = self.lines.pop(0) if self.log_line: self.full_lines.append(line.rstrip()) return line def get_multiple_line(self, indent): text = '' newind = indent + 1 while True: line = self.peek_line() if line is None: break sline = line.strip() if sline != '': newind = CFG_YAML.count_indent(line) if newind <= indent: break self.get_line() if sline != '': text = text + line return text def traverse_cfg_tree(self, handler): def _traverse_cfg_tree(root, level=0): # config structure for key in root: if type(root[key]) is OrderedDict: level += 1 handler(key, root[key], level) _traverse_cfg_tree(root[key], level) level -= 1 _traverse_cfg_tree(self.cfg_tree) def count(self): def _count(name, cfgs, level): num[0] += 1 num = [0] self.traverse_cfg_tree(_count) return num[0] def parse(self, parent_name='', curr=None, level=0): child = None last_indent = None key = '' temp_chk = {} temp_data = [] while True: line = self.get_line() if line is None: break curr_line = line.strip() if curr_line == "## DO NOT REMOVE -- YAML Mode": self.yaml_type = "vfr" if curr_line == '' or curr_line[0] == '#': continue indent = CFG_YAML.count_indent(line) if last_indent is None: last_indent = indent if indent != last_indent: # outside of current block, put the line back to queue self.put_line(' ' * indent + curr_line) if curr_line.endswith(': >'): # multiline marker old_count = len(self.full_lines) line = self.get_multiple_line(indent) if self.log_line and not self.allow_template \ and '!include ' in line: # expand include in template new_lines = [] lines = line.splitlines() for idx, each in enumerate(lines): if '!include ' in each: new_line = ''.join(self.process_include(each, False)) new_lines.append(new_line) else: new_lines.append(each) self.full_lines = self.full_lines[:old_count] + new_lines curr_line = curr_line + line if indent > last_indent: # child nodes if child is None: raise Exception('Unexpected format at line: %s' % (curr_line)) level += 1 self.parse(key, child, level) level -= 1 line = self.peek_line() if line is not None: curr_line = line.strip() indent = CFG_YAML.count_indent(line) if indent >= last_indent: # consume the line self.get_line() else: # end of file indent = -1 if curr is None: curr = OrderedDict() if indent < last_indent: return curr marker1 = curr_line[0] start = 1 if marker1 == '-' else 0 pos = curr_line.find(': ') if marker1 == '-': marker2 = curr_line[curr_line.find(":")] pos = -1 else: marker2 = curr_line[-1] if pos > 0: child = None key = curr_line[start:pos].strip() if curr_line[pos + 2] == '>': curr[key] = curr_line[pos + 3:] else: # XXXX: !include / !expand if '!include ' in curr_line: self.process_include(line) elif '!expand ' in curr_line: if self.allow_template and not self.log_line: self.process_expand(line) else: value_str = curr_line[pos + 2:].strip() curr[key] = value_str if self.log_line and value_str[0] == '{': # expand {FILE: xxxx} format in the log line if value_str[1:].rstrip().startswith('FILE:'): value_bytes = expand_file_value( self.yaml_path, value_str) value_str = bytes_to_bracket_str(value_bytes) self.full_lines[-1] = line[ :indent] + curr_line[:pos + 2] + value_str elif marker2 == ':': child = OrderedDict() key = curr_line[start:-1].strip() if key == '$ACTION': # special virtual nodes, rename to ensure unique key key = '$ACTION_%04X' % self.index self.index += 1 if self.yaml_type =='fsp': if key in curr: if key not in temp_chk: # check for duplicated keys at same level temp_chk[key] = 1 else: raise Exception("Duplicated item '%s:%s' found !" % (parent_name, key)) curr[key] = child if self.yaml_type == 'vfr': if key in curr.keys(): if type(curr[key]) == type([]): temp_data = curr[key] else: temp_data.append(curr[key]) temp_data.append(child) if level < 5: curr[key] = temp_data temp_data = [] else: if level < 5: curr[key] = child if self.var_dict is None and key == CFG_YAML.VARIABLE: self.var_dict = child if self.tmp_tree is None and key == CFG_YAML.TEMPLATE: self.tmp_tree = child if self.var_dict: for each in self.var_dict: txt = self.var_dict[each] if type(txt) is str: self.def_dict['(%s)' % each] = txt if self.tmp_tree and key == CFG_YAML.CONFIGS: # apply template for the main configs self.allow_template = True if self.tmp_tree and key == CFG_YAML.FORMSET: self.allow_template = True else: child = None # - !include cfg_opt.yaml if '!include ' in curr_line: self.process_include(line) return curr def load_yaml(self, opt_file): self.var_dict = None self.yaml_path = os.path.dirname(opt_file) self.load_file(opt_file) yaml_tree = self.parse() for key in yaml_tree.keys(): if key.lower() == "configs": self.yaml_type = 'fsp' self.tmp_tree = yaml_tree[CFG_YAML.TEMPLATE] self.cfg_tree = yaml_tree[CFG_YAML.CONFIGS] break else: self.cfg_tree = yaml_tree break if self.yaml_type == 'vfr': formset_found = True for key in yaml_tree.keys(): if key == CFG_YAML.FORMSET: self.cfg_tree = yaml_tree[CFG_YAML.FORMSET] formset_found = False break if formset_found == True: self.cfg_tree = yaml_tree elif self.yaml_type == 'fsp': self.tmp_tree = yaml_tree[CFG_YAML.TEMPLATE] self.cfg_tree = yaml_tree[CFG_YAML.CONFIGS] return self.cfg_tree def expand_yaml(self, opt_file): self.log_line = True self.load_yaml(opt_file) self.log_line = False text = '\n'.join(self.full_lines) self.full_lines = [] return text class DefTemplate(string.Template): idpattern = '\\([_A-Z][_A-Z0-9]*\\)|[_A-Z][_A-Z0-9]*' class CGenYamlCfg: STRUCT = '$STRUCT' bits_width = {'b': 1, 'B': 8, 'W': 16, 'D': 32, 'Q': 64} builtin_option = {'$EN_DIS': [('0', 'Disable'), ('1', 'Enable')]} exclude_struct = ['FSP_UPD_HEADER', 'FSPT_ARCH_UPD', 'FSPM_ARCH_UPD', 'FSPS_ARCH_UPD', 'FSPI_ARCH_UPD', 'FSPT_ARCH2_UPD', 'FSPM_ARCH2_UPD', 'FSPS_ARCH2_UPD', 'GPIO_GPP_*', 'GPIO_CFG_DATA', 'GpioConfPad*', 'GpioPinConfig', 'BOOT_OPTION*', 'PLATFORMID_CFG_DATA', '\\w+_Half[01]'] include_tag = ['GPIO_CFG_DATA'] keyword_set = set(['name', 'type', 'option', 'help', 'length', 'value', 'order', 'struct', 'condition']) def __init__(self): self._mode = '' self._debug = False self._macro_dict = {} self.binseg_dict = {} self.initialize() self._tk = self.import_tkinter() def initialize(self): self._old_bin = None self._cfg_tree = {} self._tmp_tree = {} self._cfg_list = [] self._cfg_page = {'root': {'title': '', 'child': []}} self._cur_page = '' self._main_page = '' self._var_dict = {} self._def_dict = {} self._yaml_path = '' self.yaml_type = '' #Added to overcome duplicate formid self.form_page_map = {} self.formset_level = 0 @staticmethod def deep_convert_dict(layer): # convert OrderedDict to list + dict new_list = layer if isinstance(layer, OrderedDict): new_list = list(layer.items()) for idx, pair in enumerate(new_list): new_node = CGenYamlCfg.deep_convert_dict(pair[1]) new_list[idx] = dict({pair[0]: new_node}) return new_list @staticmethod def deep_convert_list(layer): if isinstance(layer, list): od = OrderedDict({}) for each in layer: if isinstance(each, dict): key = next(iter(each)) od[key] = CGenYamlCfg.deep_convert_list(each[key]) return od else: return layer @staticmethod def expand_include_files(file_path, cur_dir=''): if cur_dir == '': cur_dir = os.path.dirname(file_path) file_path = os.path.basename(file_path) input_file_path = os.path.join(cur_dir, file_path) file = open(input_file_path, "r") lines = file.readlines() file.close() new_lines = [] for line_num, line in enumerate(lines): match = re.match("^!include\\s*(.+)?$", line.strip()) if match: inc_path = match.group(1) tmp_path = os.path.join(cur_dir, inc_path) org_path = tmp_path if not os.path.exists(tmp_path): cur_dir = os.path.join(os.path.dirname (os.path.realpath(__file__) ), "..", "..") tmp_path = os.path.join(cur_dir, inc_path) if not os.path.exists(tmp_path): raise Exception("ERROR: Cannot open include\ file '%s'." % org_path) else: new_lines.append(('# Included from file: %s\n' % inc_path, tmp_path, 0)) new_lines.append(('# %s\n' % ('=' * 80), tmp_path, 0)) new_lines.extend(CGenYamlCfg.expand_include_files (inc_path, cur_dir)) else: new_lines.append((line, input_file_path, line_num)) return new_lines @staticmethod def format_struct_field_name(input, count=0): name = '' cap = True if '_' in input: input = input.lower() for each in input: if each == '_': cap = True continue elif cap: each = each.upper() cap = False name = name + each if count > 1: name = '%s[%d]' % (name, count) # # FSP[T/M/S]_ARCH2_UPD struct field name is Fsp[t/m/s]Arch2Upd, # it should change to Fsp[t/m/s]ArchUpd for code compatibility. # name = re.sub(r'(Fsp[tms]Arch)2Upd', r'\1Upd', name) return name def get_mode(self): return self._mode def set_mode(self, mode): self._mode = mode def get_last_error(self): return '' def get_variable(self, var, attr='value'): if var in self._var_dict: var = self._var_dict[var] return var item = self.locate_cfg_item(var, False) if item is None: raise ValueError("Cannot find variable '%s' !" % var) if item: if 'indx' in item: item = self.get_item_by_index(item['indx']) if attr == 'offset': var = item['offset'] elif attr == 'length': var = item['length'] elif attr == 'value': var = self.get_cfg_item_value(item) else: raise ValueError("Unsupported variable attribute '%s' !" % attr) return var def eval(self, expr): def _handler(pattern): if pattern.group(1): target = 1 else: target = 2 result = self.get_variable(pattern.group(target)) if result is None: raise ValueError('Unknown variable $(%s) !' % pattern.group(target)) return hex(result) expr_eval = ExpressionEval() if '$' in expr: # replace known variable first expr = re.sub(r'\$\(([_a-zA-Z][\w\.]*)\)|\$([_a-zA-Z][\w\.]*)', _handler, expr) return expr_eval.eval(expr, self.get_variable) def parse_macros(self, macro_def_str): # ['-DABC=1', '-D', 'CFG_DEBUG=1', '-D', 'CFG_OUTDIR=Build'] self._macro_dict = {} is_expression = False for macro in macro_def_str: if macro.startswith('-D'): is_expression = True if len(macro) > 2: macro = macro[2:] else: continue if is_expression: is_expression = False match = re.match("(\\w+)=(.+)", macro) if match: self._macro_dict[match.group(1)] = match.group(2) else: match = re.match("(\\w+)", macro) if match: self._macro_dict[match.group(1)] = '' if len(self._macro_dict) == 0: error = 1 else: error = 0 if self._debug: print("INFO : Macro dictionary:") for each in self._macro_dict: print(" $(%s) = [ %s ]" % (each, self._macro_dict[each])) return error def get_cfg_list(self, page_id=None): cfgs = [] if page_id is None: # return full list return self._cfg_list else: if self.yaml_type == 'fsp': # build a new list for items under a page ID cfgs = [i for i in self._cfg_list if i['cname'] and (i['page'] == page_id)] #VFR YAML Support Start elif self.yaml_type =='vfr': for cfg in self._cfg_list: for i in cfg: if (i['page'] == page_id): cfgs.append(i) #VFR YAML Support End return cfgs def get_cfg_page(self): return self._cfg_page def get_cfg_item_length(self, item): return item['length'] def get_cfg_item_value(self, item, array=False): value_str = item['value'] length = item['length'] return self.get_value(value_str, length, array) def format_value_to_str(self, value, bit_length, old_value=''): # value is always int length = (bit_length + 7) // 8 fmt = '' if old_value.startswith('0x'): fmt = '0x' elif old_value and (old_value[0] in ['"', "'", '{']): fmt = old_value[0] else: fmt = '' bvalue = value_to_bytearray(value, length) if fmt in ['"', "'"]: svalue = bvalue.rstrip(b'\x00').decode() value_str = fmt + svalue + fmt elif fmt == "{": value_str = '{ ' + ', '.join(['0x%02x' % i for i in bvalue]) + ' }' elif fmt == '0x': hex_len = length * 2 if len(old_value) == hex_len + 2: fstr = '0x%%0%dx' % hex_len else: fstr = '0x%x' value_str = fstr % value else: if length <= 2: value_str = '%d' % value elif length <= 8: value_str = '0x%x' % value else: value_str = '{ ' + ', '.join(['0x%02x' % i for i in bvalue]) + ' }' return value_str def reformat_value_str(self, value_str, bit_length, old_value=None): value = self.parse_value(value_str, bit_length, False) if old_value is None: old_value = value_str new_value = self.format_value_to_str(value, bit_length, old_value) return new_value def get_value(self, value_str, bit_length, array=True): value_str = value_str.strip() if value_str[0] == "'" and value_str[-1] == "'" or \ value_str[0] == '"' and value_str[-1] == '"': value_str = value_str[1:-1] bvalue = bytearray(value_str.encode()) if len(bvalue) == 0: bvalue = bytearray(b'\x00') if array: return bvalue else: return bytes_to_value(bvalue) else: if value_str[0] in '{': value_str = value_str[1:-1].strip() value = 0 for each in value_str.split(',')[::-1]: each = each.strip() value = (value << 8) | int(each, 0) if array: length = (bit_length + 7) // 8 return value_to_bytearray(value, length) else: return value def parse_value(self, value_str, bit_length, array=True): length = (bit_length + 7) // 8 if check_quote(value_str): value_str = bytes_to_bracket_str(value_str[1:-1].encode()) elif (',' in value_str) and (value_str[0] != '{'): value_str = '{ %s }' % value_str if value_str[0] == '{': result = expand_file_value(self._yaml_path, value_str) if len(result) == 0: bin_list = value_str[1:-1].split(',') value = 0 bit_len = 0 unit_len = 1 for idx, element in enumerate(bin_list): each = element.strip() if len(each) == 0: continue in_bit_field = False if each[0] in "'" + '"': each_value = bytearray(each[1:-1], 'utf-8') elif ':' in each: match = re.match("^(.+):(\\d+)([b|B|W|D|Q])$", each) if match is None: raise SystemExit("Exception: Invald value\ list format '%s' !" % each) if match.group(1) == '0' and match.group(2) == '0': unit_len = CGenYamlCfg.bits_width[match.group(3) ] // 8 cur_bit_len = int(match.group(2) ) * CGenYamlCfg.bits_width[ match.group(3)] value += ((self.eval(match.group(1)) & ( 1 << cur_bit_len) - 1)) << bit_len bit_len += cur_bit_len each_value = bytearray() if idx + 1 < len(bin_list): in_bit_field = True else: try: each_value = value_to_bytearray( self.eval(each.strip()), unit_len) except Exception: raise SystemExit("Exception: Value %d cannot \ fit into %s bytes !" % (each, unit_len)) if not in_bit_field: if bit_len > 0: if bit_len % 8 != 0: raise SystemExit("Exception: Invalid bit \ field alignment '%s' !" % value_str) result.extend(value_to_bytes(value, bit_len // 8)) value = 0 bit_len = 0 result.extend(each_value) elif check_quote(value_str): result = bytearray(value_str[1:-1], 'utf-8') # Excluding quotes else: result = value_to_bytearray(self.eval(value_str), length) if len(result) < length: result.extend(b'\x00' * (length - len(result))) elif len(result) > length: raise SystemExit("Exception: Value '%s' is too big to fit \ into %d bytes !" % (value_str, length)) if array: return result else: return bytes_to_value(result) return result def get_cfg_item_options(self, item): tmp_list = [] if item['type'] == "Combo": if item['option'] in CGenYamlCfg.builtin_option: for op_val, op_str in CGenYamlCfg.builtin_option[item['option' ]]: tmp_list.append((op_val, op_str)) else: if item['option'].find(';') != -1: opt_list = item['option'].split(';') else: opt_list = re.split(', ', item['option']) for option in opt_list: option = option.strip() try: if option.find(':') != -1: (op_val, op_str) = option.split(':') else: op_val = option op_str = option except Exception: raise SystemExit("Exception: Invalid \ option format '%s' !" % option) tmp_list.append((op_val, op_str)) return tmp_list def get_page_title(self, page_id, top=None): if top is None: top = self.get_cfg_page()['root'] for node in top['child']: page_key = next(iter(node)) if page_id == page_key: return node[page_key]['title'] else: result = self.get_page_title(page_id, node[page_key]) if result is not None: return result return None def print_pages(self, top=None, level=0): if top is None: top = self.get_cfg_page()['root'] for node in top['child']: page_id = next(iter(node)) print('%s%s: %s' % (' ' * level, page_id, node[page_id]['title'])) level += 1 self.print_pages(node[page_id], level) level -= 1 def get_item_by_index(self, index): return self._cfg_list[index] def get_item_by_path(self, path): node = self.locate_cfg_item(path) if node: return self.get_item_by_index(node['indx']) else: return None def locate_cfg_path(self, item): def _locate_cfg_path(root, level=0): # config structure if item is root: return path for key in root: if type(root[key]) is OrderedDict: level += 1 path.append(key) ret = _locate_cfg_path(root[key], level) if ret: return ret path.pop() return None path = [] return _locate_cfg_path(self._cfg_tree) def locate_cfg_item(self, path, allow_exp=True): def _locate_cfg_item(root, path, level=0): if len(path) == level: return root if type(root) == type([]): for temp_root in root: return _locate_cfg_item(temp_root, path, level) next_root = root.get(path[level], None) if next_root is None: if allow_exp: raise Exception('Not a valid CFG config option path: %s' % '.'.join(path[:level+1])) else: return None return _locate_cfg_item(next_root, path, level + 1) path_nodes = path.split('.') return _locate_cfg_item(self._cfg_tree, path_nodes) def traverse_cfg_tree(self, handler, top=None): def _traverse_cfg_tree(root, level=0): # config structure for key in root: if type(root[key]) is OrderedDict: level += 1 handler(key, root[key], level) _traverse_cfg_tree(root[key], level) level -= 1 if top is None: top = self._cfg_tree _traverse_cfg_tree(top) def print_cfgs(self, root=None, short=True, print_level=256): def _print_cfgs(name, cfgs, level): if 'indx' in cfgs: act_cfg = self.get_item_by_index(cfgs['indx']) else: offset = 0 length = 0 value = '' if CGenYamlCfg.STRUCT in cfgs: cfg = cfgs[CGenYamlCfg.STRUCT] offset = int(cfg['offset']) length = int(cfg['length']) if 'value' in cfg: value = cfg['value'] if length == 0: return act_cfg = dict({'value': value, 'offset': offset, 'length': length}) value = act_cfg['value'] bit_len = act_cfg['length'] offset = (act_cfg['offset'] + 7) // 8 if value != '': try: value = self.reformat_value_str(act_cfg['value'], act_cfg['length']) except Exception: value = act_cfg['value'] length = bit_len // 8 bit_len = '(%db)' % bit_len if bit_len % 8 else '' * 4 if level <= print_level: if short and len(value) > 40: value = '%s ... %s' % (value[:20], value[-20:]) print('%04X:%04X%-6s %s%s : %s' % (offset, length, bit_len, ' ' * level, name, value)) self.traverse_cfg_tree(_print_cfgs) def build_var_dict(self): def _build_var_dict(name, cfgs, level): if level <= 2: if CGenYamlCfg.STRUCT in cfgs: struct_info = cfgs[CGenYamlCfg.STRUCT] self._var_dict['_LENGTH_%s_' % name] = struct_info[ 'length'] // 8 self._var_dict['_OFFSET_%s_' % name] = struct_info[ 'offset'] // 8 self._var_dict = {} self.traverse_cfg_tree(_build_var_dict) self._var_dict['_LENGTH_'] = self._cfg_tree[CGenYamlCfg.STRUCT][ 'length'] // 8 return 0 def add_cfg_page(self, child, parent, title=''): def _add_cfg_page(cfg_page, child, parent): key = next(iter(cfg_page)) if parent == key: cfg_page[key]['child'].append({child: {'title': title, 'child': []}}) return True else: result = False for each in cfg_page[key]['child']: if _add_cfg_page(each, child, parent): result = True break return result return _add_cfg_page(self._cfg_page, child, parent) def set_cur_page(self, page_str): if not page_str: return if ',' in page_str: page_list = page_str.split(',') else: page_list = [page_str] for page_str in page_list: parts = page_str.split(':') if len(parts) in [1, 3]: page = parts[0].strip() if len(parts) == 3: # it is a new page definition, add it into tree parent = parts[1] if parts[1] else 'root' parent = parent.strip() if parts[2][0] == '"' and parts[2][-1] == '"': parts[2] = parts[2][1:-1] if not self.add_cfg_page(page, parent, parts[2]): raise SystemExit("Error: Cannot find parent page \ '%s'!" % parent) else: raise SystemExit("Error: Invalid page format '%s' !" % page_str) self._cur_page = page def extend_variable(self, line): # replace all variables if line == '': return line loop = 2 while loop > 0: line_after = DefTemplate(line).safe_substitute(self._def_dict) if line == line_after: break loop -= 1 line = line_after return line_after def reformat_number_per_type(self, itype, value): if check_quote(value) or value.startswith('{'): return value parts = itype.split(',') if len(parts) > 3 and parts[0] == 'EditNum': num_fmt = parts[1].strip() else: num_fmt = '' if num_fmt == 'HEX' and not value.startswith('0x'): value = '0x%X' % int(value, 10) elif num_fmt == 'DEC' and value.startswith('0x'): value = '%d' % int(value, 16) return value def add_cfg_item(self, name, item, offset, path): self.set_cur_page(item.get('page', '')) if name != '' and name[0] == '$': # skip all virtual node return 0 if not set(item).issubset(CGenYamlCfg.keyword_set): for each in list(item): if each not in CGenYamlCfg.keyword_set: raise Exception("Invalid attribute '%s' for '%s'!" % (each, '.'.join(path))) length = item.get('length', 0) if type(length) is str: match = re.match("^(\\d+)([b|B|W|D|Q])([B|W|D|Q]?)\\s*$", length) if match: unit_len = CGenYamlCfg.bits_width[match.group(2)] length = int(match.group(1), 10) * unit_len else: try: length = int(length, 0) * 8 except Exception: raise Exception("Invalid length field '%s' for '%s' !" % (length, '.'.join(path))) if offset % 8 > 0: raise Exception("Invalid alignment for field '%s' for \ '%s' !" % (name, '.'.join(path))) else: # define is length in bytes length = length * 8 if name != '' and not name.isidentifier(): raise Exception("Invalid config name '%s' for '%s' !" % (name, '.'.join(path))) itype = str(item.get('type', 'Reserved')) value = str(item.get('value', '')) if value: if not (check_quote(value) or value.startswith('{')): if ',' in value: value = '{ %s }' % value else: value = self.reformat_number_per_type(itype, value) help = str(item.get('help', '')) if '\n' in help: help = ' '.join([i.strip() for i in help.splitlines()]) option = str(item.get('option', '')) if '\n' in option: option = ' '.join([i.strip() for i in option.splitlines()]) # extend variables for value and condition condition = str(item.get('condition', '')) if condition: condition = self.extend_variable(condition) value = self.extend_variable(value) order = str(item.get('order', '')) if order: if '.' in order: (major, minor) = order.split('.') order = int(major, 16) else: order = int(order, 16) else: order = offset cfg_item = dict() cfg_item['length'] = length cfg_item['offset'] = offset cfg_item['value'] = value cfg_item['type'] = itype cfg_item['cname'] = str(name) cfg_item['name'] = str(item.get('name', '')) cfg_item['help'] = help cfg_item['option'] = option cfg_item['page'] = self._cur_page cfg_item['order'] = order cfg_item['path'] = '.'.join(path) cfg_item['condition'] = condition if 'struct' in item: cfg_item['struct'] = item['struct'] self._cfg_list.append(cfg_item) item['indx'] = len(self._cfg_list) - 1 # remove used info for reducing pkl size item.pop('option', None) item.pop('condition', None) item.pop('help', None) item.pop('name', None) item.pop('page', None) return length def build_cfg_list(self, cfg_name='', top=None, path=[], info={'offset': 0}): if top is None: top = self._cfg_tree info.clear() info = {'offset': 0} start = info['offset'] is_leaf = True for key in top: path.append(key) if type(top[key]) is OrderedDict: is_leaf = False self.build_cfg_list(key, top[key], path, info) path.pop() if is_leaf: length = self.add_cfg_item(cfg_name, top, info['offset'], path) info['offset'] += length elif cfg_name == '' or (cfg_name and cfg_name[0] != '$'): # check first element for struct first = next(iter(top)) struct_str = CGenYamlCfg.STRUCT if first != struct_str: struct_node = OrderedDict({}) top[struct_str] = struct_node top.move_to_end(struct_str, False) else: struct_node = top[struct_str] struct_node['offset'] = start struct_node['length'] = info['offset'] - start if struct_node['length'] % 8 != 0: raise SystemExit("Error: Bits length not aligned for %s !" % str(path)) #EDK2 VFR YAML Support start def build_formset_list(self, form_name='', top=None, parent_form='',path =[]): if self.formset_level == 1: self._cfg_page['root']['title'] = 'Platform' self._cfg_page['root']['child'].append({form_name: {'title': form_name, 'child': []}}) self._main_page = form_name if top is None: top = self._cfg_tree form_name = "Formset" self._cfg_page['root']['title'] = 'Formset' is_leaf = True if form_name == "form" or form_name == "formid": self._cur_page = top["title"].split('#')[0] self.form_page_map[top['formid'].split('#')[0]] = self._cur_page for driver in self._cfg_page['root']['child']: if list(driver.keys())[0] == self._main_page: driver[self._main_page]['child'].append({self._cur_page: {'title': self._cur_page, 'child': []}}) if form_name == "formmap": self._cur_page = top["formid"].split('#')[0] self.form_page_map[top['FormId'].split('#')[0]] = self._cur_page self._cfg_page['root']['child'].append({self._cur_page: {'title': self._cur_page, 'child': []}}) form_data = {} temp_data = [] for key in top: if key == 'include': form_data['type'] = key form_data["page"] = self._cur_page continue if type(top[key]) is list and self.formset_level <= 3: self.formset_level += 1 path.append(key) for data in top[key]: self.build_formset_list(key, data, key, path) path.pop() self.formset_level -= 1 elif type(top[key]) is OrderedDict and (self.formset_level <= 3): if parent_form != '': self.formset_level += 1 path.append(key) self.build_formset_list(key, top[key], form_name, path) path.pop() self.formset_level -= 1 else: self.formset_level += 1 path.append(key) self.build_formset_list(key, top[key], key, path) path.pop() self.formset_level -= 1 else: form_data["page"] = self._cur_page form_data[key] = top[key] form_data['path'] = ".".join(path) if form_name != 'form' or form_name != "formid": form_data["type"] = form_name else: form_data["type"] = " " count = 0 if self._cfg_list != []: for cfg_name in self._cfg_list: for list_data in cfg_name: if key == list_data['type']: count +=1 if count > 1: temp_data = cfg_name if len(temp_data) != 0 or len(form_data)!=0: temp_data.append(form_data) self._cfg_list.append(temp_data) return #EDK2 VFR YAML Support End def get_field_value(self, top=None): def _get_field_value(name, cfgs, level): if 'indx' in cfgs: act_cfg = self.get_item_by_index(cfgs['indx']) if act_cfg['length'] == 0: return value = self.get_value(act_cfg['value'], act_cfg['length'], False) set_bits_to_bytes(result, act_cfg['offset'] - struct_info['offset'], act_cfg['length'], value) if top is None: top = self._cfg_tree struct_info = top[CGenYamlCfg.STRUCT] result = bytearray((struct_info['length'] + 7) // 8) self.traverse_cfg_tree(_get_field_value, top) return result data_diff = '' def find_data_difference(self, act_val, act_cfg): # checks for any difference between BSF and Binary file config_val = '' if act_val != act_cfg['value']: if 'DEC' in act_cfg['type']: bsf_val = '0x%x' % int(act_val) if bsf_val != act_cfg['value']: config_val = bsf_val else: config_val = '' else: config_val = act_val available_fv1 = 'none' available_fv2 = 'none' if self.detect_fsp(): if len(self.available_fv) >= 1: if len(self.available_fv) > 1: available_fv1 = self.available_fv[1] if self.available_fv[2]: available_fv2 = self.available_fv[2] else: available_fv1 = self.available_fv[1] if act_cfg['length'] == 16: config_val = int(config_val, 16) config_val = '0x%x' % config_val act_cfg['value'] = int( act_cfg['value'], 16) act_cfg['value'] = '0x%x' % \ act_cfg['value'] if config_val: string = ('.' + act_cfg['cname']) if (act_cfg['path'].endswith(self.available_fv[0] + string) or act_cfg['path'].endswith(available_fv1 + string) or act_cfg['path'].endswith(available_fv2 + string)) \ and 'BsfSkip' not in act_cfg['cname'] \ and 'Reserved' not in act_cfg['name']: if act_cfg['option'] != '': if act_cfg['length'] == 8: config_val = int(config_val, 16) config_val = '0x%x' % config_val act_cfg['value'] = int( act_cfg['value'], 16) act_cfg['value'] = '0x%x' % \ act_cfg['value'] option = act_cfg['option'] cfg_val = '' bin_val = '' for i in option.split(','): if act_cfg['value'] in i: bin_val = i elif config_val in i: cfg_val = i if cfg_val != '' and bin_val != '': self.data_diff += '\n\nBinary: ' \ + act_cfg['name'] \ + ': ' + bin_val.replace(' ', '') \ + '\nConfig file: ' \ + act_cfg['name'] + ': ' \ + cfg_val.replace(' ', '') + '\n' else: self.data_diff += '\n\nBinary: ' \ + act_cfg['name'] + ': ' + act_cfg['value'] \ + '\nConfig file: ' + act_cfg['name'] \ + ': ' + config_val + '\n' def set_field_value(self, top, value_bytes, force=False): def _set_field_value(name, cfgs, level): if 'indx' not in cfgs: return act_cfg = self.get_item_by_index(cfgs['indx']) actual_offset = act_cfg['offset'] - struct_info['offset'] if force or act_cfg['value'] == '': value = get_bits_from_bytes(full_bytes, actual_offset, act_cfg['length']) act_val = act_cfg['value'] if act_val == '': act_val = '%d' % value act_val = self.reformat_number_per_type(act_cfg ['type'], act_val) act_cfg['value'] = self.format_value_to_str( value, act_cfg['length'], act_val) self.find_data_difference(act_val, act_cfg) if 'indx' in top: # it is config option value = bytes_to_value(value_bytes) act_cfg = self.get_item_by_index(top['indx']) act_cfg['value'] = self.format_value_to_str( value, act_cfg['length'], act_cfg['value']) else: # it is structure struct_info = top[CGenYamlCfg.STRUCT] length = struct_info['length'] // 8 full_bytes = bytearray(value_bytes[:length]) if len(full_bytes) < length: full_bytes.extend(bytearray(length - len(value_bytes))) self.traverse_cfg_tree(_set_field_value, top) def update_def_value(self): def _update_def_value(name, cfgs, level): if 'indx' in cfgs: act_cfg = self.get_item_by_index(cfgs['indx']) if act_cfg['value'] != '' and act_cfg['length'] > 0: try: act_cfg['value'] = self.reformat_value_str( act_cfg['value'], act_cfg['length']) except Exception: raise Exception("Invalid value expression '%s' \ for '%s' !" % (act_cfg['value'], act_cfg['path'])) else: if CGenYamlCfg.STRUCT in cfgs and 'value' in \ cfgs[CGenYamlCfg.STRUCT]: curr = cfgs[CGenYamlCfg.STRUCT] value_bytes = self.get_value(curr['value'], curr['length'], True) self.set_field_value(cfgs, value_bytes) self.traverse_cfg_tree(_update_def_value, self._cfg_tree) def evaluate_condition(self, item): expr = item['condition'] result = self.parse_value(expr, 1, False) return result def detect_fsp(self): cfg_segs = self.get_cfg_segment() if len(cfg_segs) == 3: fsp = True for idx, seg in enumerate(cfg_segs): if not seg[0].endswith('UPD_%s' % 'TMSI'[idx]): fsp = False break else: fsp = False if fsp: self.set_mode('FSP') return fsp def get_cfg_segment(self): def _get_cfg_segment(name, cfgs, level): if 'indx' not in cfgs: if name.startswith('$ACTION_'): if 'find' in cfgs: find[0] = cfgs['find'] else: if find[0]: act_cfg = self.get_item_by_index(cfgs['indx']) segments.append([find[0], act_cfg['offset'] // 8, 0]) find[0] = '' return find = [''] segments = [] self.traverse_cfg_tree(_get_cfg_segment, self._cfg_tree) cfg_len = self._cfg_tree[CGenYamlCfg.STRUCT]['length'] // 8 if len(segments) == 0: segments.append(['', 0, cfg_len]) segments.append(['', cfg_len, 0]) cfg_segs = [] for idx, each in enumerate(segments[:-1]): cfg_segs.append((each[0], each[1], segments[idx+1][1] - each[1])) return cfg_segs def import_tkinter(self): try: import tkinter import tkinter.messagebox return tkinter except ImportError: print('tkinter is not supported under current OS') return None def get_bin_segment(self, bin_data): cfg_segs = self.get_cfg_segment() bin_segs = [] for seg in cfg_segs: key = seg[0].encode() if key == 0: bin_segs.append([seg[0], 0, len(bin_data)]) break pos = bin_data.find(key) if pos >= 0: # ensure no other match for the key next_pos = bin_data.find(key, pos + len(seg[0])) if next_pos >= 0: if key == b'$SKLFSP$' or key == b'$BSWFSP$': string = ('Multiple matches for %s in ' 'binary!\n\nA workaround applied to such ' 'FSP 1.x binary to use second' ' match instead of first match!' % key) if self._tk: self._tk.messagebox.showwarning('Warning: ', string) else: print('Warning: ', string) pos = next_pos else: print("Warning: Multiple matches for '%s' " "in binary, the 1st instance will be used !" % seg[0]) bin_segs.append([seg[0], pos, seg[2]]) self.binseg_dict[seg[0]] = pos else: bin_segs.append([seg[0], -1, seg[2]]) self.binseg_dict[seg[0]] = -1 continue return bin_segs available_fv = [] missing_fv = [] def extract_cfg_from_bin(self, bin_data): # get cfg bin length cfg_bins = bytearray() bin_segs = self.get_bin_segment(bin_data) Dummy_offset = 0 for each in bin_segs: if each[1] != -1: cfg_bins.extend(bin_data[each[1]:each[1] + each[2]]) self.available_fv.append(each[0]) else: self.missing_fv.append(each[0]) string = each[0] + ' is not availabe.' if self._tk: self._tk.messagebox.showinfo('', string) else: print('warning: ', string) cfg_bins.extend(bytearray(each[2])) Dummy_offset += each[2] return cfg_bins def save_current_to_bin(self): cfg_bins = self.generate_binary_array() if self._old_bin is None: return cfg_bins bin_data = bytearray(self._old_bin) bin_segs = self.get_bin_segment(self._old_bin) cfg_off = 0 for each in bin_segs: length = each[2] if each[1] != -1: bin_data[each[1]:each[1] + length] = cfg_bins[cfg_off: cfg_off + length] cfg_off += length else: cfg_off += length print('Patched the loaded binary successfully !') return bin_data def show_data_difference(self, data_diff): if self._tk is None: return # Displays if any data difference detected in BSF and Binary file pop_up_text = 'There are differences in Config file and binary '\ 'data detected!\n' pop_up_text += data_diff window = self._tk.Tk() window.title("Data Difference") window.resizable(1, 1) # Window Size window.geometry("800x400") frame = self._tk.Frame(window, height=800, width=700) frame.pack(side=self._tk.BOTTOM) # Vertical (y) Scroll Bar scroll = self._tk.Scrollbar(window) scroll.pack(side=self._tk.RIGHT, fill=self._tk.Y) text = self._tk.Text(window, wrap=self._tk.NONE, yscrollcommand=scroll.set, width=700, height=400) text.insert(self._tk.INSERT, pop_up_text) text.pack() # Configure the scrollbars scroll.config(command=text.yview) exit_button = self._tk.Button( window, text="Close", command=window.destroy) exit_button.pack(in_=frame, side=self._tk.RIGHT, padx=20, pady=10) def load_default_from_bin(self, bin_data): self._old_bin = bin_data cfg_bins = self.extract_cfg_from_bin(bin_data) self.set_field_value(self._cfg_tree, cfg_bins, True) if self.data_diff: self.show_data_difference(self.data_diff) return cfg_bins def generate_binary_array(self, path=''): if path == '': top = None else: top = self.locate_cfg_item(path) if not top: raise Exception("Invalid configuration path '%s' !" % path) return self.get_field_value(top) def generate_binary(self, bin_file_name, path=''): bin_file = open(bin_file_name, "wb") bin_file.write(self.generate_binary_array(path)) bin_file.close() return 0 def write_delta_file(self, out_file, platform_id, out_lines): dlt_fd = open(out_file, "w") dlt_fd.write("%s\n" % get_copyright_header('dlt', True)) if platform_id is not None: dlt_fd.write('#\n') dlt_fd.write('# Delta configuration values for ' 'platform ID 0x%04X\n' % platform_id) dlt_fd.write('#\n\n') for line in out_lines: dlt_fd.write('%s\n' % line) dlt_fd.close() def override_default_value(self, dlt_file): error = 0 dlt_lines = CGenYamlCfg.expand_include_files(dlt_file) platform_id = None for line, file_path, line_num in dlt_lines: line = line.strip() if not line or line.startswith('#'): continue match = re.match("\\s*([\\w\\.]+)\\s*\\|\\s*(.+)", line) if not match: raise Exception("Unrecognized line '%s' " "(File:'%s' Line:%d) !" % (line, file_path, line_num + 1)) path = match.group(1) value_str = match.group(2) top = self.locate_cfg_item(path) if not top: raise Exception( "Invalid configuration '%s' (File:'%s' Line:%d) !" % (path, file_path, line_num + 1)) if 'indx' in top: act_cfg = self.get_item_by_index(top['indx']) bit_len = act_cfg['length'] else: struct_info = top[CGenYamlCfg.STRUCT] bit_len = struct_info['length'] value_bytes = self.parse_value(value_str, bit_len) self.set_field_value(top, value_bytes, True) if path == 'PLATFORMID_CFG_DATA.PlatformId': platform_id = value_str if platform_id is None: raise Exception( "PLATFORMID_CFG_DATA.PlatformId is missing " "in file '%s' !" % (dlt_file)) return error def generate_delta_file_from_bin(self, delta_file, old_data, new_data, full=False): new_data = self.load_default_from_bin(new_data) lines = [] platform_id = None def_platform_id = 0 for item in self._cfg_list: if not full and (item['type'] in ['Reserved']): continue old_val = get_bits_from_bytes(old_data, item['offset'], item['length']) new_val = get_bits_from_bytes(new_data, item['offset'], item['length']) full_name = item['path'] if 'PLATFORMID_CFG_DATA.PlatformId' == full_name: def_platform_id = old_val if new_val != old_val or full: val_str = self.reformat_value_str(item['value'], item['length']) text = '%-40s | %s' % (full_name, val_str) lines.append(text) if self.get_mode() != 'FSP': if platform_id is None or def_platform_id == platform_id: platform_id = def_platform_id print("WARNING: 'PlatformId' configuration is " "same as default %d!" % platform_id) lines.insert(0, '%-40s | %s\n\n' % ('PLATFORMID_CFG_DATA.PlatformId', '0x%04X' % platform_id)) else: platform_id = None self.write_delta_file(delta_file, platform_id, lines) return 0 def generate_delta_file(self, delta_file, bin_file, bin_file2, full=False): fd = open(bin_file, 'rb') new_data = self.extract_cfg_from_bin(bytearray(fd.read())) fd.close() if bin_file2 == '': old_data = self.generate_binary_array() else: old_data = new_data fd = open(bin_file2, 'rb') new_data = self.extract_cfg_from_bin(bytearray(fd.read())) fd.close() return self.generate_delta_file_from_bin(delta_file, old_data, new_data, full) def prepare_marshal(self, is_save): if is_save: # Ordered dict is not marshallable, convert to list self._cfg_tree = CGenYamlCfg.deep_convert_dict(self._cfg_tree) else: # Revert it back self._cfg_tree = CGenYamlCfg.deep_convert_list(self._cfg_tree) def generate_yml_file(self, in_file, out_file): cfg_yaml = CFG_YAML() text = cfg_yaml.expand_yaml(in_file) yml_fd = open(out_file, "w") yml_fd.write(text) yml_fd.close() return 0 def write_cfg_header_file(self, hdr_file_name, tag_mode, tag_dict, struct_list): lines = [] lines.append ('\n') if self.get_mode() == 'FSP': lines.append('#include \n') tag_mode = tag_mode & 0x7F tag_list = sorted(list(tag_dict.items()), key=lambda x: x[1]) for tagname, tagval in tag_list: if (tag_mode == 0 and tagval >= 0x100) or \ (tag_mode == 1 and tagval < 0x100): continue lines.append('#define %-30s 0x%03X\n' % ( 'CDATA_%s_TAG' % tagname[:-9], tagval)) lines.append ('\n#pragma pack(1)\n') name_dict = {} new_dict = {} for each in struct_list: if (tag_mode == 0 and each['tag'] >= 0x100) or \ (tag_mode == 1 and each['tag'] < 0x100): continue new_dict[each['name']] = (each['alias'], each['count'], each['exclude']) if each['alias'] not in name_dict: name_dict[each['alias']] = 1 lines.extend(self.create_struct(each['alias'], each['node'], new_dict)) lines.append('#pragma pack()\n\n') self.write_header_file(lines, hdr_file_name) def write_header_file(self, txt_body, file_name, type='h'): file_name_def = os.path.basename(file_name).replace('.', '_') file_name_def = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', file_name_def) file_name_def = re.sub('([a-z0-9])([A-Z])', r'\1_\2', file_name_def).upper() lines = [] lines.append("%s\n" % get_copyright_header(type)) lines.append("#ifndef __%s__\n" % file_name_def) lines.append ("#define __%s__\n" % file_name_def) lines.extend(txt_body) lines.append("#endif\n") # Don't rewrite if the contents are the same create = True if os.path.exists(file_name): hdr_file = open(file_name, "r") org_txt = hdr_file.read() hdr_file.close() new_txt = ''.join(lines) if org_txt == new_txt: create = False if create: hdr_file = open(file_name, "w") hdr_file.write(''.join(lines)) hdr_file.close() def generate_data_inc_file(self, dat_inc_file_name, bin_file=None): # Put a prefix GUID before CFGDATA so that it can be located later on prefix = b'\xa7\xbd\x7f\x73\x20\x1e\x46\xd6\ xbe\x8f\x64\x12\x05\x8d\x0a\xa8' if bin_file: fin = open(bin_file, 'rb') bin_dat = prefix + bytearray(fin.read()) fin.close() else: bin_dat = prefix + self.generate_binary_array() file_name = os.path.basename(dat_inc_file_name).upper() file_name = file_name.replace('.', '_') txt_lines = [] txt_lines.append("UINT8 mConfigDataBlob[%d] = {\n" % len(bin_dat)) count = 0 line = [' '] for each in bin_dat: line.append('0x%02X, ' % each) count = count + 1 if (count & 0x0F) == 0: line.append('\n') txt_lines.append(''.join(line)) line = [' '] if len(line) > 1: txt_lines.append(''.join(line) + '\n') txt_lines.append("};\n\n") self.write_header_file(txt_lines, dat_inc_file_name, 'inc') return 0 def get_struct_array_info(self, input): parts = input.split(':') if len(parts) > 1: var = parts[1] input = parts[0] else: var = '' array_str = input.split('[') name = array_str[0] if len(array_str) > 1: num_str = ''.join(c for c in array_str[-1] if c.isdigit()) num_str = '1000' if len(num_str) == 0 else num_str array_num = int(num_str) else: array_num = 0 return name, array_num, var def process_multilines(self, string, max_char_length): multilines = '' string_length = len(string) current_string_start = 0 string_offset = 0 break_line_dict = [] if len(string) <= max_char_length: while (string_offset < string_length): if string_offset >= 1: if string[string_offset - 1] == '\\' and string[ string_offset] == 'n': break_line_dict.append(string_offset + 1) string_offset += 1 if break_line_dict != []: for each in break_line_dict: multilines += " %s\n" % string[ current_string_start:each].lstrip() current_string_start = each if string_length - current_string_start > 0: multilines += " %s\n" % string[ current_string_start:].lstrip() else: multilines = " %s\n" % string else: new_line_start = 0 new_line_count = 0 found_space_char = False while (string_offset < string_length): if string_offset >= 1: if new_line_count >= max_char_length - 1: if string[string_offset] == ' ' and \ string_length - string_offset > 10: break_line_dict.append(new_line_start + new_line_count) new_line_start = new_line_start + new_line_count new_line_count = 0 found_space_char = True elif string_offset == string_length - 1 and \ found_space_char is False: break_line_dict.append(0) if string[string_offset - 1] == '\\' and string[ string_offset] == 'n': break_line_dict.append(string_offset + 1) new_line_start = string_offset + 1 new_line_count = 0 string_offset += 1 new_line_count += 1 if break_line_dict != []: break_line_dict.sort() for each in break_line_dict: if each > 0: multilines += " %s\n" % string[ current_string_start:each].lstrip() current_string_start = each if string_length - current_string_start > 0: multilines += " %s\n" % \ string[current_string_start:].lstrip() return multilines def create_field(self, item, name, length, offset, struct, bsf_name, help, option, bits_length=None): pos_name = 28 name_line = '' help_line='' option_line='' if length == 0 and name == 'dummy': return '\n' if bits_length == 0: return '\n' is_array = False if length in [1, 2, 4, 8]: type = "UINT%d" % (length * 8) else: is_array = True type = "UINT8" if item and item['value'].startswith('{'): type = "UINT8" is_array = True if struct != '': struct_base = struct.rstrip('*') name = '*' * (len(struct) - len(struct_base)) + name struct = struct_base type = struct if struct in ['UINT8', 'UINT16', 'UINT32', 'UINT64']: is_array = True unit = int(type[4:]) // 8 length = length / unit else: is_array = False if is_array: name = name + '[%d]' % length if len(type) < pos_name: space1 = pos_name - len(type) else: space1 = 1 if bsf_name != '': name_line = " %s\n" % bsf_name else: name_line = "N/A\n" if help != '': help_line = self.process_multilines(help, 80) if option != '': option_line = self.process_multilines(option, 80) if offset is None: offset_str = '????' else: offset_str = '0x%04X' % offset if bits_length is None: bits_length = '' else: bits_length = ' : %d' % bits_length return "\n/** %s%s%s**/\n %s%s%s%s;\n" % (name_line, help_line, option_line, type, ' ' * space1, name, bits_length) def create_struct(self, cname, top, struct_dict): index = 0 last = '' lines = [] off_base = -1 if cname in struct_dict: if struct_dict[cname][2]: return [] lines.append('\ntypedef struct {\n') for field in top: if field[0] == '$': continue index += 1 t_item = top[field] if 'indx' not in t_item: if CGenYamlCfg.STRUCT not in top[field]: continue if struct_dict[field][1] == 0: continue append = True struct_info = top[field][CGenYamlCfg.STRUCT] if 'struct' in struct_info: struct, array_num, var = self.get_struct_array_info( struct_info['struct']) if array_num > 0: if last == struct: append = False last = struct if var == '': var = field field = CGenYamlCfg.format_struct_field_name( var, struct_dict[field][1]) else: struct = struct_dict[field][0] field = CGenYamlCfg.format_struct_field_name( field, struct_dict[field][1]) if append: offset = t_item['$STRUCT']['offset'] // 8 if off_base == -1: off_base = offset line = self.create_field(None, field, 0, 0, struct, '', '', '') lines.append(' %s' % line) last = struct continue item = self.get_item_by_index(t_item['indx']) if item['cname'] == 'CfgHeader' and index == 1 or \ (item['cname'] == 'CondValue' and index == 2): continue bit_length = None length = (item['length'] + 7) // 8 match = re.match("^(\\d+)([b|B|W|D|Q])([B|W|D|Q]?)", t_item['length']) if match and match.group(2) == 'b': bit_length = int(match.group(1)) if match.group(3) != '': length = CGenYamlCfg.bits_width[match.group(3)] // 8 else: length = 4 offset = item['offset'] // 8 if off_base == -1: off_base = offset struct = item.get('struct', '') name = field prompt = item['name'] help = item['help'] option = item['option'] line = self.create_field(item, name, length, offset - off_base, struct, prompt, help, option, bit_length) lines.append(' %s' % line) last = struct lines.append('\n} %s;\n\n' % cname) return lines def write_fsp_sig_header_file(self, hdr_file_name): hdr_fd = open(hdr_file_name, 'w') hdr_fd.write("%s\n" % get_copyright_header('h')) hdr_fd.write("#ifndef __FSPUPD_H__\n" "#define __FSPUPD_H__\n\n" "#include \n\n" "#pragma pack(1)\n\n") lines = [] for fsp_comp in 'TMSI': top = self.locate_cfg_item('FSP%s_UPD' % fsp_comp) if not top: raise Exception('Could not find FSP UPD definition !') bins = self.get_field_value(top) lines.append("#define FSP%s_UPD_SIGNATURE" " 0x%016X /* '%s' */\n\n" % (fsp_comp, bytes_to_value(bins[:8]), bins[:8].decode())) hdr_fd.write(''.join(lines)) hdr_fd.write("#pragma pack()\n\n" "#endif\n") hdr_fd.close() def create_header_file(self, hdr_file_name, com_hdr_file_name='', path=''): def _build_header_struct(name, cfgs, level): if CGenYamlCfg.STRUCT in cfgs: if 'CfgHeader' in cfgs: # collect CFGDATA TAG IDs cfghdr = self.get_item_by_index(cfgs['CfgHeader']['indx']) tag_val = array_str_to_value(cfghdr['value']) >> 20 tag_dict[name] = tag_val if level == 1: tag_curr[0] = tag_val struct_dict[name] = (level, tag_curr[0], cfgs) if path == 'FSP_SIG': self.write_fsp_sig_header_file(hdr_file_name) return tag_curr = [0] tag_dict = {} struct_dict = {} if path == '': top = None else: top = self.locate_cfg_item(path) if not top: raise Exception("Invalid configuration path '%s' !" % path) _build_header_struct(path, top, 0) self.traverse_cfg_tree(_build_header_struct, top) if tag_curr[0] == 0: hdr_mode = 2 else: hdr_mode = 1 if re.match('FSP[TMSI]_UPD', path): hdr_mode |= 0x80 # filter out the items to be built for tags and structures struct_list = [] for each in struct_dict: match = False for check in CGenYamlCfg.exclude_struct: if re.match(check, each): match = True if each in tag_dict: if each not in CGenYamlCfg.include_tag: del tag_dict[each] break struct_list.append({'name': each, 'alias': '', 'count': 0, 'level': struct_dict[each][0], 'tag': struct_dict[each][1], 'node': struct_dict[each][2], 'exclude': True if match else False}) # sort by level so that the bottom level struct # will be build first to satisfy dependencies struct_list = sorted(struct_list, key=lambda x: x['level'], reverse=True) # Convert XXX_[0-9]+ to XXX as an array hint for each in struct_list: cfgs = each['node'] if 'struct' in cfgs['$STRUCT']: each['alias'], array_num, var = self.get_struct_array_info( cfgs['$STRUCT']['struct']) else: match = re.match('(\\w+)(_\\d+)', each['name']) if match: each['alias'] = match.group(1) else: each['alias'] = each['name'] # count items for array build for idx, each in enumerate(struct_list): if idx > 0: last_struct = struct_list[idx-1]['node']['$STRUCT'] curr_struct = each['node']['$STRUCT'] if struct_list[idx-1]['alias'] == each['alias'] and \ curr_struct['length'] == last_struct['length'] and \ curr_struct['offset'] == last_struct['offset'] + \ last_struct['length']: for idx2 in range(idx-1, -1, -1): if struct_list[idx2]['count'] > 0: struct_list[idx2]['count'] += 1 break continue each['count'] = 1 # generate common header if com_hdr_file_name: self.write_cfg_header_file(com_hdr_file_name, 0, tag_dict, struct_list) # generate platform header self.write_cfg_header_file(hdr_file_name, hdr_mode, tag_dict, struct_list) return 0 def load_yaml(self, cfg_file): cfg_yaml = CFG_YAML() self.initialize() self._cfg_tree = cfg_yaml.load_yaml(cfg_file) self._def_dict = cfg_yaml.def_dict self.yaml_type = cfg_yaml.yaml_type self._yaml_path = os.path.dirname(cfg_file) if self.yaml_type == 'vfr': self.build_formset_list() elif self.yaml_type == 'fsp': self.build_cfg_list() self.build_var_dict() self.update_def_value() return 0 def usage(): print('\n'.join([ "GenYamlCfg Version 0.50", "Usage:", " GenYamlCfg GENINC BinFile IncOutFile " " [-D Macros]", " GenYamlCfg GENPKL YamlFile PklOutFile " " [-D Macros]", " GenYamlCfg GENBIN YamlFile[;DltFile] BinOutFile " " [-D Macros]", " GenYamlCfg GENDLT YamlFile[;BinFile] DltOutFile " " [-D Macros]", " GenYamlCfg GENYML YamlFile YamlOutFile" " [-D Macros]", " GenYamlCfg GENHDR YamlFile HdrOutFile " " [-D Macros]" ])) def main(): # Parse the options and args argc = len(sys.argv) if argc < 4: usage() return 1 gen_cfg_data = CGenYamlCfg() command = sys.argv[1].upper() out_file = sys.argv[3] if argc >= 5 and gen_cfg_data.parse_macros(sys.argv[4:]) != 0: raise Exception("ERROR: Macro parsing failed !") file_list = sys.argv[2].split(';') if len(file_list) >= 2: yml_file = file_list[0] dlt_file = file_list[1] elif len(file_list) == 1: yml_file = file_list[0] dlt_file = '' else: raise Exception("ERROR: Invalid parameter '%s' !" % sys.argv[2]) yml_scope = '' if '@' in yml_file: parts = yml_file.split('@') yml_file = parts[0] yml_scope = parts[1] if command == "GENDLT" and yml_file.endswith('.dlt'): # It needs to expand an existing DLT file dlt_file = yml_file lines = gen_cfg_data.expand_include_files(dlt_file) write_lines(lines, out_file) return 0 if command == "GENYML": if not yml_file.lower().endswith('.yaml'): raise Exception('Only YAML file is supported !') gen_cfg_data.generate_yml_file(yml_file, out_file) return 0 bin_file = '' if (yml_file.lower().endswith('.bin')) and (command == "GENINC"): # It is binary file bin_file = yml_file yml_file = '' if bin_file: gen_cfg_data.generate_data_inc_file(out_file, bin_file) return 0 cfg_bin_file = '' cfg_bin_file2 = '' if dlt_file: if command == "GENDLT": cfg_bin_file = dlt_file dlt_file = '' if len(file_list) >= 3: cfg_bin_file2 = file_list[2] if yml_file.lower().endswith('.pkl'): with open(yml_file, "rb") as pkl_file: gen_cfg_data.__dict__ = marshal.load(pkl_file) gen_cfg_data.prepare_marshal(False) # Override macro definition again for Pickle file if argc >= 5: gen_cfg_data.parse_macros(sys.argv[4:]) else: gen_cfg_data.load_yaml(yml_file) if command == 'GENPKL': gen_cfg_data.prepare_marshal(True) with open(out_file, "wb") as pkl_file: marshal.dump(gen_cfg_data.__dict__, pkl_file) json_file = os.path.splitext(out_file)[0] + '.json' fo = open(json_file, 'w') path_list = [] cfgs = {'_cfg_page': gen_cfg_data._cfg_page, '_cfg_list': gen_cfg_data._cfg_list, '_path_list': path_list} # optimize to reduce size path = None for each in cfgs['_cfg_list']: new_path = each['path'][:-len(each['cname'])-1] if path != new_path: path = new_path each['path'] = path path_list.append(path) else: del each['path'] if each['order'] == each['offset']: del each['order'] del each['offset'] # value is just used to indicate display type value = each['value'] if value.startswith('0x'): hex_len = ((each['length'] + 7) // 8) * 2 if len(value) == hex_len: value = 'x%d' % hex_len else: value = 'x' each['value'] = value elif value and value[0] in ['"', "'", '{']: each['value'] = value[0] else: del each['value'] fo.write(repr(cfgs)) fo.close() return 0 if dlt_file: gen_cfg_data.override_default_value(dlt_file) if gen_cfg_data.yaml_type == 'fsp': gen_cfg_data.detect_fsp() if command == "GENBIN": if len(file_list) == 3: old_data = gen_cfg_data.generate_binary_array() fi = open(file_list[2], 'rb') new_data = bytearray(fi.read()) fi.close() if len(new_data) != len(old_data): raise Exception("Binary file '%s' length does not match, \ ignored !" % file_list[2]) else: gen_cfg_data.load_default_from_bin(new_data) gen_cfg_data.override_default_value(dlt_file) gen_cfg_data.generate_binary(out_file, yml_scope) elif command == "GENDLT": full = True if 'FULL' in gen_cfg_data._macro_dict else False gen_cfg_data.generate_delta_file(out_file, cfg_bin_file, cfg_bin_file2, full) elif command == "GENHDR": out_files = out_file.split(';') brd_out_file = out_files[0].strip() if len(out_files) > 1: com_out_file = out_files[1].strip() else: com_out_file = '' gen_cfg_data.create_header_file(brd_out_file, com_out_file, yml_scope) elif command == "GENINC": gen_cfg_data.generate_data_inc_file(out_file) elif command == "DEBUG": gen_cfg_data.print_cfgs() else: raise Exception("Unsuported command '%s' !" % command) return 0 if __name__ == '__main__': sys.exit(main())