# @ ConfigEditor.py | |
# | |
# Copyright(c) 2018 - 2021, Intel Corporation. All rights reserved.<BR> | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
# | |
## | |
import os | |
import sys | |
import marshal | |
import tkinter | |
import tkinter.ttk as ttk | |
import tkinter.messagebox as messagebox | |
import tkinter.filedialog as filedialog | |
from pickle import FALSE, TRUE | |
from pathlib import Path | |
from GenYamlCfg import CGenYamlCfg, bytes_to_value, \ | |
bytes_to_bracket_str, value_to_bytes, array_str_to_value | |
from ctypes import sizeof, Structure, ARRAY, c_uint8, c_uint64, c_char, \ | |
c_uint32, c_uint16 | |
from functools import reduce | |
sys.path.insert(0, '..') | |
from FspDscBsf2Yaml import bsf_to_dsc, dsc_to_yaml # noqa | |
sys.dont_write_bytecode = True | |
class create_tool_tip(object): | |
''' | |
create a tooltip for a given widget | |
''' | |
in_progress = False | |
def __init__(self, widget, text=''): | |
self.top_win = None | |
self.widget = widget | |
self.text = text | |
self.widget.bind("<Enter>", self.enter) | |
self.widget.bind("<Leave>", self.leave) | |
def enter(self, event=None): | |
if self.in_progress: | |
return | |
if self.widget.winfo_class() == 'Treeview': | |
# Only show help when cursor is on row header. | |
rowid = self.widget.identify_row(event.y) | |
if rowid != '': | |
return | |
else: | |
x, y, cx, cy = self.widget.bbox("insert") | |
cursor = self.widget.winfo_pointerxy() | |
x = self.widget.winfo_rootx() + 35 | |
y = self.widget.winfo_rooty() + 20 | |
if cursor[1] > y and cursor[1] < y + 20: | |
y += 20 | |
# creates a toplevel window | |
self.top_win = tkinter.Toplevel(self.widget) | |
# Leaves only the label and removes the app window | |
self.top_win.wm_overrideredirect(True) | |
self.top_win.wm_geometry("+%d+%d" % (x, y)) | |
label = tkinter.Message(self.top_win, | |
text=self.text, | |
justify='left', | |
background='bisque', | |
relief='solid', | |
borderwidth=1, | |
font=("times", "10", "normal")) | |
label.pack(ipadx=1) | |
self.in_progress = True | |
def leave(self, event=None): | |
if self.top_win: | |
self.top_win.destroy() | |
self.in_progress = False | |
class validating_entry(tkinter.Entry): | |
def __init__(self, master, **kw): | |
tkinter.Entry.__init__(*(self, master), **kw) | |
self.parent = master | |
self.old_value = '' | |
self.last_value = '' | |
self.variable = tkinter.StringVar() | |
self.variable.trace("w", self.callback) | |
self.config(textvariable=self.variable) | |
self.config({"background": "#c0c0c0"}) | |
self.bind("<Return>", self.move_next) | |
self.bind("<Tab>", self.move_next) | |
self.bind("<Escape>", self.cancel) | |
for each in ['BackSpace', 'Delete']: | |
self.bind("<%s>" % each, self.ignore) | |
self.display(None) | |
def ignore(self, even): | |
return "break" | |
def move_next(self, event): | |
if self.row < 0: | |
return | |
row, col = self.row, self.col | |
txt, row_id, col_id = self.parent.get_next_cell(row, col) | |
self.display(txt, row_id, col_id) | |
return "break" | |
def cancel(self, event): | |
self.variable.set(self.old_value) | |
self.display(None) | |
def display(self, txt, row_id='', col_id=''): | |
if txt is None: | |
self.row = -1 | |
self.col = -1 | |
self.place_forget() | |
else: | |
row = int('0x' + row_id[1:], 0) - 1 | |
col = int(col_id[1:]) - 1 | |
self.row = row | |
self.col = col | |
self.old_value = txt | |
self.last_value = txt | |
x, y, width, height = self.parent.bbox(row_id, col) | |
self.place(x=x, y=y, w=width) | |
self.variable.set(txt) | |
self.focus_set() | |
self.icursor(0) | |
def callback(self, *Args): | |
cur_val = self.variable.get() | |
new_val = self.validate(cur_val) | |
if new_val is not None and self.row >= 0: | |
self.last_value = new_val | |
self.parent.set_cell(self.row, self.col, new_val) | |
self.variable.set(self.last_value) | |
def validate(self, value): | |
if len(value) > 0: | |
try: | |
int(value, 16) | |
except Exception: | |
return None | |
# Normalize the cell format | |
self.update() | |
cell_width = self.winfo_width() | |
max_len = custom_table.to_byte_length(cell_width) * 2 | |
cur_pos = self.index("insert") | |
if cur_pos == max_len + 1: | |
value = value[-max_len:] | |
else: | |
value = value[:max_len] | |
if value == '': | |
value = '0' | |
fmt = '%%0%dX' % max_len | |
return fmt % int(value, 16) | |
class custom_table(ttk.Treeview): | |
_Padding = 20 | |
_Char_width = 6 | |
def __init__(self, parent, col_hdr, bins): | |
cols = len(col_hdr) | |
col_byte_len = [] | |
for col in range(cols): # Columns | |
col_byte_len.append(int(col_hdr[col].split(':')[1])) | |
byte_len = sum(col_byte_len) | |
rows = (len(bins) + byte_len - 1) // byte_len | |
self.rows = rows | |
self.cols = cols | |
self.col_byte_len = col_byte_len | |
self.col_hdr = col_hdr | |
self.size = len(bins) | |
self.last_dir = '' | |
style = ttk.Style() | |
style.configure("Custom.Treeview.Heading", | |
font=('calibri', 10, 'bold'), | |
foreground="blue") | |
ttk.Treeview.__init__(self, parent, height=rows, | |
columns=[''] + col_hdr, show='headings', | |
style="Custom.Treeview", | |
selectmode='none') | |
self.bind("<Button-1>", self.click) | |
self.bind("<FocusOut>", self.focus_out) | |
self.entry = validating_entry(self, width=4, justify=tkinter.CENTER) | |
self.heading(0, text='LOAD') | |
self.column(0, width=60, stretch=0, anchor=tkinter.CENTER) | |
for col in range(cols): # Columns | |
text = col_hdr[col].split(':')[0] | |
byte_len = int(col_hdr[col].split(':')[1]) | |
self.heading(col+1, text=text) | |
self.column(col+1, width=self.to_cell_width(byte_len), | |
stretch=0, anchor=tkinter.CENTER) | |
idx = 0 | |
for row in range(rows): # Rows | |
text = '%04X' % (row * len(col_hdr)) | |
vals = ['%04X:' % (cols * row)] | |
for col in range(cols): # Columns | |
if idx >= len(bins): | |
break | |
byte_len = int(col_hdr[col].split(':')[1]) | |
value = bytes_to_value(bins[idx:idx+byte_len]) | |
hex = ("%%0%dX" % (byte_len * 2)) % value | |
vals.append(hex) | |
idx += byte_len | |
self.insert('', 'end', values=tuple(vals)) | |
if idx >= len(bins): | |
break | |
@staticmethod | |
def to_cell_width(byte_len): | |
return byte_len * 2 * custom_table._Char_width + custom_table._Padding | |
@staticmethod | |
def to_byte_length(cell_width): | |
return(cell_width - custom_table._Padding) \ | |
// (2 * custom_table._Char_width) | |
def focus_out(self, event): | |
self.entry.display(None) | |
def refresh_bin(self, bins): | |
if not bins: | |
return | |
# Reload binary into widget | |
bin_len = len(bins) | |
for row in range(self.rows): | |
iid = self.get_children()[row] | |
for col in range(self.cols): | |
idx = row * sum(self.col_byte_len) + \ | |
sum(self.col_byte_len[:col]) | |
byte_len = self.col_byte_len[col] | |
if idx + byte_len <= self.size: | |
byte_len = int(self.col_hdr[col].split(':')[1]) | |
if idx + byte_len > bin_len: | |
val = 0 | |
else: | |
val = bytes_to_value(bins[idx:idx+byte_len]) | |
hex_val = ("%%0%dX" % (byte_len * 2)) % val | |
self.set(iid, col + 1, hex_val) | |
def get_cell(self, row, col): | |
iid = self.get_children()[row] | |
txt = self.item(iid, 'values')[col] | |
return txt | |
def get_next_cell(self, row, col): | |
rows = self.get_children() | |
col += 1 | |
if col > self.cols: | |
col = 1 | |
row += 1 | |
cnt = row * sum(self.col_byte_len) + sum(self.col_byte_len[:col]) | |
if cnt > self.size: | |
# Reached the last cell, so roll back to beginning | |
row = 0 | |
col = 1 | |
txt = self.get_cell(row, col) | |
row_id = rows[row] | |
col_id = '#%d' % (col + 1) | |
return(txt, row_id, col_id) | |
def set_cell(self, row, col, val): | |
iid = self.get_children()[row] | |
self.set(iid, col, val) | |
def load_bin(self): | |
# Load binary from file | |
path = filedialog.askopenfilename( | |
initialdir=self.last_dir, | |
title="Load binary file", | |
filetypes=(("Binary files", "*.bin"), ( | |
"binary files", "*.bin"))) | |
if path: | |
self.last_dir = os.path.dirname(path) | |
fd = open(path, 'rb') | |
bins = bytearray(fd.read())[:self.size] | |
fd.close() | |
bins.extend(b'\x00' * (self.size - len(bins))) | |
return bins | |
return None | |
def click(self, event): | |
row_id = self.identify_row(event.y) | |
col_id = self.identify_column(event.x) | |
if row_id == '' and col_id == '#1': | |
# Clicked on "LOAD" cell | |
bins = self.load_bin() | |
self.refresh_bin(bins) | |
return | |
if col_id == '#1': | |
# Clicked on column 1(Offset column) | |
return | |
item = self.identify('item', event.x, event.y) | |
if not item or not col_id: | |
# Not clicked on valid cell | |
return | |
# Clicked cell | |
row = int('0x' + row_id[1:], 0) - 1 | |
col = int(col_id[1:]) - 1 | |
if row * self.cols + col > self.size: | |
return | |
vals = self.item(item, 'values') | |
if col < len(vals): | |
txt = self.item(item, 'values')[col] | |
self.entry.display(txt, row_id, col_id) | |
def get(self): | |
bins = bytearray() | |
row_ids = self.get_children() | |
for row_id in row_ids: | |
row = int('0x' + row_id[1:], 0) - 1 | |
for col in range(self.cols): | |
idx = row * sum(self.col_byte_len) + \ | |
sum(self.col_byte_len[:col]) | |
byte_len = self.col_byte_len[col] | |
if idx + byte_len > self.size: | |
break | |
hex = self.item(row_id, 'values')[col + 1] | |
values = value_to_bytes(int(hex, 16) | |
& ((1 << byte_len * 8) - 1), byte_len) | |
bins.extend(values) | |
return bins | |
class c_uint24(Structure): | |
"""Little-Endian 24-bit Unsigned Integer""" | |
_pack_ = 1 | |
_fields_ = [('Data', (c_uint8 * 3))] | |
def __init__(self, val=0): | |
self.set_value(val) | |
def __str__(self, indent=0): | |
return '0x%.6x' % self.value | |
def __int__(self): | |
return self.get_value() | |
def set_value(self, val): | |
self.Data[0:3] = Val2Bytes(val, 3) | |
def get_value(self): | |
return Bytes2Val(self.Data[0:3]) | |
value = property(get_value, set_value) | |
class EFI_FIRMWARE_VOLUME_HEADER(Structure): | |
_fields_ = [ | |
('ZeroVector', ARRAY(c_uint8, 16)), | |
('FileSystemGuid', ARRAY(c_uint8, 16)), | |
('FvLength', c_uint64), | |
('Signature', ARRAY(c_char, 4)), | |
('Attributes', c_uint32), | |
('HeaderLength', c_uint16), | |
('Checksum', c_uint16), | |
('ExtHeaderOffset', c_uint16), | |
('Reserved', c_uint8), | |
('Revision', c_uint8) | |
] | |
class EFI_FIRMWARE_VOLUME_EXT_HEADER(Structure): | |
_fields_ = [ | |
('FvName', ARRAY(c_uint8, 16)), | |
('ExtHeaderSize', c_uint32) | |
] | |
class EFI_FFS_INTEGRITY_CHECK(Structure): | |
_fields_ = [ | |
('Header', c_uint8), | |
('File', c_uint8) | |
] | |
class EFI_FFS_FILE_HEADER(Structure): | |
_fields_ = [ | |
('Name', ARRAY(c_uint8, 16)), | |
('IntegrityCheck', EFI_FFS_INTEGRITY_CHECK), | |
('Type', c_uint8), | |
('Attributes', c_uint8), | |
('Size', c_uint24), | |
('State', c_uint8) | |
] | |
class EFI_COMMON_SECTION_HEADER(Structure): | |
_fields_ = [ | |
('Size', c_uint24), | |
('Type', c_uint8) | |
] | |
class EFI_SECTION_TYPE: | |
"""Enumeration of all valid firmware file section types.""" | |
ALL = 0x00 | |
COMPRESSION = 0x01 | |
GUID_DEFINED = 0x02 | |
DISPOSABLE = 0x03 | |
PE32 = 0x10 | |
PIC = 0x11 | |
TE = 0x12 | |
DXE_DEPEX = 0x13 | |
VERSION = 0x14 | |
USER_INTERFACE = 0x15 | |
COMPATIBILITY16 = 0x16 | |
FIRMWARE_VOLUME_IMAGE = 0x17 | |
FREEFORM_SUBTYPE_GUID = 0x18 | |
RAW = 0x19 | |
PEI_DEPEX = 0x1b | |
SMM_DEPEX = 0x1c | |
class FSP_COMMON_HEADER(Structure): | |
_fields_ = [ | |
('Signature', ARRAY(c_char, 4)), | |
('HeaderLength', c_uint32) | |
] | |
class FSP_INFORMATION_HEADER(Structure): | |
_fields_ = [ | |
('Signature', ARRAY(c_char, 4)), | |
('HeaderLength', c_uint32), | |
('Reserved1', c_uint16), | |
('SpecVersion', c_uint8), | |
('HeaderRevision', c_uint8), | |
('ImageRevision', c_uint32), | |
('ImageId', ARRAY(c_char, 8)), | |
('ImageSize', c_uint32), | |
('ImageBase', c_uint32), | |
('ImageAttribute', c_uint16), | |
('ComponentAttribute', c_uint16), | |
('CfgRegionOffset', c_uint32), | |
('CfgRegionSize', c_uint32), | |
('Reserved2', c_uint32), | |
('TempRamInitEntryOffset', c_uint32), | |
('Reserved3', c_uint32), | |
('NotifyPhaseEntryOffset', c_uint32), | |
('FspMemoryInitEntryOffset', c_uint32), | |
('TempRamExitEntryOffset', c_uint32), | |
('FspSiliconInitEntryOffset', c_uint32), | |
('FspMultiPhaseSiInitEntryOffset', c_uint32), | |
('ExtendedImageRevision', c_uint16), | |
('Reserved4', c_uint16) | |
] | |
class FSP_EXTENDED_HEADER(Structure): | |
_fields_ = [ | |
('Signature', ARRAY(c_char, 4)), | |
('HeaderLength', c_uint32), | |
('Revision', c_uint8), | |
('Reserved', c_uint8), | |
('FspProducerId', ARRAY(c_char, 6)), | |
('FspProducerRevision', c_uint32), | |
('FspProducerDataSize', c_uint32) | |
] | |
class FSP_PATCH_TABLE(Structure): | |
_fields_ = [ | |
('Signature', ARRAY(c_char, 4)), | |
('HeaderLength', c_uint16), | |
('HeaderRevision', c_uint8), | |
('Reserved', c_uint8), | |
('PatchEntryNum', c_uint32) | |
] | |
class Section: | |
def __init__(self, offset, secdata): | |
self.SecHdr = EFI_COMMON_SECTION_HEADER.from_buffer(secdata, 0) | |
self.SecData = secdata[0:int(self.SecHdr.Size)] | |
self.Offset = offset | |
def AlignPtr(offset, alignment=8): | |
return (offset + alignment - 1) & ~(alignment - 1) | |
def Bytes2Val(bytes): | |
return reduce(lambda x, y: (x << 8) | y, bytes[:: -1]) | |
def Val2Bytes(value, blen): | |
return [(value >> (i*8) & 0xff) for i in range(blen)] | |
class FirmwareFile: | |
def __init__(self, offset, filedata): | |
self.FfsHdr = EFI_FFS_FILE_HEADER.from_buffer(filedata, 0) | |
self.FfsData = filedata[0:int(self.FfsHdr.Size)] | |
self.Offset = offset | |
self.SecList = [] | |
def ParseFfs(self): | |
ffssize = len(self.FfsData) | |
offset = sizeof(self.FfsHdr) | |
if self.FfsHdr.Name != '\xff' * 16: | |
while offset < (ffssize - sizeof(EFI_COMMON_SECTION_HEADER)): | |
sechdr = EFI_COMMON_SECTION_HEADER.from_buffer( | |
self.FfsData, offset) | |
sec = Section( | |
offset, self.FfsData[offset:offset + int(sechdr.Size)]) | |
self.SecList.append(sec) | |
offset += int(sechdr.Size) | |
offset = AlignPtr(offset, 4) | |
class FirmwareVolume: | |
def __init__(self, offset, fvdata): | |
self.FvHdr = EFI_FIRMWARE_VOLUME_HEADER.from_buffer(fvdata, 0) | |
self.FvData = fvdata[0: self.FvHdr.FvLength] | |
self.Offset = offset | |
if self.FvHdr.ExtHeaderOffset > 0: | |
self.FvExtHdr = EFI_FIRMWARE_VOLUME_EXT_HEADER.from_buffer( | |
self.FvData, self.FvHdr.ExtHeaderOffset) | |
else: | |
self.FvExtHdr = None | |
self.FfsList = [] | |
def ParseFv(self): | |
fvsize = len(self.FvData) | |
if self.FvExtHdr: | |
offset = self.FvHdr.ExtHeaderOffset + self.FvExtHdr.ExtHeaderSize | |
else: | |
offset = self.FvHdr.HeaderLength | |
offset = AlignPtr(offset) | |
while offset < (fvsize - sizeof(EFI_FFS_FILE_HEADER)): | |
ffshdr = EFI_FFS_FILE_HEADER.from_buffer(self.FvData, offset) | |
if (ffshdr.Name == '\xff' * 16) and \ | |
(int(ffshdr.Size) == 0xFFFFFF): | |
offset = fvsize | |
else: | |
ffs = FirmwareFile( | |
offset, self.FvData[offset:offset + int(ffshdr.Size)]) | |
ffs.ParseFfs() | |
self.FfsList.append(ffs) | |
offset += int(ffshdr.Size) | |
offset = AlignPtr(offset) | |
class FspImage: | |
def __init__(self, offset, fih, fihoff, patch): | |
self.Fih = fih | |
self.FihOffset = fihoff | |
self.Offset = offset | |
self.FvIdxList = [] | |
self.Type = "XTMSXXXXOXXXXXXX"[(fih.ComponentAttribute >> 12) & 0x0F] | |
self.PatchList = patch | |
self.PatchList.append(fihoff + 0x1C) | |
def AppendFv(self, FvIdx): | |
self.FvIdxList.append(FvIdx) | |
def Patch(self, delta, fdbin): | |
count = 0 | |
applied = 0 | |
for idx, patch in enumerate(self.PatchList): | |
ptype = (patch >> 24) & 0x0F | |
if ptype not in [0x00, 0x0F]: | |
raise Exception('ERROR: Invalid patch type %d !' % ptype) | |
if patch & 0x80000000: | |
patch = self.Fih.ImageSize - (0x1000000 - (patch & 0xFFFFFF)) | |
else: | |
patch = patch & 0xFFFFFF | |
if (patch < self.Fih.ImageSize) and \ | |
(patch + sizeof(c_uint32) <= self.Fih.ImageSize): | |
offset = patch + self.Offset | |
value = Bytes2Val(fdbin[offset:offset+sizeof(c_uint32)]) | |
value += delta | |
fdbin[offset:offset+sizeof(c_uint32)] = Val2Bytes( | |
value, sizeof(c_uint32)) | |
applied += 1 | |
count += 1 | |
# Don't count the FSP base address patch entry appended at the end | |
if count != 0: | |
count -= 1 | |
applied -= 1 | |
return (count, applied) | |
class FirmwareDevice: | |
def __init__(self, offset, FdData): | |
self.FvList = [] | |
self.FspList = [] | |
self.FspExtList = [] | |
self.FihList = [] | |
self.BuildList = [] | |
self.OutputText = "" | |
self.Offset = 0 | |
self.FdData = FdData | |
def ParseFd(self): | |
offset = 0 | |
fdsize = len(self.FdData) | |
self.FvList = [] | |
while offset < (fdsize - sizeof(EFI_FIRMWARE_VOLUME_HEADER)): | |
fvh = EFI_FIRMWARE_VOLUME_HEADER.from_buffer(self.FdData, offset) | |
if b'_FVH' != fvh.Signature: | |
raise Exception("ERROR: Invalid FV header !") | |
fv = FirmwareVolume( | |
offset, self.FdData[offset:offset + fvh.FvLength]) | |
fv.ParseFv() | |
self.FvList.append(fv) | |
offset += fv.FvHdr.FvLength | |
def CheckFsp(self): | |
if len(self.FspList) == 0: | |
return | |
fih = None | |
for fsp in self.FspList: | |
if not fih: | |
fih = fsp.Fih | |
else: | |
newfih = fsp.Fih | |
if (newfih.ImageId != fih.ImageId) or \ | |
(newfih.ImageRevision != fih.ImageRevision): | |
raise Exception( | |
"ERROR: Inconsistent FSP ImageId or " | |
"ImageRevision detected !") | |
def ParseFsp(self): | |
flen = 0 | |
for idx, fv in enumerate(self.FvList): | |
# Check if this FV contains FSP header | |
if flen == 0: | |
if len(fv.FfsList) == 0: | |
continue | |
ffs = fv.FfsList[0] | |
if len(ffs.SecList) == 0: | |
continue | |
sec = ffs.SecList[0] | |
if sec.SecHdr.Type != EFI_SECTION_TYPE.RAW: | |
continue | |
fihoffset = ffs.Offset + sec.Offset + sizeof(sec.SecHdr) | |
fspoffset = fv.Offset | |
offset = fspoffset + fihoffset | |
fih = FSP_INFORMATION_HEADER.from_buffer(self.FdData, offset) | |
self.FihList.append(fih) | |
if b'FSPH' != fih.Signature: | |
continue | |
offset += fih.HeaderLength | |
offset = AlignPtr(offset, 2) | |
Extfih = FSP_EXTENDED_HEADER.from_buffer(self.FdData, offset) | |
self.FspExtList.append(Extfih) | |
offset = AlignPtr(offset, 4) | |
plist = [] | |
while True: | |
fch = FSP_COMMON_HEADER.from_buffer(self.FdData, offset) | |
if b'FSPP' != fch.Signature: | |
offset += fch.HeaderLength | |
offset = AlignPtr(offset, 4) | |
else: | |
fspp = FSP_PATCH_TABLE.from_buffer( | |
self.FdData, offset) | |
offset += sizeof(fspp) | |
start_offset = offset + 32 | |
end_offset = offset + 32 | |
while True: | |
end_offset += 1 | |
if(self.FdData[ | |
end_offset: end_offset + 1] == b'\xff'): | |
break | |
self.BuildList.append( | |
self.FdData[start_offset:end_offset]) | |
pdata = (c_uint32 * fspp.PatchEntryNum).from_buffer( | |
self.FdData, offset) | |
plist = list(pdata) | |
break | |
fsp = FspImage(fspoffset, fih, fihoffset, plist) | |
fsp.AppendFv(idx) | |
self.FspList.append(fsp) | |
flen = fsp.Fih.ImageSize - fv.FvHdr.FvLength | |
else: | |
fsp.AppendFv(idx) | |
flen -= fv.FvHdr.FvLength | |
if flen < 0: | |
raise Exception("ERROR: Incorrect FV size in image !") | |
self.CheckFsp() | |
def IsIntegerType(self, val): | |
if sys.version_info[0] < 3: | |
if type(val) in (int, long): | |
return True | |
else: | |
if type(val) is int: | |
return True | |
return False | |
def ConvertRevisionString(self, obj): | |
for field in obj._fields_: | |
key = field[0] | |
val = getattr(obj, key) | |
rep = '' | |
if self.IsIntegerType(val): | |
if (key == 'ImageRevision'): | |
FspImageRevisionMajor = ((val >> 24) & 0xFF) | |
FspImageRevisionMinor = ((val >> 16) & 0xFF) | |
FspImageRevisionRevision = ((val >> 8) & 0xFF) | |
FspImageRevisionBuildNumber = (val & 0xFF) | |
rep = '0x%08X' % val | |
elif (key == 'ExtendedImageRevision'): | |
FspImageRevisionRevision |= (val & 0xFF00) | |
FspImageRevisionBuildNumber |= ((val << 8) & 0xFF00) | |
rep = "0x%04X ('%02X.%02X.%04X.%04X')" % (val, FspImageRevisionMajor, FspImageRevisionMinor, FspImageRevisionRevision, FspImageRevisionBuildNumber) | |
return rep | |
def OutputFsp(self): | |
def copy_text_to_clipboard(): | |
window.clipboard_clear() | |
window.clipboard_append(self.OutputText) | |
window = tkinter.Tk() | |
window.title("Fsp Headers") | |
window.resizable(0, 0) | |
# Window Size | |
window.geometry("300x400+350+150") | |
frame = tkinter.Frame(window) | |
frame.pack(side=tkinter.BOTTOM) | |
# Vertical (y) Scroll Bar | |
scroll = tkinter.Scrollbar(window) | |
scroll.pack(side=tkinter.RIGHT, fill=tkinter.Y) | |
text = tkinter.Text(window, | |
wrap=tkinter.NONE, yscrollcommand=scroll.set) | |
i = 0 | |
self.OutputText = self.OutputText + "Fsp Header Details \n\n" | |
while i < len(self.FihList): | |
try: | |
# self.OutputText += str(self.BuildList[i].decode()) + "\n" | |
self.OutputText += str(self.BuildList[i]) + "\n" | |
except Exception: | |
self.OutputText += "No description found\n" | |
self.OutputText += "FSP Header :\n " | |
self.OutputText += "Signature : " + \ | |
str(self.FihList[i].Signature.decode('utf-8')) + "\n " | |
self.OutputText += "Header Length : " + \ | |
str(hex(self.FihList[i].HeaderLength)) + "\n " | |
self.OutputText += "Reserved1 : " + \ | |
str(hex(self.FihList[i].Reserved1)) + "\n " | |
self.OutputText += "Header Revision : " + \ | |
str(hex(self.FihList[i].HeaderRevision)) + "\n " | |
self.OutputText += "Spec Version : " + \ | |
str(hex(self.FihList[i].SpecVersion)) + "\n " | |
self.OutputText += "Image Revision : " + \ | |
str(hex(self.FihList[i].ImageRevision)) + "\n " | |
self.OutputText += "Image Id : " + \ | |
str(self.FihList[i].ImageId.decode('utf-8')) + "\n " | |
self.OutputText += "Image Size : " + \ | |
str(hex(self.FihList[i].ImageSize)) + "\n " | |
self.OutputText += "Image Base : " + \ | |
str(hex(self.FihList[i].ImageBase)) + "\n " | |
self.OutputText += "Image Attribute : " + \ | |
str(hex(self.FihList[i].ImageAttribute)) + "\n " | |
self.OutputText += "Component Attribute : " + \ | |
str(hex(self.FihList[i].ComponentAttribute)) + "\n " | |
self.OutputText += "Cfg Region Offset : " + \ | |
str(hex(self.FihList[i].CfgRegionOffset)) + "\n " | |
self.OutputText += "Cfg Region Size : " + \ | |
str(hex(self.FihList[i].CfgRegionSize)) + "\n " | |
self.OutputText += "Reserved2 : " + \ | |
str(hex(self.FihList[i].Reserved2)) + "\n " | |
self.OutputText += "Temp Ram Init Entry : " + \ | |
str(hex(self.FihList[i].TempRamInitEntryOffset)) + "\n " | |
self.OutputText += "Reserved3 : " + \ | |
str(hex(self.FihList[i].Reserved3)) + "\n " | |
self.OutputText += "Notify Phase Entry : " + \ | |
str(hex(self.FihList[i].NotifyPhaseEntryOffset)) + "\n " | |
self.OutputText += "Fsp Memory Init Entry : " + \ | |
str(hex(self.FihList[i].FspMemoryInitEntryOffset)) + "\n " | |
self.OutputText += "Temp Ram Exit Entry : " + \ | |
str(hex(self.FihList[i].TempRamExitEntryOffset)) + "\n " | |
self.OutputText += "Fsp Silicon Init Entry : " + \ | |
str(hex(self.FihList[i].FspSiliconInitEntryOffset)) + "\n " | |
self.OutputText += "Fsp Multi Phase Si Init Entry : " + \ | |
str(hex(self.FihList[i].FspMultiPhaseSiInitEntryOffset)) + "\n " | |
# display ExtendedImageRevision & Reserved4 if HeaderRevision >= 6 | |
for fsp in self.FihList: | |
if fsp.HeaderRevision >= 6: | |
Display_ExtndImgRev = TRUE | |
else: | |
Display_ExtndImgRev = FALSE | |
self.OutputText += "\n" | |
if Display_ExtndImgRev == TRUE: | |
self.OutputText += "ExtendedImageRevision : " + \ | |
str(self.ConvertRevisionString(self.FihList[i])) + "\n " | |
self.OutputText += "Reserved4 : " + \ | |
str(hex(self.FihList[i].Reserved4)) + "\n\n" | |
self.OutputText += "FSP Extended Header:\n " | |
self.OutputText += "Signature : " + \ | |
str(self.FspExtList[i].Signature.decode('utf-8')) + "\n " | |
self.OutputText += "Header Length : " + \ | |
str(hex(self.FspExtList[i].HeaderLength)) + "\n " | |
self.OutputText += "Header Revision : " + \ | |
str(hex(self.FspExtList[i].Revision)) + "\n " | |
self.OutputText += "Fsp Producer Id : " + \ | |
str(self.FspExtList[i].FspProducerId.decode('utf-8')) + "\n " | |
self.OutputText += "FspProducerRevision : " + \ | |
str(hex(self.FspExtList[i].FspProducerRevision)) + "\n\n" | |
i += 1 | |
text.insert(tkinter.INSERT, self.OutputText) | |
text.pack() | |
# Configure the scrollbars | |
scroll.config(command=text.yview) | |
copy_button = tkinter.Button( | |
window, text="Copy to Clipboard", command=copy_text_to_clipboard) | |
copy_button.pack(in_=frame, side=tkinter.LEFT, padx=20, pady=10) | |
exit_button = tkinter.Button( | |
window, text="Close", command=window.destroy) | |
exit_button.pack(in_=frame, side=tkinter.RIGHT, padx=20, pady=10) | |
window.mainloop() | |
class state: | |
def __init__(self): | |
self.state = False | |
def set(self, value): | |
self.state = value | |
def get(self): | |
return self.state | |
class application(tkinter.Frame): | |
def __init__(self, master=None): | |
root = master | |
self.debug = True | |
self.mode = 'FSP' | |
self.last_dir = '.' | |
self.page_id = '' | |
self.page_list = {} | |
self.conf_list = {} | |
self.cfg_page_dict = {} | |
self.cfg_data_obj = None | |
self.org_cfg_data_bin = None | |
self.in_left = state() | |
self.in_right = state() | |
self.search_text = '' | |
# Check if current directory contains a file with a .yaml extension | |
# if not default self.last_dir to a Platform directory where it is | |
# easier to locate *BoardPkg\CfgData\*Def.yaml files | |
self.last_dir = '.' | |
if not any(fname.endswith('.yaml') for fname in os.listdir('.')): | |
platform_path = Path(os.path.realpath(__file__)).parents[2].\ | |
joinpath('Platform') | |
if platform_path.exists(): | |
self.last_dir = platform_path | |
tkinter.Frame.__init__(self, master, borderwidth=2) | |
self.menu_string = [ | |
'Save Config Data to Binary', 'Load Config Data from Binary', | |
'Show Binary Information', | |
'Load Config Changes from Delta File', | |
'Save Config Changes to Delta File', | |
'Save Full Config Data to Delta File', | |
'Open Config BSF file' | |
] | |
root.geometry("1200x800") | |
# Search string | |
fram = tkinter.Frame(root) | |
# adding label to search box | |
tkinter.Label(fram, text='Text to find:').pack(side=tkinter.LEFT) | |
# adding of single line text box | |
self.edit = tkinter.Entry(fram, width=30) | |
# positioning of text box | |
self.edit.pack( | |
side=tkinter.LEFT, fill=tkinter.BOTH, expand=1, padx=(4, 4)) | |
# setting focus | |
self.edit.focus_set() | |
# adding of search button | |
butt = tkinter.Button(fram, text='Search', relief=tkinter.GROOVE, | |
command=self.search_bar) | |
butt.pack(side=tkinter.RIGHT, padx=(4, 4)) | |
fram.pack(side=tkinter.TOP, anchor=tkinter.SE) | |
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) | |
status.pack(side=tkinter.BOTTOM, fill=tkinter.X) | |
frame_left = ttk.Frame(paned, height=800, relief="groove") | |
self.left = ttk.Treeview(frame_left, show="tree") | |
# Set up tree HScroller | |
pady = (10, 10) | |
self.tree_scroll = ttk.Scrollbar(frame_left, | |
orient="vertical", | |
command=self.left.yview) | |
self.left.configure(yscrollcommand=self.tree_scroll.set) | |
self.left.bind("<<TreeviewSelect>>", self.on_config_page_select_change) | |
self.left.bind("<Enter>", lambda e: self.in_left.set(True)) | |
self.left.bind("<Leave>", lambda e: self.in_left.set(False)) | |
self.left.bind("<MouseWheel>", self.on_tree_scroll) | |
self.left.pack(side='left', | |
fill=tkinter.BOTH, | |
expand=True, | |
padx=(5, 0), | |
pady=pady) | |
self.tree_scroll.pack(side='right', fill=tkinter.Y, | |
pady=pady, padx=(0, 5)) | |
frame_right = ttk.Frame(paned, relief="groove") | |
self.frame_right = frame_right | |
self.conf_canvas = tkinter.Canvas(frame_right, highlightthickness=0) | |
self.page_scroll = ttk.Scrollbar(frame_right, | |
orient="vertical", | |
command=self.conf_canvas.yview) | |
self.right_grid = ttk.Frame(self.conf_canvas) | |
self.conf_canvas.configure(yscrollcommand=self.page_scroll.set) | |
self.conf_canvas.pack(side='left', | |
fill=tkinter.BOTH, | |
expand=True, | |
pady=pady, | |
padx=(5, 0)) | |
self.page_scroll.pack(side='right', fill=tkinter.Y, | |
pady=pady, padx=(0, 5)) | |
self.conf_canvas.create_window(0, 0, window=self.right_grid, | |
anchor='nw') | |
self.conf_canvas.bind('<Enter>', lambda e: self.in_right.set(True)) | |
self.conf_canvas.bind('<Leave>', lambda e: self.in_right.set(False)) | |
self.conf_canvas.bind("<Configure>", self.on_canvas_configure) | |
self.conf_canvas.bind_all("<MouseWheel>", self.on_page_scroll) | |
paned.add(frame_left, weight=2) | |
paned.add(frame_right, weight=10) | |
style = ttk.Style() | |
style.layout("Treeview", [('Treeview.treearea', {'sticky': 'nswe'})]) | |
menubar = tkinter.Menu(root) | |
file_menu = tkinter.Menu(menubar, tearoff=0) | |
file_menu.add_command(label="Open Config YAML file", | |
command=self.load_from_yaml) | |
file_menu.add_command(label=self.menu_string[6], | |
command=self.load_from_bsf_file) | |
file_menu.add_command(label=self.menu_string[2], | |
command=self.load_from_fd) | |
file_menu.add_command(label=self.menu_string[0], | |
command=self.save_to_bin, | |
state='disabled') | |
file_menu.add_command(label=self.menu_string[1], | |
command=self.load_from_bin, | |
state='disabled') | |
file_menu.add_command(label=self.menu_string[3], | |
command=self.load_from_delta, | |
state='disabled') | |
file_menu.add_command(label=self.menu_string[4], | |
command=self.save_to_delta, | |
state='disabled') | |
file_menu.add_command(label=self.menu_string[5], | |
command=self.save_full_to_delta, | |
state='disabled') | |
file_menu.add_command(label="About", command=self.about) | |
menubar.add_cascade(label="File", menu=file_menu) | |
self.file_menu = file_menu | |
root.config(menu=menubar) | |
if len(sys.argv) > 1: | |
path = sys.argv[1] | |
if not path.endswith('.yaml') and not path.endswith('.pkl'): | |
messagebox.showerror('LOADING ERROR', | |
"Unsupported file '%s' !" % path) | |
return | |
else: | |
self.load_cfg_file(path) | |
if len(sys.argv) > 2: | |
path = sys.argv[2] | |
if path.endswith('.dlt'): | |
self.load_delta_file(path) | |
elif path.endswith('.bin'): | |
self.load_bin_file(path) | |
else: | |
messagebox.showerror('LOADING ERROR', | |
"Unsupported file '%s' !" % path) | |
return | |
# VFR Format Page modification | |
def page_construct(self): | |
self.left.bind("<<TreeviewSelect>>", self.on_config_page_select_change) | |
def search_bar(self): | |
# get data from text box | |
self.search_text = self.edit.get() | |
# Clear the page and update it according to search value | |
self.refresh_config_data_page() | |
def set_object_name(self, widget, name): | |
self.conf_list[id(widget)] = name | |
def get_object_name(self, widget): | |
if id(widget) in self.conf_list: | |
return self.conf_list[id(widget)] | |
else: | |
return None | |
def limit_entry_size(self, variable, limit): | |
value = variable.get() | |
if len(value) > limit: | |
variable.set(value[:limit]) | |
def on_canvas_configure(self, event): | |
self.right_grid.grid_columnconfigure(0, minsize=event.width) | |
def on_tree_scroll(self, event): | |
if not self.in_left.get() and self.in_right.get(): | |
# This prevents scroll event from being handled by both left and | |
# right frame at the same time. | |
self.on_page_scroll(event) | |
return 'break' | |
def on_page_scroll(self, event): | |
if self.in_right.get(): | |
# Only scroll when it is in active area | |
min, max = self.page_scroll.get() | |
if not((min == 0.0) and (max == 1.0)): | |
self.conf_canvas.yview_scroll(-1 * int(event.delta / 120), | |
'units') | |
def update_visibility_for_widget(self, widget, args): | |
visible = True | |
item = self.get_config_data_item_from_widget(widget, True) | |
if item is None: | |
return visible | |
elif not item: | |
return visible | |
if self.cfg_data_obj.binseg_dict: | |
str_split = item['path'].split('.') | |
if str_split[-2] not in CGenYamlCfg.available_fv and \ | |
str_split[-2] not in CGenYamlCfg.missing_fv: | |
if self.cfg_data_obj.binseg_dict[str_split[-3]] == -1: | |
visible = False | |
widget.grid_remove() | |
return visible | |
else: | |
if self.cfg_data_obj.binseg_dict[str_split[-2]] == -1: | |
visible = False | |
widget.grid_remove() | |
return visible | |
result = 1 | |
if item['condition']: | |
result = self.evaluate_condition(item) | |
if result == 2: | |
# Gray | |
widget.configure(state='disabled') | |
elif result == 0: | |
# Hide | |
visible = False | |
widget.grid_remove() | |
else: | |
# Show | |
widget.grid() | |
widget.configure(state='normal') | |
if visible and self.search_text != '': | |
name = item['name'] | |
if name.lower().find(self.search_text.lower()) == -1: | |
visible = False | |
widget.grid_remove() | |
return visible | |
def update_widgets_visibility_on_page(self): | |
self.walk_widgets_in_layout(self.right_grid, | |
self.update_visibility_for_widget) | |
def combo_select_changed(self, event): | |
self.update_config_data_from_widget(event.widget, None) | |
self.update_widgets_visibility_on_page() | |
def edit_num_finished(self, event): | |
widget = event.widget | |
item = self.get_config_data_item_from_widget(widget) | |
if not item: | |
return | |
parts = item['type'].split(',') | |
if len(parts) > 3: | |
min = parts[2].lstrip()[1:] | |
max = parts[3].rstrip()[:-1] | |
min_val = array_str_to_value(min) | |
max_val = array_str_to_value(max) | |
text = widget.get() | |
if ',' in text: | |
text = '{ %s }' % text | |
try: | |
value = array_str_to_value(text) | |
if value < min_val or value > max_val: | |
raise Exception('Invalid input!') | |
self.set_config_item_value(item, text) | |
except Exception: | |
pass | |
text = item['value'].strip('{').strip('}').strip() | |
widget.delete(0, tkinter.END) | |
widget.insert(0, text) | |
self.update_widgets_visibility_on_page() | |
def update_page_scroll_bar(self): | |
# Update scrollbar | |
self.frame_right.update() | |
self.conf_canvas.config(scrollregion=self.conf_canvas.bbox("all")) | |
def on_config_page_select_change(self, event): | |
self.update_config_data_on_page() | |
sel = self.left.selection() | |
if len(sel) > 0: | |
page_id = sel[0] | |
self.build_config_data_page(page_id) | |
self.update_widgets_visibility_on_page() | |
self.update_page_scroll_bar() | |
def walk_widgets_in_layout(self, parent, callback_function, args=None): | |
for widget in parent.winfo_children(): | |
callback_function(widget, args) | |
def clear_widgets_inLayout(self, parent=None): | |
if parent is None: | |
parent = self.right_grid | |
for widget in parent.winfo_children(): | |
widget.destroy() | |
parent.grid_forget() | |
self.conf_list.clear() | |
def build_config_page_tree(self, cfg_page, parent): | |
for page in cfg_page['child']: | |
page_id = next(iter(page)) | |
# Put CFG items into related page list | |
self.page_list[page_id] = self.cfg_data_obj.get_cfg_list(page_id) | |
if self.mode == 'fsp': | |
self.page_list[page_id].sort(key=lambda x: x['order']) | |
page_name = self.cfg_data_obj.get_page_title(page_id) | |
child = self.left.insert( | |
parent, 'end', | |
iid=page_id, text=page_name, | |
value=0) | |
if len(page[page_id]) > 0: | |
self.build_config_page_tree(page[page_id], child) | |
def is_config_data_loaded(self): | |
return True if len(self.page_list) else False | |
def set_current_config_page(self, page_id): | |
self.page_id = page_id | |
def get_current_config_page(self): | |
return self.page_id | |
def get_current_config_data(self): | |
page_id = self.get_current_config_page() | |
if page_id in self.page_list: | |
return self.page_list[page_id] | |
else: | |
return [] | |
invalid_values = {} | |
def build_config_data_page(self, page_id): | |
self.clear_widgets_inLayout() | |
self.set_current_config_page(page_id) | |
disp_list = [] | |
for item in self.get_current_config_data(): | |
disp_list.append(item) | |
row = 0 | |
if self.mode == 'fsp': | |
disp_list.sort(key=lambda x: x['order']) | |
for item in disp_list: | |
self.add_config_item(item, row) | |
row += 2 | |
if self.invalid_values: | |
string = 'The following contails invalid options/values \n\n' | |
for i in self.invalid_values: | |
string += i + ": " + str(self.invalid_values[i]) + "\n" | |
reply = messagebox.showwarning('Warning!', string) | |
if reply == 'ok': | |
self.invalid_values.clear() | |
elif self.mode == 'vfr': | |
for item in disp_list: | |
self.add_vfr_config_item(item, row) | |
row += 2 | |
fsp_version = '' | |
def load_config_data(self, file_name): | |
gen_cfg_data = CGenYamlCfg() | |
if file_name.endswith('.pkl'): | |
with open(file_name, "rb") as pkl_file: | |
gen_cfg_data.__dict__ = marshal.load(pkl_file) | |
gen_cfg_data.prepare_marshal(False) | |
elif file_name.endswith('.yaml') or file_name.endswith('.yml'): | |
if gen_cfg_data.load_yaml(file_name) != 0: | |
raise Exception(gen_cfg_data.get_last_error()) | |
else: | |
raise Exception('Unsupported file "%s" !' % file_name) | |
self.mode = gen_cfg_data.yaml_type | |
# checking fsp version | |
if gen_cfg_data.yaml_type == 'fsp': | |
if gen_cfg_data.detect_fsp(): | |
self.fsp_version = '2.X' | |
else: | |
self.fsp_version = '1.X' | |
return gen_cfg_data | |
def about(self): | |
msg = 'Configuration Editor\n--------------------------------\n \ | |
Version 0.8\n2021' | |
lines = msg.split('\n') | |
width = 30 | |
text = [] | |
for line in lines: | |
text.append(line.center(width, ' ')) | |
messagebox.showinfo('Config Editor', '\n'.join(text)) | |
def update_last_dir(self, path): | |
self.last_dir = os.path.dirname(path) | |
def get_open_file_name(self, ftype): | |
if self.is_config_data_loaded(): | |
if ftype == 'dlt': | |
question = '' | |
elif ftype == 'bin': | |
question = 'All configuration will be reloaded from BIN file, \ | |
continue ?' | |
elif ftype == 'yaml' or ftype == 'yml': | |
question = '' | |
elif ftype == 'bsf': | |
question = '' | |
else: | |
raise Exception('Unsupported file type !') | |
if question: | |
reply = messagebox.askquestion('', question, icon='warning') | |
if reply == 'no': | |
return None | |
if ftype == 'yaml' or ftype == 'yml': | |
if self.mode == 'fsp': | |
file_type = 'YAML' | |
file_ext = 'yaml' | |
else: | |
file_type = 'YAML or PKL' | |
file_ext = 'pkl *.yaml *.yml' | |
else: | |
file_type = ftype.upper() | |
file_ext = ftype | |
path = filedialog.askopenfilename( | |
initialdir=self.last_dir, | |
title="Load file", | |
filetypes=(("%s files" % file_type, "*.%s" % file_ext), ( | |
"all files", "*.*"))) | |
if path: | |
self.update_last_dir(path) | |
return path | |
else: | |
return None | |
def load_from_delta(self): | |
path = self.get_open_file_name('dlt') | |
if not path: | |
return | |
self.load_delta_file(path) | |
def load_delta_file(self, path): | |
self.reload_config_data_from_bin(self.org_cfg_data_bin) | |
try: | |
self.cfg_data_obj.override_default_value(path) | |
except Exception as e: | |
messagebox.showerror('LOADING ERROR', str(e)) | |
return | |
self.update_last_dir(path) | |
self.refresh_config_data_page() | |
def load_from_bin(self): | |
path = filedialog.askopenfilename( | |
initialdir=self.last_dir, | |
title="Load file", | |
filetypes={("Binaries", "*.fv *.fd *.bin *.rom")}) | |
if not path: | |
return | |
self.load_bin_file(path) | |
def load_bin_file(self, path): | |
with open(path, 'rb') as fd: | |
bin_data = bytearray(fd.read()) | |
if len(bin_data) < len(self.org_cfg_data_bin): | |
messagebox.showerror('Binary file size is smaller than what \ | |
YAML requires !') | |
return | |
try: | |
self.reload_config_data_from_bin(bin_data) | |
except Exception as e: | |
messagebox.showerror('LOADING ERROR', str(e)) | |
return | |
def load_from_bsf_file(self): | |
path = self.get_open_file_name('bsf') | |
if not path: | |
return | |
self.load_bsf_file(path) | |
def load_bsf_file(self, path): | |
bsf_file = path | |
dsc_file = os.path.splitext(bsf_file)[0] + '.dsc' | |
yaml_file = os.path.splitext(bsf_file)[0] + '.yaml' | |
bsf_to_dsc(bsf_file, dsc_file) | |
dsc_to_yaml(dsc_file, yaml_file) | |
self.load_cfg_file(yaml_file) | |
return | |
def load_from_fd(self): | |
path = filedialog.askopenfilename( | |
initialdir=self.last_dir, | |
title="Load file", | |
filetypes={("Binaries", "*.fv *.fd *.bin *.rom")}) | |
if not path: | |
return | |
self.load_fd_file(path) | |
def load_fd_file(self, path): | |
with open(path, 'rb') as fd: | |
bin_data = bytearray(fd.read()) | |
fd = FirmwareDevice(0, bin_data) | |
fd.ParseFd() | |
fd.ParseFsp() | |
fd.OutputFsp() | |
def load_cfg_file(self, path): | |
# Save current values in widget and clear database | |
self.clear_widgets_inLayout() | |
self.left.delete(*self.left.get_children()) | |
self.cfg_data_obj = self.load_config_data(path) | |
self.build_config_page_tree(self.cfg_data_obj.get_cfg_page()['root'], | |
'') | |
self.update_last_dir(path) | |
if self.cfg_data_obj.yaml_type == 'fsp': | |
self.org_cfg_data_bin = self.cfg_data_obj.generate_binary_array() | |
msg_string = 'Click YES if it is FULL FSP '\ | |
+ self.fsp_version + ' Binary' | |
reply = messagebox.askquestion('Form', msg_string) | |
if reply == 'yes': | |
self.load_from_bin() | |
for menu in self.menu_string: | |
self.file_menu.entryconfig(menu, state="normal") | |
return 0 | |
def load_from_yaml(self): | |
path = self.get_open_file_name('yaml') | |
if not path: | |
return | |
self.load_cfg_file(path) | |
def get_save_file_name(self, extension): | |
path = filedialog.asksaveasfilename( | |
initialdir=self.last_dir, | |
title="Save file", | |
defaultextension=extension) | |
if path: | |
self.last_dir = os.path.dirname(path) | |
return path | |
else: | |
return None | |
def save_delta_file(self, full=False): | |
path = self.get_save_file_name(".dlt") | |
if not path: | |
return | |
self.update_config_data_on_page() | |
if self.mode == "fsp": | |
new_data = self.cfg_data_obj.generate_binary_array() | |
self.cfg_data_obj.generate_delta_file_from_bin(path, | |
self.org_cfg_data_bin, | |
new_data, full) | |
def save_to_delta(self): | |
self.save_delta_file() | |
def save_full_to_delta(self): | |
self.save_delta_file(True) | |
def save_to_bin(self): | |
path = self.get_save_file_name(".bin") | |
if not path: | |
return | |
self.update_config_data_on_page() | |
bins = self.cfg_data_obj.save_current_to_bin() | |
with open(path, 'wb') as fd: | |
fd.write(bins) | |
def refresh_config_data_page(self): | |
self.clear_widgets_inLayout() | |
self.on_config_page_select_change(None) | |
def set_config_data_page(self): | |
page_id_list = [] | |
for idx, page in enumerate( | |
self.cfg_data_obj._cfg_page['root']['child']): | |
page_id_list.append(list(page.keys())[0]) | |
page_list = self.cfg_data_obj.get_cfg_list(page_id_list[idx]) | |
self.cfg_page_dict[page_id_list[idx]] = 0 | |
for item in page_list: | |
str_split = item['path'].split('.') | |
if str_split[-2] not in CGenYamlCfg.available_fv and \ | |
str_split[-2] not in CGenYamlCfg.missing_fv: | |
if self.cfg_data_obj.binseg_dict[str_split[-3]] != -1: | |
self.cfg_page_dict[page_id_list[idx]] += 1 | |
else: | |
if self.cfg_data_obj.binseg_dict[str_split[-2]] != -1: | |
self.cfg_page_dict[page_id_list[idx]] += 1 | |
removed_page = 0 | |
for idx, id in enumerate(page_id_list): | |
if self.cfg_page_dict[id] == 0: | |
del self.cfg_data_obj._cfg_page['root']['child'][idx-removed_page] # noqa: E501 | |
removed_page += 1 | |
def reload_config_data_from_bin(self, bin_dat): | |
self.cfg_data_obj.load_default_from_bin(bin_dat) | |
self.set_config_data_page() | |
self.left.delete(*self.left.get_children()) | |
self.build_config_page_tree(self.cfg_data_obj.get_cfg_page()['root'], | |
'') | |
self.refresh_config_data_page() | |
def set_config_item_value(self, item, value_str): | |
itype = item['type'].split(',')[0] | |
if itype == "Table": | |
new_value = value_str | |
elif itype == "EditText": | |
length = (self.cfg_data_obj.get_cfg_item_length(item) + 7) // 8 | |
new_value = value_str[:length] | |
if item['value'].startswith("'"): | |
new_value = "'%s'" % new_value | |
else: | |
try: | |
new_value = self.cfg_data_obj.reformat_value_str( | |
value_str, | |
self.cfg_data_obj.get_cfg_item_length(item), | |
item['value']) | |
except Exception: | |
print("WARNING: Failed to format value string '%s' for '%s' !" | |
% (value_str, item['path'])) | |
new_value = item['value'] | |
if item['value'] != new_value: | |
if self.debug: | |
print('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): | |
name = self.get_object_name(widget) | |
if not name or not len(self.page_list): | |
return None | |
if name.startswith('LABEL_'): | |
if label: | |
path = name[6:] | |
else: | |
return None | |
else: | |
path = name | |
item = self.cfg_data_obj.get_item_by_path(path) | |
return item | |
def update_config_data_from_widget(self, widget, args): | |
item = self.get_config_data_item_from_widget(widget) | |
if item is None: | |
return | |
elif not item: | |
if isinstance(widget, tkinter.Label): | |
return | |
raise Exception('Failed to find "%s" !' % | |
self.get_object_name(widget)) | |
itype = item['type'].split(',')[0] | |
if itype == "Combo": | |
opt_list = self.cfg_data_obj.get_cfg_item_options(item) | |
tmp_list = [opt[0] for opt in opt_list] | |
idx = widget.current() | |
if idx != -1: | |
self.set_config_item_value(item, tmp_list[idx]) | |
elif itype in ["EditNum", "EditText"]: | |
self.set_config_item_value(item, widget.get()) | |
elif itype in ["Table"]: | |
new_value = bytes_to_bracket_str(widget.get()) | |
self.set_config_item_value(item, new_value) | |
#YAML VFR Part Start | |
def update_vfr_config_data_from_widget(self, widget, args): | |
item = self.get_config_data_item_from_widget(widget) | |
#YAML VFR Part End | |
def evaluate_condition(self, item): | |
try: | |
result = self.cfg_data_obj.evaluate_condition(item) | |
except Exception: | |
print("WARNING: Condition '%s' is invalid for '%s' !" | |
% (item['condition'], item['path'])) | |
result = 1 | |
return result | |
#YAML VFR Part Start | |
def add_vfr_config_item(self, item, row): | |
parent = self.right_grid | |
widget = None | |
if item['type'] == 'string': | |
value = '' | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
elif item['type'] == 'text': | |
value = '' | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
elif item['type'] == 'label': | |
value = '' | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
elif item['type'] == 'checkbox': | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
widget = tkinter.Checkbutton(parent, text= item['prompt'].split("#")[0], variable= 1) | |
elif item['type'] == 'subtitle': | |
value = '' | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
elif item['type'] == 'oneof': | |
OPTIONS = [] | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
for key in item: | |
if key.startswith("option"): | |
if type(item[key]) == type([]): | |
for option_data in item[key]: | |
OPTIONS.append(option_data['text']) | |
else: | |
OPTIONS.append(item[key]["text"]) | |
txt_val = tkinter.StringVar() | |
txt_val.set(OPTIONS[0]) # set default value | |
widget = tkinter.OptionMenu(parent, txt_val, *OPTIONS) | |
txt_val.set(OPTIONS[0]) | |
elif item['type'] == 'numeric': | |
value = 0 | |
for key in item.keys(): | |
if key == "value": | |
value = item['value'] | |
elif key == 'default': | |
for dict_key in item['default']: | |
if dict_key == "value": | |
value = item['default']['value'] | |
else: | |
continue | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
elif item['type'] == 'orderedlist': | |
OPTIONS = [] | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
for key in item: | |
if key.startswith("option"): | |
if type(item[key]) == type([]): | |
for option_data in item[key]: | |
OPTIONS.append(option_data['text']) | |
else: | |
OPTIONS.append(item[key]["text"]) | |
txt_val = tkinter.StringVar() | |
txt_val.set(OPTIONS[0]) # default value | |
widget = tkinter.OptionMenu(parent, txt_val, *OPTIONS) | |
txt_val.set(OPTIONS[0]) | |
elif item['type'] == 'date': | |
value = '' | |
for key in item.keys(): | |
if key == "value": | |
value = item['value'] | |
elif key == 'default': | |
for dict_key in item['default']: | |
if dict_key == "value": | |
value = item['default']['value'] | |
else: | |
continue | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
elif item['type'] == 'time': | |
value = '' | |
for key in item.keys(): | |
if key == "value": | |
value = item['value'] | |
elif key == 'default': | |
for dict_key in item['default']: | |
if dict_key == "value": | |
value = item['default']['value'] | |
else: | |
continue | |
name = tkinter.Label(parent, text=item['prompt'].split("#")[0], anchor="w") | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
txt_val.set(value) | |
if widget: | |
if item['type'] == 'string' or item['type'] == 'text' or item['type'] == 'numeric' or item['type'] == "oneof"\ | |
or item['type'] == 'date' or item['type'] == 'time' or item['type'] == 'orderedlist' or item['type'] == 'label':# or item['type'] == 'goto'or item['type'] == 'checkbox': | |
if 'help' in item.keys(): | |
create_tool_tip(widget, item['help'].split("#")[0]) | |
name.grid(row=row, column=0, padx=5, pady=5, sticky="nsew") | |
widget.grid(row=row + 1, rowspan=1, column=0, | |
padx=5, pady=5, sticky="nsew") | |
#YAML VFR Part End | |
def add_config_item(self, item, row): | |
parent = self.right_grid | |
name = tkinter.Label(parent, text=item['name'], anchor="w") | |
parts = item['type'].split(',') | |
itype = parts[0] | |
widget = None | |
if itype == "Combo": | |
# Build | |
opt_list = self.cfg_data_obj.get_cfg_item_options(item) | |
current_value = self.cfg_data_obj.get_cfg_item_value(item, False) | |
option_list = [] | |
current = None | |
for idx, option in enumerate(opt_list): | |
option_str = option[0] | |
try: | |
option_value = self.cfg_data_obj.get_value( | |
option_str, | |
len(option_str), False) | |
except Exception: | |
option_value = 0 | |
print('WARNING: Option "%s" has invalid format for "%s" !' | |
% (option_str, item['path'])) | |
if option_value == current_value: | |
current = idx | |
option_list.append(option[1]) | |
widget = ttk.Combobox(parent, value=option_list, state="readonly") | |
widget.bind("<<ComboboxSelected>>", self.combo_select_changed) | |
widget.unbind_class("TCombobox", "<MouseWheel>") | |
if current is None: | |
print('WARNING: Value "%s" is an invalid option for "%s" !' % | |
(current_value, item['path'])) | |
self.invalid_values[item['path']] = current_value | |
else: | |
widget.current(current) | |
elif itype in ["EditNum", "EditText"]: | |
txt_val = tkinter.StringVar() | |
widget = tkinter.Entry(parent, textvariable=txt_val) | |
value = item['value'].strip("'") | |
if itype in ["EditText"]: | |
txt_val.trace( | |
'w', | |
lambda *args: self.limit_entry_size | |
(txt_val, (self.cfg_data_obj.get_cfg_item_length(item) | |
+ 7) // 8)) | |
elif itype in ["EditNum"]: | |
value = item['value'].strip("{").strip("}").strip() | |
widget.bind("<FocusOut>", self.edit_num_finished) | |
txt_val.set(value) | |
elif itype in ["Table"]: | |
bins = self.cfg_data_obj.get_cfg_item_value(item, True) | |
col_hdr = item['option'].split(',') | |
widget = custom_table(parent, col_hdr, bins) | |
else: | |
if itype and itype not in ["Reserved"]: | |
print("WARNING: Type '%s' is invalid for '%s' !" % | |
(itype, item['path'])) | |
self.invalid_values[item['path']] = itype | |
if widget: | |
create_tool_tip(widget, item['help']) | |
self.set_object_name(name, 'LABEL_' + item['path']) | |
self.set_object_name(widget, item['path']) | |
name.grid(row=row, column=0, padx=10, pady=5, sticky="nsew") | |
widget.grid(row=row + 1, rowspan=1, column=0, | |
padx=10, pady=5, sticky="nsew") | |
def update_config_data_on_page(self): | |
if self.mode == "fsp": | |
self.walk_widgets_in_layout(self.right_grid, | |
self.update_config_data_from_widget) | |
elif self.mode == "vfr": | |
self.walk_widgets_in_layout(self.right_grid, | |
self.update_vfr_config_data_from_widget) | |
else: | |
print("WARNING: Invalid config file!!") | |
if __name__ == '__main__': | |
root = tkinter.Tk() | |
app = application(master=root) | |
root.title("Config Editor") | |
root.mainloop() |