| # @ 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() |