Skip to content

Commit eb4957b

Browse files
Feature to display the Modified knobs in the binary vl file while loading in the UI (#605)
1 parent 49f242c commit eb4957b

2 files changed

Lines changed: 337 additions & 3 deletions

File tree

SetupDataPkg/Tools/ConfigEditor.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import base64
1111
import datetime
1212
import ctypes
13+
import copy
1314
from pathlib import Path
1415

1516
sys.dont_write_bytecode = True
@@ -30,6 +31,7 @@
3031
array_str_to_value,
3132
get_xml_full_hash
3233
)
34+
from KnobDelta import CompareConfigs # noqa: E402
3335

3436

3537
def ask_yes_no(prompt):
@@ -384,6 +386,8 @@ class application(tkinter.Frame):
384386
def __init__(self, master=None):
385387
root = master
386388
self.debug = True
389+
self.config_xml_path = None
390+
self.default_cfg_data_obj = None
387391
self.page_id = ""
388392
self.page_list = {}
389393
self.conf_list = {}
@@ -1147,6 +1151,7 @@ def load_delta_file(self, path):
11471151
# if loading xml, ensure that knobs GUID + name exist in any loaded XML
11481152
try:
11491153
updated_knobs += self.cfg_data_list[idx].cfg_data_obj.override_default_value(path)
1154+
self.default_cfg_data_obj = copy.deepcopy(self.cfg_data_list[idx].cfg_data_obj)
11501155
except Exception as e:
11511156
messagebox.showerror("LOADING ERROR", str(e))
11521157
return
@@ -1181,7 +1186,23 @@ def load_from_svd(self):
11811186
self.cfg_data_list[idx].cfg_data_obj.load_from_svd(path)
11821187
self.refresh_config_data_page()
11831188

1184-
def load_bin_file(self, path):
1189+
def print_diff_cfg(self):
1190+
if self.default_cfg_data_obj is None or not self.config_xml_path:
1191+
self.output_current_status("Skip config diff: default configuration is not initialized.")
1192+
return
1193+
1194+
if len(self.cfg_data_list) == 0:
1195+
return
1196+
1197+
idx = len(self.cfg_data_list) - 1
1198+
cmp_obj = CompareConfigs(self.config_xml_path, self.default_cfg_data_obj, self.cfg_data_list[idx].cfg_data_obj)
1199+
diff_cfg = cmp_obj.compare_mu_setting()
1200+
for knob in diff_cfg:
1201+
print(knob)
1202+
self.output_current_status(knob)
1203+
self.output_current_status("\n")
1204+
1205+
def load_bin_file(self, path, print_diff=True):
11851206
with open(path, "rb") as fd:
11861207
bin_data = bytearray(fd.read())
11871208

@@ -1191,8 +1212,11 @@ def load_bin_file(self, path):
11911212
except Exception as e:
11921213
messagebox.showerror("LOADING ERROR", str(e))
11931214
return
1194-
1195-
self.output_current_status(f"{path} file is loaded")
1215+
self.output_current_status(f"{path} file is loaded\n")
1216+
print("\n", f"{path} file is loaded\n")
1217+
if print_diff:
1218+
self.print_diff_cfg()
1219+
print()
11961220

11971221
def load_cfg_file(self, path, file_id, clear_config):
11981222
# Clear out old config if requested
@@ -1226,6 +1250,7 @@ def load_cfg_file(self, path, file_id, clear_config):
12261250

12271251
self.config_xml_path = path
12281252
self.output_current_status(f"{path} file is loaded")
1253+
self.default_cfg_data_obj = copy.deepcopy(self.cfg_data_list[file_id].cfg_data_obj)
12291254
# load xml file and get the hash value of all xml nodes
12301255
config_xml_hash = get_xml_full_hash(self.config_xml_path)
12311256

SetupDataPkg/Tools/KnobDelta.py

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
import argparse
2+
import os
3+
import re
4+
import textwrap
5+
import xml.etree.ElementTree as ET
6+
7+
from GenNCCfgData import CGenNCCfgData, get_delta_vlist
8+
9+
10+
class CompareConfigs:
11+
def __init__(self, xml_path, default_configs_obj, modified_config_obj) -> None:
12+
self.xml_path = xml_path
13+
self.default_config_obj = default_configs_obj
14+
self.bin_config_obj = modified_config_obj
15+
self.xml_information = self._parsing_xml(self.xml_path)
16+
17+
def _parsing_xml(self, xml_path):
18+
root = ET.parse(xml_path).getroot()
19+
xml_info = {}
20+
for section in root:
21+
xml_info[section.tag] = {}
22+
for child in section:
23+
if section.tag == "Knobs":
24+
namespace = section.get("namespace")
25+
if namespace is None:
26+
raise RuntimeError("Fail to get Sub root namespace")
27+
xml_info[section.tag]["guid"] = namespace.strip("{}")
28+
xml_info[section.tag][child.get("type")] = child.get("name")
29+
else:
30+
xml_info[section.tag][child.get("name")] = {}
31+
index_count = 0
32+
for item in child:
33+
if section.tag == "Enums":
34+
xml_info[section.tag][child.get("name")][item.get("name")] = item.get("value")
35+
elif section.tag == "Structs":
36+
xml_info[section.tag][child.get("name")][index_count] = {}
37+
xml_info[section.tag][child.get("name")][index_count]["name"] = item.get("name")
38+
xml_info[section.tag][child.get("name")][index_count]["value"] = item.get("default")
39+
xml_info[section.tag][child.get("name")][index_count]["type"] = item.get("type")
40+
xml_info[section.tag][child.get("name")][index_count][
41+
"prettyname"
42+
] = item.get("prettyname", "")
43+
index_count += 1
44+
return xml_info
45+
46+
def generate_csv_rows(self, schema, full, subknobs=True):
47+
rows = []
48+
guid = None
49+
name_list = get_delta_vlist(schema)[0]
50+
51+
rows.append(["Guid", "Knob", "Value", "Binary", "Help"])
52+
53+
if subknobs:
54+
for subknob in schema.subknobs:
55+
if full or subknob.name in name_list:
56+
binary = subknob.format.object_to_binary(subknob.value)
57+
string_binary = " ".join(map("%2.2x".__mod__, binary))
58+
59+
if subknob.namespace != guid:
60+
guid = subknob.namespace
61+
guid_val = guid
62+
else:
63+
guid_val = "*"
64+
65+
rows.append(
66+
[
67+
guid_val,
68+
subknob.name,
69+
subknob.format.object_to_string(subknob.value),
70+
string_binary,
71+
subknob.help,
72+
]
73+
)
74+
else:
75+
for knob in schema.knobs:
76+
if full or knob.name in name_list:
77+
binary = knob.format.object_to_binary(knob.value)
78+
string_binary = " ".join(map("%2.2x".__mod__, binary))
79+
80+
if knob.namespace != guid:
81+
guid = knob.namespace
82+
guid_val = guid
83+
else:
84+
guid_val = "*"
85+
86+
rows.append(
87+
[
88+
guid_val,
89+
knob.name,
90+
knob.format.object_to_string(knob.value),
91+
string_binary,
92+
knob.help,
93+
]
94+
)
95+
96+
return rows
97+
98+
def parse_csv_string(self, s):
99+
s = s[1:-1] if s.startswith("{") and s.endswith("}") else s
100+
out, buf, depth = [], "", 0
101+
for c in s:
102+
if c == "," and depth == 0:
103+
out.append(buf.strip())
104+
buf = ""
105+
else:
106+
buf += c
107+
if c == "{":
108+
depth += 1
109+
elif c == "}":
110+
depth -= 1
111+
if buf:
112+
out.append(buf.strip())
113+
if depth != 0:
114+
print("Unbalanced curly braces in the input string", "stop")
115+
return []
116+
return out
117+
118+
def parsing_csv_data(self, csv_rows, with_eg_token_name=False):
119+
bios_setting_dict = {}
120+
for csv_data in csv_rows[1:]:
121+
csv_knob_value = csv_data[1]
122+
csv_page_name = None
123+
for type_name, value_name in self.xml_information["Knobs"].items():
124+
if value_name == csv_knob_value:
125+
csv_page_name = type_name
126+
break
127+
128+
if csv_page_name is None:
129+
continue
130+
131+
bios_setting_dict[csv_page_name] = {}
132+
csv_string_values = self.parse_csv_string(csv_data[2])
133+
for index, csv_string_value in enumerate(csv_string_values):
134+
name = self.xml_information["Structs"][csv_page_name][index]["name"]
135+
if with_eg_token_name is False:
136+
bios_setting_dict[csv_page_name][name] = csv_string_value
137+
else:
138+
prettyname = self.xml_information["Structs"][csv_page_name][index]["prettyname"]
139+
if prettyname == "":
140+
bios_setting_dict[csv_page_name][name] = csv_string_value
141+
else:
142+
bios_setting_dict[csv_page_name][name] = f"{csv_string_value}|{prettyname}"
143+
144+
return bios_setting_dict
145+
146+
def compare_mu_setting(self):
147+
bin_csv_data = self.generate_csv_rows(self.bin_config_obj.schema, True, False)
148+
default_csv_data = self.generate_csv_rows(self.default_config_obj.schema, True, False)
149+
modified_csv_data = self.parsing_csv_data(bin_csv_data)
150+
default_csv_data = self.parsing_csv_data(default_csv_data)
151+
152+
pattern = r"^\d+$|^0[xX][0-9a-fA-F]+$"
153+
154+
differences_found = False
155+
diff_cfg_list = []
156+
for bios_page in default_csv_data:
157+
if bios_page not in modified_csv_data:
158+
differences_found = True
159+
continue
160+
161+
for name, cur_value in default_csv_data[bios_page].items():
162+
if name not in modified_csv_data[bios_page]:
163+
print(f"[Missing Setting] Page: {bios_page}, Knob: {name} not found in target CSV")
164+
differences_found = True
165+
continue
166+
167+
target_value = modified_csv_data[bios_page][name]
168+
169+
if re.match(pattern, cur_value) and re.match(pattern, target_value):
170+
if int(cur_value, 0) != int(target_value, 0):
171+
diff_cfg_list.append(f"Modified {bios_page}.{name} from {cur_value} to {target_value} !")
172+
differences_found = True
173+
else:
174+
if cur_value != target_value:
175+
diff_cfg_list.append(f"Modified {bios_page}.{name} from {cur_value} to {target_value} !")
176+
differences_found = True
177+
178+
if not differences_found:
179+
diff_cfg_list.append("✅ No Modified knobs")
180+
181+
return diff_cfg_list
182+
183+
184+
def load_bin_file_configs(bin_file, bin_configs_obj):
185+
with open(bin_file, "rb") as fd:
186+
new_data = bytearray(fd.read())
187+
bin_configs_obj.load_default_from_bin(new_data, True)
188+
189+
190+
def load_xml_file_configs(flavor, xml_folder, default_configs_obj, flavor_csv_path=None):
191+
if flavor == "GN":
192+
return
193+
194+
try:
195+
if flavor_csv_path:
196+
default_csv_file_path = os.path.abspath(flavor_csv_path)
197+
if not os.path.isfile(default_csv_file_path):
198+
raise RuntimeError(f"Flavor CSV file not found: {default_csv_file_path}")
199+
else:
200+
files = os.listdir(xml_folder)
201+
csv_file = ""
202+
for file in files:
203+
if flavor in file and file.lower().endswith(".csv"):
204+
csv_file = file
205+
break
206+
if not csv_file:
207+
raise RuntimeError(
208+
f"Failed to find {flavor} flavor CSV in folder: {xml_folder}. "
209+
"Use --flavor-csv to provide it explicitly."
210+
)
211+
default_csv_file_path = os.path.join(xml_folder, csv_file)
212+
213+
default_configs_obj.override_default_value(default_csv_file_path)
214+
except Exception as e:
215+
raise RuntimeError("LOADING ERROR", str(e))
216+
217+
218+
def parse_args():
219+
parser = argparse.ArgumentParser(
220+
prog="KnobDelta.py",
221+
formatter_class=argparse.RawDescriptionHelpFormatter,
222+
description="Compare a .vl config against defaults from a base XML config.",
223+
epilog=textwrap.dedent(
224+
"""\
225+
Examples:
226+
python KnobDelta.py --bin data.vl --xml-path C:\\path\\config.xml
227+
python KnobDelta.py -v data.vl -p C:\\path\\config.xml
228+
-f <FlavorName>(e.g. GN)
229+
--flavor-csv C:\\path\\profile.csv
230+
python KnobDelta.py -v data.vl -p C:\\path\\config.xml
231+
-f <FlavorName>(e.g. GN) -c C:\\path\\profile.csv
232+
233+
Flavor behavior:
234+
Base BIOS flavors use XML defaults directly.
235+
Non-Base BIOS flavors can use --flavor-csv explicitly.
236+
Otherwise the tool searches the XML folder.
237+
"""
238+
),
239+
)
240+
241+
parser.add_argument(
242+
"-v",
243+
"--bin",
244+
required=True,
245+
metavar="FILE",
246+
help="Path to .vl config file (example: data.vl).",
247+
)
248+
parser.add_argument(
249+
"-p",
250+
"--xml-path",
251+
"--xml_path",
252+
required=True,
253+
metavar="FILE",
254+
help="Path to base XML config used as schema/default source.",
255+
)
256+
parser.add_argument(
257+
"-f",
258+
"--flavor",
259+
default="GN",
260+
metavar="NAME",
261+
help="Flavor name (default: GN).",
262+
)
263+
parser.add_argument(
264+
"-c",
265+
"--flavor-csv",
266+
default=None,
267+
metavar="FILE",
268+
help="Optional path to flavor CSV (recommended for non-Base BIOS flavors).",
269+
)
270+
parser.add_argument(
271+
"flavor_csv_positional",
272+
nargs="?",
273+
default=None,
274+
metavar="PROFILE_CSV",
275+
help="Optional trailing flavor CSV path (same as --flavor-csv).",
276+
)
277+
278+
args = parser.parse_args()
279+
280+
if args.flavor_csv is None and args.flavor_csv_positional is not None:
281+
args.flavor_csv = args.flavor_csv_positional
282+
283+
if not os.path.isfile(args.bin):
284+
parser.error(f"Binary file not found: {args.bin}")
285+
if not os.path.isfile(args.xml_path):
286+
parser.error(f"XML file not found: {args.xml_path}")
287+
if args.flavor.upper() != "GN" and args.flavor_csv and not os.path.isfile(args.flavor_csv):
288+
parser.error(f"Flavor CSV file not found: {args.flavor_csv}")
289+
290+
return args
291+
292+
293+
if __name__ == "__main__":
294+
args = parse_args()
295+
296+
xml_path = os.path.abspath(args.xml_path)
297+
xml_folder = os.path.dirname(xml_path)
298+
flavor = args.flavor.upper()
299+
300+
default_configs_obj = CGenNCCfgData(xml_path)
301+
bin_configs_obj = CGenNCCfgData(xml_path)
302+
303+
load_bin_file_configs(args.bin, bin_configs_obj)
304+
load_xml_file_configs(flavor, xml_folder, default_configs_obj, args.flavor_csv)
305+
306+
comp_results = CompareConfigs(xml_path, default_configs_obj, bin_configs_obj)
307+
diff_config_knobs = comp_results.compare_mu_setting()
308+
for knob in diff_config_knobs:
309+
print(knob)

0 commit comments

Comments
 (0)