IntelFsp2Pkg: Add Config Editor tool support

BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3396

This is a GUI interface that can be used by users who
would like to change configuration settings directly
from the interface without having to modify the source.

This tool depends on Python GUI tool kit Tkinter.
It runs on both Windows and Linux.

The user needs to load the YAML file along with DLT file
for a specific board into the ConfigEditor, change the desired
configuration values. Finally, generate a new configuration delta
file or a config binary blob for the newly changed values to take
effect. These will be the inputs to the merge tool or the stitch
tool so that new config changes can be merged and stitched into
the final configuration blob.

This tool also supports binary update directly and display FSP
information. It is also backward compatible for BSF file format.

Running Configuration Editor:
python ConfigEditor.py

Co-authored-by: Maurice Ma <maurice.ma@intel.com>
Cc: Maurice Ma <maurice.ma@intel.com>
Cc: Nate DeSimone <nathaniel.l.desimone@intel.com>
Cc: Star Zeng <star.zeng@intel.com>
Cc: Chasel Chiu <chasel.chiu@intel.com>
Signed-off-by: Loo Tung Lun <tung.lun.loo@intel.com>
Reviewed-by: Chasel Chiu <chasel.chiu@intel.com>
This commit is contained in:
Loo, Tung Lun
2021-06-29 12:32:35 +08:00
committed by mergify[bot]
parent 55dee4947b
commit 580b11201e
7 changed files with 7338 additions and 294 deletions

View File

@ -10,277 +10,38 @@
import os
import re
import sys
from datetime import date
from collections import OrderedDict
from functools import reduce
from GenCfgOpt import CGenCfgOpt
from collections import OrderedDict
from datetime import date
from FspGenCfgData import CFspBsf2Dsc, CGenCfgData
__copyright_tmp__ = """## @file
#
# YAML CFGDATA %s File.
# Slim Bootloader CFGDATA %s File.
#
# Copyright(c) %4d, Intel Corporation. All rights reserved.<BR>
# Copyright (c) %4d, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##
"""
__copyright_dsc__ = """## @file
#
# Copyright (c) %04d, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##
[PcdsDynamicVpd.Upd]
#
# Global definitions in BSF
# !BSF BLOCK:{NAME:"FSP UPD Configuration", VER:"0.1"}
#
"""
def Bytes2Val(Bytes):
return reduce(lambda x, y: (x << 8) | y, Bytes[::-1])
def Str2Bytes(Value, Blen):
Result = bytearray(Value[1:-1], 'utf-8') # Excluding quotes
if len(Result) < Blen:
Result.extend(b'\x00' * (Blen - len(Result)))
return Result
class CFspBsf2Dsc:
def __init__(self, bsf_file):
self.cfg_list = CFspBsf2Dsc.parse_bsf(bsf_file)
def get_dsc_lines(self):
return CFspBsf2Dsc.generate_dsc(self.cfg_list)
def save_dsc(self, dsc_file):
return CFspBsf2Dsc.generate_dsc(self.cfg_list, dsc_file)
@staticmethod
def parse_bsf(bsf_file):
fd = open(bsf_file, 'r')
bsf_txt = fd.read()
fd.close()
find_list = []
regex = re.compile(r'\s+Find\s+"(.*?)"(.*?)^\s+\$(.*?)\s+', re.S | re.MULTILINE)
for match in regex.finditer(bsf_txt):
find = match.group(1)
name = match.group(3)
if not name.endswith('_Revision'):
raise Exception("Unexpected CFG item following 'Find' !")
find_list.append((name, find))
idx = 0
count = 0
prefix = ''
chk_dict = {}
cfg_list = []
cfg_temp = {'find': '', 'cname': '', 'length': 0, 'value': '0', 'type': 'Reserved',
'embed': '', 'page': '', 'option': '', 'instance': 0}
regex = re.compile(r'^\s+(\$(.*?)|Skip)\s+(\d+)\s+bytes(\s+\$_DEFAULT_\s+=\s+(.+?))?$',
re.S | re.MULTILINE)
for match in regex.finditer(bsf_txt):
dlen = int(match.group(3))
if match.group(1) == 'Skip':
key = 'gPlatformFspPkgTokenSpaceGuid_BsfSkip%d' % idx
val = ', '.join(['%02X' % ord(i) for i in '\x00' * dlen])
idx += 1
option = '$SKIP'
else:
key = match.group(2)
val = match.group(5)
option = ''
cfg_item = dict(cfg_temp)
finds = [i for i in find_list if i[0] == key]
if len(finds) > 0:
if count >= 1:
# Append a dummy one
cfg_item['cname'] = 'Dummy'
cfg_list.append(dict(cfg_item))
cfg_list[-1]['embed'] = '%s:TAG_%03X:END' % (prefix, ord(prefix[-1]))
prefix = finds[0][1]
cfg_item['embed'] = '%s:TAG_%03X:START' % (prefix, ord(prefix[-1]))
cfg_item['find'] = prefix
cfg_item['cname'] = 'Signature'
cfg_item['length'] = len(finds[0][1])
str2byte = Str2Bytes("'" + finds[0][1] + "'", len(finds[0][1]))
cfg_item['value'] = '0x%X' % Bytes2Val(str2byte)
cfg_list.append(dict(cfg_item))
cfg_item = dict(cfg_temp)
find_list.pop(0)
count = 0
cfg_item['cname'] = key
cfg_item['length'] = dlen
cfg_item['value'] = val
cfg_item['option'] = option
if key not in chk_dict.keys():
chk_dict[key] = 0
else:
chk_dict[key] += 1
cfg_item['instance'] = chk_dict[key]
cfg_list.append(cfg_item)
count += 1
if prefix:
cfg_item = dict(cfg_temp)
cfg_item['cname'] = 'Dummy'
cfg_item['embed'] = '%s:%03X:END' % (prefix, ord(prefix[-1]))
cfg_list.append(cfg_item)
option_dict = {}
selreg = re.compile(r'\s+Selection\s*(.+?)\s*,\s*"(.*?)"$', re.S | re.MULTILINE)
regex = re.compile(r'^List\s&(.+?)$(.+?)^EndList$', re.S | re.MULTILINE)
for match in regex.finditer(bsf_txt):
key = match.group(1)
option_dict[key] = []
for select in selreg.finditer(match.group(2)):
option_dict[key].append((int(select.group(1), 0), select.group(2)))
chk_dict = {}
pagereg = re.compile(r'^Page\s"(.*?)"$(.+?)^EndPage$', re.S | re.MULTILINE)
for match in pagereg.finditer(bsf_txt):
page = match.group(1)
for line in match.group(2).splitlines():
match = re.match(r'\s+(Combo|EditNum)\s\$(.+?),\s"(.*?)",\s(.+?),$', line)
if match:
cname = match.group(2)
if cname not in chk_dict.keys():
chk_dict[cname] = 0
else:
chk_dict[cname] += 1
instance = chk_dict[cname]
cfg_idxs = [i for i, j in enumerate(cfg_list) if j['cname'] == cname and j['instance'] == instance]
if len(cfg_idxs) != 1:
raise Exception("Multiple CFG item '%s' found !" % cname)
cfg_item = cfg_list[cfg_idxs[0]]
cfg_item['page'] = page
cfg_item['type'] = match.group(1)
cfg_item['prompt'] = match.group(3)
cfg_item['range'] = None
if cfg_item['type'] == 'Combo':
cfg_item['option'] = option_dict[match.group(4)[1:]]
elif cfg_item['type'] == 'EditNum':
cfg_item['option'] = match.group(4)
match = re.match(r'\s+ Help\s"(.*?)"$', line)
if match:
cfg_item['help'] = match.group(1)
match = re.match(r'\s+"Valid\srange:\s(.*)"$', line)
if match:
parts = match.group(1).split()
cfg_item['option'] = (
(int(parts[0], 0), int(parts[2], 0), cfg_item['option']))
return cfg_list
@staticmethod
def generate_dsc(option_list, dsc_file=None):
dsc_lines = []
header = '%s' % (__copyright_dsc__ % date.today().year)
dsc_lines.extend(header.splitlines())
pages = []
for cfg_item in option_list:
if cfg_item['page'] and (cfg_item['page'] not in pages):
pages.append(cfg_item['page'])
page_id = 0
for page in pages:
dsc_lines.append(' # !BSF PAGES:{PG%02X::"%s"}' % (page_id, page))
page_id += 1
dsc_lines.append('')
last_page = ''
for option in option_list:
dsc_lines.append('')
default = option['value']
pos = option['cname'].find('_')
name = option['cname'][pos + 1:]
if option['find']:
dsc_lines.append(' # !BSF FIND:{%s}' % option['find'])
dsc_lines.append('')
if option['instance'] > 0:
name = name + '_%s' % option['instance']
if option['embed']:
dsc_lines.append(' # !HDR EMBED:{%s}' % option['embed'])
if option['type'] == 'Reserved':
dsc_lines.append(' # !BSF NAME:{Reserved} TYPE:{Reserved}')
if option['option'] == '$SKIP':
dsc_lines.append(' # !BSF OPTION:{$SKIP}')
else:
prompt = option['prompt']
if last_page != option['page']:
last_page = option['page']
dsc_lines.append(' # !BSF PAGE:{PG%02X}' % (pages.index(option['page'])))
if option['type'] == 'Combo':
dsc_lines.append(' # !BSF NAME:{%s} TYPE:{%s}' %
(prompt, option['type']))
ops = []
for val, text in option['option']:
ops.append('0x%x:%s' % (val, text))
dsc_lines.append(' # !BSF OPTION:{%s}' % (', '.join(ops)))
elif option['type'] == 'EditNum':
cfg_len = option['length']
if ',' in default and cfg_len > 8:
dsc_lines.append(' # !BSF NAME:{%s} TYPE:{Table}' % (prompt))
if cfg_len > 16:
cfg_len = 16
ops = []
for i in range(cfg_len):
ops.append('%X:1:HEX' % i)
dsc_lines.append(' # !BSF OPTION:{%s}' % (', '.join(ops)))
else:
dsc_lines.append(
' # !BSF NAME:{%s} TYPE:{%s, %s,(0x%X, 0x%X)}' %
(prompt, option['type'], option['option'][2],
option['option'][0], option['option'][1]))
dsc_lines.append(' # !BSF HELP:{%s}' % option['help'])
if ',' in default:
default = '{%s}' % default
dsc_lines.append(' gCfgData.%-30s | * | 0x%04X | %s' %
(name, option['length'], default))
if dsc_file:
fd = open(dsc_file, 'w')
fd.write('\n'.join(dsc_lines))
fd.close()
return dsc_lines
class CFspDsc2Yaml():
def __init__(self):
self._Hdr_key_list = ['EMBED', 'STRUCT']
self._Bsf_key_list = ['NAME', 'HELP', 'TYPE', 'PAGE', 'PAGES', 'OPTION',
'CONDITION', 'ORDER', 'MARKER', 'SUBT', 'FIELD', 'FIND']
self._Bsf_key_list = ['NAME', 'HELP', 'TYPE', 'PAGE', 'PAGES',
'OPTION', 'CONDITION', 'ORDER', 'MARKER',
'SUBT', 'FIELD', 'FIND']
self.gen_cfg_data = None
self.cfg_reg_exp = re.compile(r"^([_a-zA-Z0-9$\(\)]+)\s*\|\s*(0x[0-9A-F]+|\*)\s*\|"
+ r"\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)")
self.bsf_reg_exp = re.compile(r"(%s):{(.+?)}(?:$|\s+)" % '|'.join(self._Bsf_key_list))
self.hdr_reg_exp = re.compile(r"(%s):{(.+?)}" % '|'.join(self._Hdr_key_list))
self.cfg_reg_exp = re.compile(
"^([_a-zA-Z0-9$\\(\\)]+)\\s*\\|\\s*(0x[0-9A-F]+|\\*)"
"\\s*\\|\\s*(\\d+|0x[0-9a-fA-F]+)\\s*\\|\\s*(.+)")
self.bsf_reg_exp = re.compile("(%s):{(.+?)}(?:$|\\s+)"
% '|'.join(self._Bsf_key_list))
self.hdr_reg_exp = re.compile("(%s):{(.+?)}"
% '|'.join(self._Hdr_key_list))
self.prefix = ''
self.unused_idx = 0
self.offset = 0
@ -290,15 +51,15 @@ class CFspDsc2Yaml():
"""
Load and parse a DSC CFGDATA file.
"""
gen_cfg_data = CGenCfgOpt('FSP')
gen_cfg_data = CGenCfgData('FSP')
if file_name.endswith('.dsc'):
# if gen_cfg_data.ParseDscFileYaml(file_name, '') != 0:
if gen_cfg_data.ParseDscFile(file_name, '') != 0:
if gen_cfg_data.ParseDscFile(file_name) != 0:
raise Exception('DSC file parsing error !')
if gen_cfg_data.CreateVarDict() != 0:
raise Exception('DSC variable creation error !')
else:
raise Exception('Unsupported file "%s" !' % file_name)
gen_cfg_data.UpdateDefaultValue()
self.gen_cfg_data = gen_cfg_data
def print_dsc_line(self):
@ -312,14 +73,15 @@ class CFspDsc2Yaml():
"""
Format a CFGDATA item into YAML format.
"""
if(not text.startswith('!expand')) and (': ' in text):
if (not text.startswith('!expand')) and (': ' in text):
tgt = ':' if field == 'option' else '- '
text = text.replace(': ', tgt)
lines = text.splitlines()
if len(lines) == 1 and field != 'help':
return text
else:
return '>\n ' + '\n '.join([indent + i.lstrip() for i in lines])
return '>\n ' + '\n '.join(
[indent + i.lstrip() for i in lines])
def reformat_pages(self, val):
# Convert XXX:YYY into XXX::YYY format for page definition
@ -355,14 +117,16 @@ class CFspDsc2Yaml():
cfg['page'] = self.reformat_pages(cfg['page'])
if 'struct' in cfg:
cfg['value'] = self.reformat_struct_value(cfg['struct'], cfg['value'])
cfg['value'] = self.reformat_struct_value(
cfg['struct'], cfg['value'])
def parse_dsc_line(self, dsc_line, config_dict, init_dict, include):
"""
Parse a line in DSC and update the config dictionary accordingly.
"""
init_dict.clear()
match = re.match(r'g(CfgData|\w+FspPkgTokenSpaceGuid)\.(.+)', dsc_line)
match = re.match('g(CfgData|\\w+FspPkgTokenSpaceGuid)\\.(.+)',
dsc_line)
if match:
match = self.cfg_reg_exp.match(match.group(2))
if not match:
@ -385,7 +149,7 @@ class CFspDsc2Yaml():
self.offset = offset + int(length, 0)
return True
match = re.match(r"^\s*#\s+!([<>])\s+include\s+(.+)", dsc_line)
match = re.match("^\\s*#\\s+!([<>])\\s+include\\s+(.+)", dsc_line)
if match and len(config_dict) == 0:
# !include should not be inside a config field
# if so, do not convert include into YAML
@ -398,7 +162,7 @@ class CFspDsc2Yaml():
config_dict['include'] = ''
return True
match = re.match(r"^\s*#\s+(!BSF|!HDR)\s+(.+)", dsc_line)
match = re.match("^\\s*#\\s+(!BSF|!HDR)\\s+(.+)", dsc_line)
if not match:
return False
@ -434,16 +198,19 @@ class CFspDsc2Yaml():
tmp_name = parts[0][:-5]
if tmp_name == 'CFGHDR':
cfg_tag = '_$FFF_'
sval = '!expand { %s_TMPL : [ ' % tmp_name + '%s, %s, ' % (parts[1], cfg_tag) \
+ ', '.join(parts[2:]) + ' ] }'
sval = '!expand { %s_TMPL : [ ' % \
tmp_name + '%s, %s, ' % (parts[1], cfg_tag) + \
', '.join(parts[2:]) + ' ] }'
else:
sval = '!expand { %s_TMPL : [ ' % tmp_name + ', '.join(parts[1:]) + ' ] }'
sval = '!expand { %s_TMPL : [ ' % \
tmp_name + ', '.join(parts[1:]) + ' ] }'
config_dict.clear()
config_dict['cname'] = tmp_name
config_dict['expand'] = sval
return True
else:
if key in ['name', 'help', 'option'] and val.startswith('+'):
if key in ['name', 'help', 'option'] and \
val.startswith('+'):
val = config_dict[key] + '\n' + val[1:]
if val.strip() == '':
val = "''"
@ -493,21 +260,23 @@ class CFspDsc2Yaml():
include_file = ['.']
for line in lines:
match = re.match(r"^\s*#\s+!([<>])\s+include\s+(.+)", line)
match = re.match("^\\s*#\\s+!([<>])\\s+include\\s+(.+)", line)
if match:
if match.group(1) == '<':
include_file.append(match.group(2))
else:
include_file.pop()
match = re.match(r"^\s*#\s+(!BSF)\s+DEFT:{(.+?):(START|END)}", line)
match = re.match(
"^\\s*#\\s+(!BSF)\\s+DEFT:{(.+?):(START|END)}", line)
if match:
if match.group(3) == 'START' and not template_name:
template_name = match.group(2).strip()
temp_file_dict[template_name] = list(include_file)
bsf_temp_dict[template_name] = []
if match.group(3) == 'END' and (template_name == match.group(2).strip()) \
and template_name:
if match.group(3) == 'END' and \
(template_name == match.group(2).strip()) and \
template_name:
template_name = ''
else:
if template_name:
@ -531,12 +300,14 @@ class CFspDsc2Yaml():
init_dict.clear()
padding_dict = {}
cfgs.append(padding_dict)
padding_dict['cname'] = 'UnusedUpdSpace%d' % self.unused_idx
padding_dict['cname'] = 'UnusedUpdSpace%d' % \
self.unused_idx
padding_dict['length'] = '0x%x' % num
padding_dict['value'] = '{ 0 }'
self.unused_idx += 1
if cfgs and cfgs[-1]['cname'][0] != '@' and config_dict['cname'][0] == '@':
if cfgs and cfgs[-1]['cname'][0] != '@' and \
config_dict['cname'][0] == '@':
# it is a bit field, mark the previous one as virtual
cname = cfgs[-1]['cname']
new_cfg = dict(cfgs[-1])
@ -545,7 +316,8 @@ class CFspDsc2Yaml():
cfgs[-1]['cname'] = cname
cfgs.append(new_cfg)
if cfgs and cfgs[-1]['cname'] == 'CFGHDR' and config_dict['cname'][0] == '<':
if cfgs and cfgs[-1]['cname'] == 'CFGHDR' and \
config_dict['cname'][0] == '<':
# swap CfgHeader and the CFG_DATA order
if ':' in config_dict['cname']:
# replace the real TAG for CFG_DATA
@ -661,7 +433,7 @@ class CFspDsc2Yaml():
lines = []
for each in self.gen_cfg_data._MacroDict:
key, value = self.variable_fixup(each)
lines.append('%-30s : %s' % (key, value))
lines.append('%-30s : %s' % (key, value))
return lines
def output_template(self):
@ -671,7 +443,8 @@ class CFspDsc2Yaml():
self.offset = 0
self.base_offset = 0
start, end = self.get_section_range('PcdsDynamicVpd.Tmp')
bsf_temp_dict, temp_file_dict = self.process_template_lines(self.gen_cfg_data._DscLines[start:end])
bsf_temp_dict, temp_file_dict = self.process_template_lines(
self.gen_cfg_data._DscLines[start:end])
template_dict = dict()
lines = []
file_lines = {}
@ -679,15 +452,18 @@ class CFspDsc2Yaml():
file_lines[last_file] = []
for tmp_name in temp_file_dict:
temp_file_dict[tmp_name][-1] = self.normalize_file_name(temp_file_dict[tmp_name][-1], True)
temp_file_dict[tmp_name][-1] = self.normalize_file_name(
temp_file_dict[tmp_name][-1], True)
if len(temp_file_dict[tmp_name]) > 1:
temp_file_dict[tmp_name][-2] = self.normalize_file_name(temp_file_dict[tmp_name][-2], True)
temp_file_dict[tmp_name][-2] = self.normalize_file_name(
temp_file_dict[tmp_name][-2], True)
for tmp_name in bsf_temp_dict:
file = temp_file_dict[tmp_name][-1]
if last_file != file and len(temp_file_dict[tmp_name]) > 1:
inc_file = temp_file_dict[tmp_name][-2]
file_lines[inc_file].extend(['', '- !include %s' % temp_file_dict[tmp_name][-1], ''])
file_lines[inc_file].extend(
['', '- !include %s' % temp_file_dict[tmp_name][-1], ''])
last_file = file
if file not in file_lines:
file_lines[file] = []
@ -708,7 +484,8 @@ class CFspDsc2Yaml():
self.offset = 0
self.base_offset = 0
start, end = self.get_section_range('PcdsDynamicVpd.Upd')
cfgs = self.process_option_lines(self.gen_cfg_data._DscLines[start:end])
cfgs = self.process_option_lines(
self.gen_cfg_data._DscLines[start:end])
self.config_fixup(cfgs)
file_lines = self.output_dict(cfgs, True)
return file_lines
@ -721,13 +498,17 @@ class CFspDsc2Yaml():
level = 0
file = '.'
for each in cfgs:
if 'length' in each and int(each['length'], 0) == 0:
continue
if 'length' in each:
if not each['length'].endswith('b') and int(each['length'],
0) == 0:
continue
if 'include' in each:
if each['include']:
each['include'] = self.normalize_file_name(each['include'])
file_lines[file].extend(['', '- !include %s' % each['include'], ''])
each['include'] = self.normalize_file_name(
each['include'])
file_lines[file].extend(
['', '- !include %s' % each['include'], ''])
file = each['include']
else:
file = '.'
@ -766,7 +547,8 @@ class CFspDsc2Yaml():
for field in each:
if field in ['cname', 'expand', 'include']:
continue
value_str = self.format_value(field, each[field], padding + ' ' * 16)
value_str = self.format_value(
field, each[field], padding + ' ' * 16)
full_line = ' %s %-12s : %s' % (padding, field, value_str)
lines.extend(full_line.splitlines())
@ -802,11 +584,13 @@ def dsc_to_yaml(dsc_file, yaml_file):
if file == '.':
cfgs[cfg] = lines
else:
if('/' in file or '\\' in file):
if ('/' in file or '\\' in file):
continue
file = os.path.basename(file)
fo = open(os.path.join(file), 'w')
fo.write(__copyright_tmp__ % (cfg, date.today().year) + '\n\n')
out_dir = os.path.dirname(file)
fo = open(os.path.join(out_dir, file), 'w')
fo.write(__copyright_tmp__ % (
cfg, date.today().year) + '\n\n')
for line in lines:
fo.write(line + '\n')
fo.close()
@ -821,13 +605,11 @@ def dsc_to_yaml(dsc_file, yaml_file):
fo.write('\n\ntemplate:\n')
for line in cfgs['Template']:
if line != '':
fo.write(' ' + line + '\n')
fo.write(' ' + line + '\n')
fo.write('\n\nconfigs:\n')
for line in cfgs['Option']:
if line != '':
fo.write(' ' + line + '\n')
fo.write(' ' + line + '\n')
fo.close()
@ -864,7 +646,8 @@ def main():
bsf_file = sys.argv[1]
yaml_file = sys.argv[2]
if os.path.isdir(yaml_file):
yaml_file = os.path.join(yaml_file, get_fsp_name_from_path(bsf_file) + '.yaml')
yaml_file = os.path.join(
yaml_file, get_fsp_name_from_path(bsf_file) + '.yaml')
if bsf_file.endswith('.dsc'):
dsc_file = bsf_file