Skip to content

Commit

Permalink
Add new features in config editor (#413)
Browse files Browse the repository at this point in the history
## Description

In this PR, we added the below new features in config editor
1. UI menu to read/write variable from SUT in runtime
2. UI menu to delete variable listed in platformcfg.xml on SUT in
runtime
3. UI to show, admin mode, manufacturing mode and mfci policy on the
lower right side
4. Status box show log output from config editor so that user can know
what vl or xml file is loaded

![image](https://github.com/user-attachments/assets/e6cc1985-5fe8-429f-bb96-89a87188bd99)

- [ ] Impacts functionality?
- [ ] Impacts security?
- [ ] Breaking change?
- [x] Includes tests?
- [ ] Includes documentation?

## How This Was Tested
We tested on real SUT and verify each function we added. 

## Integration Instructions
N/A

---------

Co-authored-by: Catvin <[email protected]>
Co-authored-by: HungYu Hsiao (UST Global Inc) <[email protected]>
Co-authored-by: Aaron Pop <[email protected]>
  • Loading branch information
4 people authored Nov 5, 2024
1 parent 45a6d33 commit d4dcb4d
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 22 deletions.
8 changes: 7 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@
"headerRef",
"modea",
"modeb",
"uefivariablesupport"
"uefivariablesupport",
"mfci",
"Mfci",
"MFCI",
"errorcode",
"relx",
"RSMB"
]
}
92 changes: 92 additions & 0 deletions SetupDataPkg/Tools/BoardMiscInfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

# @file
#
# Module to read infomation such as smbios or MFCI policy
#
# Copyright (c), Microsoft Corporation
# SPDX-License-Identifier: BSD-2-Clause-Patent

import ctypes
import struct
from edk2toollib.os.uefivariablesupport import UefiVariable


def get_mfci_policy():
"""
Function to fetch and return the MFCI policy result from UEFI variable.
Returns the MFCI policy in hexadecimal format or 'Unknown' if an error occurs.
"""
CURRENT_MFCI_POLICY = "CurrentMfciPolicy"
MFCI_VENDOR_GUID = "EBA1A9D2-BF4D-4736-B680-B36AFB4DD65B"
# Load the kernel32.dll library
UefiVar = UefiVariable()
(errorcode, data) = UefiVar.GetUefiVar(CURRENT_MFCI_POLICY, MFCI_VENDOR_GUID)

result = "Unknown"
if errorcode == 0:
mfci_value = int.from_bytes(data, byteorder="little", signed=False)
result = f"0x{mfci_value:016x}"

return result


def locate_smbios_data():
# Define constants
FIRMWARE_TABLE_ID = 0x52534D42 # 'RSMB' ascii signature for smbios table
SMBIOS_TABLE = 0x53

# Load the kernel32.dll library
kernel32 = ctypes.windll.kernel32

buffer_size = kernel32.GetSystemFirmwareTable(FIRMWARE_TABLE_ID, SMBIOS_TABLE, None, 0)
buffer = ctypes.create_string_buffer(buffer_size)
kernel32.GetSystemFirmwareTable(FIRMWARE_TABLE_ID, SMBIOS_TABLE, buffer, buffer_size)

# Convert the buffer to bytes for easier manipulation
smbios_data = buffer.raw
return smbios_data


# Helper function to calculate the total string data of an SMBIOS entry
def calc_smbios_string_len(smbios_data, string_data_offset):

# Iterate until we find a double-zero sequence indicating the end of the structure
i = 1
while smbios_data[string_data_offset + i - 1] != 0 or smbios_data[string_data_offset + i] != 0:
i += 1

# Return the total string length
return i + 1


def locate_smbios_entry(smbios_type):
found_smbios_entry = []
smbios_data = locate_smbios_data()

# Offset the first 8 bytes of SMBIOS entry data
offset = 8

# Iterate over all SMBIOS structures until we find given smbios_type
while offset < len(smbios_data):
# SMBIOS HEADER Type (1 byte), Length (1 byte), and Handle (2 bytes)
structure_type, structure_length, handle = struct.unpack_from("<BBH", smbios_data, offset)

if structure_type == 127: # End-of-table marker (type 127)
print("End of SMBIOS table reached.")
break

# Calculate the length of the current entry structure
string_data_offset = offset + structure_length
string_data_size = calc_smbios_string_len(smbios_data, string_data_offset)
total_struct_len = string_data_size + structure_length

if structure_type == smbios_type:
found_smbios_entry = found_smbios_entry + [smbios_data[offset:offset + total_struct_len]]

# Move to the next structure
offset += total_struct_len

if found_smbios_entry == []:
print(f"Type {smbios_type} structure not found.")
return None
return found_smbios_entry
129 changes: 125 additions & 4 deletions SetupDataPkg/Tools/ConfigEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import base64
import datetime
import ctypes
from pathlib import Path

sys.dont_write_bytecode = True
Expand All @@ -18,6 +19,10 @@
import tkinter.messagebox as messagebox # noqa: E402
import tkinter.filedialog as filedialog # noqa: E402
from GenNCCfgData import CGenNCCfgData # noqa: E402
import WriteConfVarListToUefiVars as uefi_var_write # noqa: E402
import ReadUefiVarsToConfVarList as uefi_var_read # noqa: E402
import BoardMiscInfo # noqa: E402
from VariableList import Schema # noqa: E402
from CommonUtility import ( # noqa: E402
bytes_to_value,
bytes_to_bracket_str,
Expand All @@ -26,6 +31,11 @@
)


def ask_yes_no(prompt):
result = messagebox.askyesno("Question", prompt)
return result


class create_tool_tip(object):
"""
create a tooltip for a given widget
Expand All @@ -39,6 +49,7 @@ def __init__(self, widget, text=""):
self.text = text
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.config_xml_path = None

def enter(self, event=None):
if self.in_progress:
Expand Down Expand Up @@ -371,7 +382,6 @@ def __init__(self):
class application(tkinter.Frame):
def __init__(self, master=None):
root = master

self.debug = True
self.page_id = ""
self.page_list = {}
Expand Down Expand Up @@ -408,6 +418,12 @@ def __init__(self, master=None):
'Load Config from Change File',
]

self.variable_menu_string = [
'Load Runtime Variables from system',
'Save Runtime Variables to system',
'Delete Runtime Variables to system',
]

self.xml_specific_setting = [
'Save Config Changes to Binary'
]
Expand All @@ -417,10 +433,10 @@ def __init__(self, master=None):
paned = ttk.Panedwindow(root, orient=tkinter.HORIZONTAL)
paned.pack(fill=tkinter.BOTH, expand=True, padx=(4, 4))

status = tkinter.Label(
master, text="", bd=1, relief=tkinter.SUNKEN, anchor=tkinter.W
self.status = tkinter.Text(
master, height=8, bd=1, relief=tkinter.SUNKEN, wrap=tkinter.WORD
)
status.pack(side=tkinter.BOTTOM, fill=tkinter.X)
self.status.pack(side=tkinter.BOTTOM, fill=tkinter.X)

frame_left = ttk.Frame(paned, height=800, relief="groove")

Expand Down Expand Up @@ -475,6 +491,7 @@ def __init__(self, master=None):
file_menu.add_command(
label="Open Config file and Clear Old Config", command=self.load_from_ml_and_clear
)
file_menu.add_separator()
file_menu.add_command(
label=self.menu_string[0], command=self.save_to_bin, state="disabled"
)
Expand All @@ -484,6 +501,7 @@ def __init__(self, master=None):
file_menu.add_command(
label=self.menu_string[2], command=self.load_from_bin, state="disabled"
)
file_menu.add_separator()
file_menu.add_command(
label=self.menu_string[3], command=self.save_full_to_svd, state="disabled"
)
Expand All @@ -493,6 +511,7 @@ def __init__(self, master=None):
file_menu.add_command(
label=self.menu_string[5], command=self.load_from_svd, state="disabled"
)
file_menu.add_separator()
file_menu.add_command(
label=self.menu_string[6], command=self.save_full_to_delta, state="disabled"
)
Expand All @@ -502,12 +521,59 @@ def __init__(self, master=None):
file_menu.add_command(
label=self.menu_string[8], command=self.load_from_delta, state="disabled"
)

file_menu.add_separator()
file_menu.add_command(label="About", command=self.about)
menubar.add_cascade(label="File", menu=file_menu)
self.file_menu = file_menu

self.admin_mode = False
if os.name == 'nt' and ctypes.windll.shell32.IsUserAnAdmin():
self.admin_mode = True
elif os.name == 'posix' and os.getuid() == 0:
self.admin_mode = True

if self.admin_mode:
# Variable Menu
variable_menu = tkinter.Menu(menubar, tearoff=0)
variable_menu.add_command(
label=self.variable_menu_string[0], command=self.load_variable_runtime, state="disabled"
)
variable_menu.add_command(
label=self.variable_menu_string[1], command=self.set_variable_runtime, state="disabled"
)
variable_menu.add_command(
label=self.variable_menu_string[2], command=self.del_all_variable_runtime, state="disabled"
)
menubar.add_cascade(label="Variables", menu=variable_menu)
self.variable_menu = variable_menu

root.config(menu=menubar)

# Checking if we are in Manufacturing mode
bios_info_smbios_data = BoardMiscInfo.locate_smbios_entry(0)
# Check if we have the SMBIOS data in the first entry
bios_info_smbios_data = bios_info_smbios_data[0]
if (bios_info_smbios_data != []):
char_ext2_data = bios_info_smbios_data[0x13]
Manufacturing_enabled = (char_ext2_data & (0x1 << 6)) >> 6
print(f"Manufacturing : {Manufacturing_enabled:02X}")

# get mfci policy
mfci_policy_result = BoardMiscInfo.get_mfci_policy()
self.canvas = tkinter.Canvas(master, width=240, height=50, bg=master['bg'], highlightthickness=0)
self.canvas.place(relx=1.0, rely=1.0, x=0, y=0, anchor='se')
self.canvas.create_text(
120, 25,
text=(
f"AdminMode: {self.admin_mode}\n"
f"Manufacturing Mode: {Manufacturing_enabled}\n"
f"Mfci Policy: {mfci_policy_result}"
),
fill="black",
font=("Helvetica", 10, "bold")
)

idx = 0

if len(sys.argv) > 1:
Expand Down Expand Up @@ -784,6 +850,37 @@ def load_from_delta(self):
return
self.load_delta_file(path)

def set_variable_runtime(self):
self.update_config_data_on_page()
if (not ask_yes_no("Do you want to save the variable to the system?\n")):
return

runtime_var_delta_path = "RuntimeVarToWrite.vl"
bin = b''
for idx in self.cfg_data_list:
if self.cfg_data_list[idx].config_type == 'xml':
bin = self.cfg_data_list[idx].cfg_data_obj.generate_binary_array(True)

with open(runtime_var_delta_path, "wb") as fd:
fd.write(bin)

uefi_var_write.set_variable_from_file(runtime_var_delta_path)
self.load_variable_runtime()
self.output_current_status("Settings are set to system and save to RuntimeVar.vl")

def del_all_variable_runtime(self):
if (not ask_yes_no(f"Do you want to delete all variables in {self.config_xml_path} on system?\n")):
return

schema = Schema.load(self.config_xml_path)
for knob in schema.knobs:
self.output_current_status(f"Delete variable {knob.name} with namespace {knob.namespace}")
rc = uefi_var_write.delete_var_by_guid_name(knob.name, knob.namespace)
if rc == 0:
self.output_current_status(f"{knob.name} variable was not deleted from system {rc}")
else:
self.output_current_status(f"{knob.name} variable is deleted from system")

def load_delta_file(self, path):
# assumption is there may be multiple xml files
# so we can only load this delta file if the file name matches to this xml data
Expand Down Expand Up @@ -837,6 +934,8 @@ def load_bin_file(self, path):
messagebox.showerror("LOADING ERROR", str(e))
return

self.output_current_status(f"{path} file is loaded")

def load_cfg_file(self, path, file_id, clear_config):
# Clear out old config if requested
if clear_config is True:
Expand All @@ -863,6 +962,12 @@ def load_cfg_file(self, path, file_id, clear_config):
for menu in self.menu_string:
self.file_menu.entryconfig(menu, state="normal")

if self.admin_mode:
for menu in self.variable_menu_string:
self.variable_menu.entryconfig(menu, state="normal")

self.config_xml_path = path
self.output_current_status(f"{path} file is loaded")
return 0

def load_from_ml_and_clear(self):
Expand All @@ -885,6 +990,17 @@ def load_from_ml(self):

self.load_cfg_file(path, file_id, False)

def load_variable_runtime(self):
status = uefi_var_read.read_all_uefi_vars("RuntimeVar.vl", self.config_xml_path)
info_msg = "Settings are read from system and save to RuntimeVar.vl"
if status == -1:
info_msg = f"No Config Var is found, all the data from from {self.config_xml_path}"
messagebox.showinfo("WARNING", f"No Config Var is found, all the data from from {self.config_xml_path}")
else:
self.load_bin_file("RuntimeVar.vl")

self.output_current_status(info_msg)

def get_save_file_name(self, extension):
file_ext = extension.split(' ')
file_ext_opt = ['*.' + i for i in file_ext]
Expand Down Expand Up @@ -1090,6 +1206,7 @@ def set_config_item_value(self, item, value_str, file_id):
"Update %s from %s to %s !"
% (item["cname"], item["value"], new_value)
)
self.output_current_status("Update %s from %s to %s !" % (item["cname"], item["value"], new_value))
item["value"] = new_value

def get_config_data_item_from_widget(self, widget, label=False):
Expand Down Expand Up @@ -1282,6 +1399,10 @@ def update_config_data_on_page(self):
self.right_grid, self.update_config_data_from_widget
)

def output_current_status(self, output_log):
self.status.insert(tkinter.END, output_log + "\n")
self.status.see(tkinter.END)


if __name__ == "__main__":
root = tkinter.Tk()
Expand Down
Loading

0 comments on commit d4dcb4d

Please sign in to comment.