Update FSP to follow: https://firmware.intel.com/sites/default/files/FSP_EAS_v2.0_Draft%20External.pdf Update BSF handler to follow: https://firmware.intel.com/sites/default/files/BSF_1_0.pdf
Update PatchFv tool to align with FSP2.0 to handle FSP-T, FSP-M, FSP-S. Update GenCfgOpt to follow BSF1.0. Add FspTool to split a FSP 2.0 compatibale image into individual FSP-T/M/S and generate the mapping header file Cc: Giri P Mudusuru <giri.p.mudus...@intel.com> Cc: Maurice Ma <maurice...@intel.com> Cc: Ravi P Rangarajan <ravi.p.rangara...@intel.com> Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Jiewen Yao <jiewen....@intel.com> Reviewed-by: Giri P Mudusuru <giri.p.mudus...@intel.com> Reviewed-by: Maurice Ma <maurice...@intel.com> Reviewed-by: Ravi P Rangarajan <ravi.p.rangara...@intel.com> --- IntelFspPkg/Tools/FspTool.py | 363 ++++++++++ IntelFspPkg/Tools/GenCfgOpt.py | 739 ++++++++++++-------- IntelFspPkg/Tools/PatchFv.py | 106 ++- IntelFspPkg/Tools/UserManuals/GenCfgOptUserManual.docx | Bin 24424 -> 28336 bytes 4 files changed, 882 insertions(+), 326 deletions(-) diff --git a/IntelFspPkg/Tools/FspTool.py b/IntelFspPkg/Tools/FspTool.py new file mode 100644 index 0000000..512304b --- /dev/null +++ b/IntelFspPkg/Tools/FspTool.py @@ -0,0 +1,363 @@ +## @ FspTool.py +# +# Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved.<BR> +# This program and the accompanying materials are licensed and made available under +# the terms and conditions of the BSD License that accompanies this distribution. +# The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php. +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# +## + +import os +import sys +import uuid +import copy +import struct +import argparse +from ctypes import * + +""" +This utility supports some operations for Intel FSP image. +It supports: + - Split a FSP 2.0 compatibale image into individual FSP-T/M/S/C + and generate the mapping header file. +""" + +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 get_value(self): + return ( + (self.Data[0] ) + + (self.Data[1] << 8) + + (self.Data[2] << 16) + ) + + def set_value(self, val): + self.Data[0] = (val ) & 0xff + self.Data[1] = (val >> 8) & 0xff + self.Data[2] = (val >> 16) & 0xff + + value = property(get_value, set_value) + +class EFI_FIRMWARE_VOLUME_HEADER(Structure): + _fields_ = [ + ('ZeroVector', ARRAY(c_uint8, 16)), + ('FileSystemGuid', ARRAY(c_char, 16)), + ('FvLength', c_uint64), + ('Signature', c_uint32), + ('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_char, 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_char, 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 FSP_INFORMATION_HEADER(Structure): + _fields_ = [ + ('Signature', c_uint32), + ('HeaderLength', c_uint32), + ('Reserved1', ARRAY(c_uint8, 3)), + ('HeaderRevision', c_uint8), + ('ImageRevision', c_uint32), + ('ImageId', c_uint64), + ('ImageSize', c_uint32), + ('ImageBase', c_uint32), + ('ImageAttribute', c_uint32), + ('CfgRegionOffset', c_uint32), + ('CfgRegionSize', c_uint32), + ('ApiEntryNum', c_uint32), + ('NemInitEntry', c_uint32), + ('FspInitEntry', c_uint32), + ('NotifyPhaseEntry', c_uint32), + ('FspMemoryInitEntry', c_uint32), + ('TempRamExitEntry', c_uint32), + ('FspSiliconInitEntry', c_uint32) + ] + +class FspFv: + HeaderFile = """/* + * + * Automatically generated file; DO NOT EDIT. + * FSP mapping file + * + */ +""" + FspNameDict = { + "0" : "-C.Fv", + "1" : "-T.Fv", + "2" : "-M.Fv", + "3" : "-S.Fv", + } + + def __init__(self, FvBin): + self.FspFv = {} + self.FvList = [] + self.FspBin = FvBin + hfsp = open (self.FspBin, 'r+b') + self.FspDat = bytearray(hfsp.read()) + hfsp.close() + + def OutputStruct (self, obj, indent = 0): + max_key_len = 20 + pstr = (' ' * indent + '{0:<%d} = {1}\n') % max_key_len + if indent: + s = '' + else: + s = (' ' * indent + '<%s>:\n') % obj.__class__.__name__ + for field in obj._fields_: + key = field[0] + val = getattr(obj, key) + rep = '' + + if not isinstance(val, c_uint24) and isinstance(val, Structure): + s += pstr.format(key, val.__class__.__name__) + s += self.OutputStruct (val, indent + 1) + else: + if type(val) in (int, long): + rep = hex(val) + elif isinstance(val, str) and (len(val) == 16): + rep = str(uuid.UUID(bytes = val)) + elif isinstance(val, c_uint24): + rep = hex(val.get_value()) + elif 'c_ubyte_Array' in str(type(val)): + rep = str(list(bytearray(val))) + else: + rep = str(val) + s += pstr.format(key, rep) + return s + + def PrintFv (self): + print ("FV LIST:") + idx = 0 + for (fvh, fvhe, offset) in self.FvList: + guid = uuid.UUID(bytes = fvhe.FvName) + print ("FV%d FV GUID:%s Offset:0x%08X Length:0x%08X" % (idx, str(guid), offset, fvh.FvLength)) + idx = idx + 1 + print ("\nFSP LIST:") + for fsp in self.FspFv: + print "FSP%s contains FV%s" % (fsp, str(self.FspFv[fsp][1])) + print "\nFSP%s Info Header:" % fsp + fih = self.FspFv[fsp][0] + + def AlaignPtr (self, offset, alignment = 8): + return (offset + alignment - 1) & ~(alignment - 1) + + def GetFspInfoHdr (self, fvh, fvhe, fvoffset): + if fvhe: + offset = fvh.ExtHeaderOffset + fvhe.ExtHeaderSize + else: + offset = fvh.HeaderLength + offset = self.AlaignPtr(offset) + + # Now it should be 1st FFS + ffs = EFI_FFS_FILE_HEADER.from_buffer (self.FspDat, offset) + offset += sizeof(ffs) + offset = self.AlaignPtr(offset) + + # Now it should be 1st Section + sec = EFI_COMMON_SECTION_HEADER.from_buffer (self.FspDat, offset) + offset += sizeof(sec) + + # Now it should be FSP_INFO_HEADER + offset += fvoffset + fih = FSP_INFORMATION_HEADER.from_buffer (self.FspDat, offset) + if 'FSPH' != bytearray.fromhex('%08X' % fih.Signature)[::-1]: + return None + + return fih + + def GetFvHdr (self, offset): + fvh = EFI_FIRMWARE_VOLUME_HEADER.from_buffer (self.FspDat, offset) + if '_FVH' != bytearray.fromhex('%08X' % fvh.Signature)[::-1]: + return None, None + if fvh.ExtHeaderOffset > 0: + offset += fvh.ExtHeaderOffset + fvhe = EFI_FIRMWARE_VOLUME_EXT_HEADER.from_buffer (self.FspDat, offset) + else: + fvhe = None + return fvh, fvhe + + def GetFvData(self, idx): + (fvh, fvhe, offset) = self.FvList[idx] + return self.FspDat[offset:offset+fvh.FvLength] + + def CheckFsp (self): + if len(self.FspFv) == 0: + return + + fih = None + for fv in self.FspFv: + if not fih: + fih = self.FspFv[fv][0] + else: + newfih = self.FspFv[fv][0] + if (newfih.ImageId != fih.ImageId) or (newfih.ImageRevision != fih.ImageRevision): + raise Exception("Inconsistent FSP ImageId or ImageRevision detected !") + return + + def WriteFsp(self, dir, name): + if not name: + name = self.FspBin + fspname, ext = os.path.splitext(os.path.basename(name)) + for fv in self.FspFv: + filename = os.path.join(dir, fspname + fv + ext) + hfsp = open(filename, 'w+b') + for fvidx in self.FspFv[fv][1]: + hfsp.write (self.GetFvData(fvidx)) + hfsp.close() + + def WriteMap(self, dir, hfile): + if not hfile: + hfile = os.path.splitext(os.path.basename(self.FspBin))[0] + '.h' + fspname, ext = os.path.splitext(os.path.basename(hfile)) + filename = os.path.join(dir, fspname + ext) + hfsp = open(filename, 'w') + hfsp.write ('%s\n\n' % self.HeaderFile) + + firstfv = True + for fsp in self.FspFv: + fih = self.FspFv[fsp][0] + fvs = self.FspFv[fsp][1] + if firstfv: + IdStr = str(bytearray.fromhex('%016X' % fih.ImageId)[::-1]) + hfsp.write("#define FSP_IMAGE_ID 0x%016X /* '%s' */\n" % (fih.ImageId, IdStr)) + hfsp.write("#define FSP_IMAGE_REV 0x%08X \n\n" % fih.ImageRevision) + firstfv = False + hfsp.write ('#define FSP%s_BASE 0x%08X\n' % (fsp, fih.ImageBase)) + hfsp.write ('#define FSP%s_OFFSET 0x%08X\n' % (fsp, self.FvList[fvs[0]][-1])) + hfsp.write ('#define FSP%s_LENGTH 0x%08X\n\n' % (fsp, fih.ImageSize)) + hfsp.close() + + def ParseFsp (self): + self.FspFv = {} + flen = 0 + for (fvh, fvhe, offset) in self.FvList: + fih = self.GetFspInfoHdr (fvh, fvhe, offset) + if fih: + if flen != 0: + raise Exception("Incorrect FV size in image !") + ftype = str((fih.ImageAttribute >> 29) & 7) + if ftype not in self.FspNameDict: + raise Exception("Unknown Attribute in image !") + fname = self.FspNameDict[str(ftype)] + if fname in self.FspFv: + raise Exception("Multiple '%s' in image !" % fname) + self.FspFv[fname] = (copy.deepcopy(fih), []) + flen = fih.ImageSize + if flen > 0: + flen = flen - fvh.FvLength + if flen < 0: + raise Exception("Incorrect FV size in image !") + self.FspFv[fname][1].append(self.FvList.index((fvh, fvhe, offset))) + + def AddFv(self, offset): + fvh, fvhe = self.GetFvHdr (offset) + if fvh is None: + raise Exception('FV signature is not valid !') + fvitem = (copy.deepcopy(fvh), copy.deepcopy(fvhe), offset) + self.FvList.append(fvitem) + return fvh.FvLength + + def ParseFv(self): + offset = 0 + while (offset < len(self.FspDat)): + fv_len = self.AddFv (offset) + offset += fv_len + +def GenFspHdr (fspfile, outdir, hfile, show): + fsp_fv = FspFv(fspfile) + fsp_fv.ParseFv() + fsp_fv.ParseFsp() + fsp_fv.CheckFsp() + if show: + fsp_fv.PrintFv() + fsp_fv.WriteMap(outdir, hfile) + +def SplitFspBin (fspfile, outdir, nametemplate, show): + fsp_fv = FspFv(fspfile) + fsp_fv.ParseFv() + fsp_fv.ParseFsp() + if show: + fsp_fv.PrintFv() + fsp_fv.WriteFsp(outdir, nametemplate) + +def main (): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title='commands') + + parser_split = subparsers.add_parser('split', help='split a FSP into multiple components') + parser_split.set_defaults(which='split') + parser_split.add_argument('-f', '--fspbin' , dest='FspBinary', type=str, help='FSP binary file path', required = True) + parser_split.add_argument('-o', '--outdir' , dest='OutputDir', type=str, help='Output directory path', default = '.') + parser_split.add_argument('-n', '--nametpl', dest='NameTemplate', type=str, help='Output name template', default = '') + parser_split.add_argument('-p', action='store_true', help='Print FSP FV information', default = False) + + parser_genhdr = subparsers.add_parser('genhdr', help='generate a header file for FSP binary') + parser_genhdr.set_defaults(which='genhdr') + parser_genhdr.add_argument('-f', '--fspbin' , dest='FspBinary', type=str, help='FSP binary file path', required = True) + parser_genhdr.add_argument('-o', '--outdir' , dest='OutputDir', type=str, help='Output directory path', default = '.') + parser_genhdr.add_argument('-n', '--hfile', dest='HFileName', type=str, help='Output header file name', default = '') + parser_genhdr.add_argument('-p', action='store_true', help='Print FSP FV information', default = False) + + args = parser.parse_args() + if args.which in ['split', 'genhdr']: + if not os.path.exists(args.FspBinary): + raise Exception ("Could not locate FSP binary file '%s' !" % args.FspBinary) + if not os.path.exists(args.OutputDir): + raise Exception ("Invalid output directory '%s' !" % args.OutputDir) + + if args.which == 'split': + SplitFspBin (args.FspBinary, args.OutputDir, args.NameTemplate, args.p) + elif args.which == 'genhdr': + GenFspHdr (args.FspBinary, args.OutputDir, args.HFileName, args.p) + else: + pass + + print 'Done!' + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/IntelFspPkg/Tools/GenCfgOpt.py b/IntelFspPkg/Tools/GenCfgOpt.py index a38da70..a6c4bfe 100644 --- a/IntelFspPkg/Tools/GenCfgOpt.py +++ b/IntelFspPkg/Tools/GenCfgOpt.py @@ -1,6 +1,6 @@ ## @ GenCfgOpt.py # -# Copyright (c) 2014 - 2015, Intel Corporation. All rights reserved.<BR> +# Copyright (c) 2014 - 2016, Intel Corporation. All rights reserved.<BR> # This program and the accompanying materials are licensed and made available under # the terms and conditions of the BSD License that accompanies this distribution. # The full text of the license may be found at @@ -88,48 +88,6 @@ are permitted provided that the following conditions are met: **/ """ -def UpdateMemSiUpdInitOffsetValue (DscFile): - DscFd = open(DscFile, "r") - DscLines = DscFd.readlines() - DscFd.close() - - DscContent = [] - MemUpdInitOffset = 0 - SiUpdInitOffset = 0 - MemUpdInitOffsetValue = 0 - SiUpdInitOffsetValue = 0 - - while len(DscLines): - DscLine = DscLines.pop(0) - DscContent.append(DscLine) - DscLine = DscLine.strip() - Match = re.match("^([_a-zA-Z0-9]+).(MemoryInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine) - if Match: - MemUpdInitOffsetValue = int(Match.group(5), 0) - Match = re.match("^\s*([_a-zA-Z0-9]+).(SiliconInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine) - if Match: - SiUpdInitOffsetValue = int(Match.group(5), 0) - Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(0x244450554D454D24)",DscLine) - if Match: - MemUpdInitOffset = int(Match.group(3), 0) - Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(0x244450555F495324)",DscLine) - if Match: - SiUpdInitOffset = int(Match.group(3), 0) - - if MemUpdInitOffsetValue != MemUpdInitOffset or SiUpdInitOffsetValue != SiUpdInitOffset: - MemUpdInitOffsetStr = "0x%08X" % MemUpdInitOffset - SiUpdInitOffsetStr = "0x%08X" % SiUpdInitOffset - DscFd = open(DscFile,"w") - for DscLine in DscContent: - Match = re.match("^\s*([_a-zA-Z0-9]+).(MemoryInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine) - if Match: - DscLine = re.sub(r'(?:[^\s]+\s*$)', MemUpdInitOffsetStr + '\n', DscLine) - Match = re.match("^\s*([_a-zA-Z0-9]+).(SiliconInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine) - if Match: - DscLine = re.sub(r'(?:[^\s]+\s*$)', SiUpdInitOffsetStr + '\n', line) - DscFd.writelines(DscLine) - DscFd.close() - class CLogicalExpression: def __init__(self): self.index = 0 @@ -331,6 +289,7 @@ class CGenCfgOpt: def __init__(self): self.Debug = False self.Error = '' + self.ReleaseMode = True self._GlobalDataDef = """ GlobalDataDef @@ -347,7 +306,7 @@ EndList """ self._BsfKeyList = ['FIND','NAME','HELP','TYPE','PAGE','OPTION','ORDER'] - self._HdrKeyList = ['HEADER','STRUCT', 'EMBED'] + self._HdrKeyList = ['HEADER','STRUCT', 'EMBED', 'COMMENT'] self._BuidinOption = {'$EN_DIS' : 'EN_DIS'} self._MacroDict = {} @@ -358,6 +317,13 @@ EndList self._FvDir = '' self._MapVer = 0 + def ParseBuildMode (self, OutputStr): + if "RELEASE_" in OutputStr: + self.ReleaseMode = True + if "DEBUG_" in OutputStr: + self.ReleaseMode = False + return + def ParseMacros (self, MacroDefStr): # ['-DABC=1', '-D', 'CFG_DEBUG=1', '-D', 'CFG_OUTDIR=Build'] self._MacroDict = {} @@ -445,7 +411,7 @@ EndList ConfigDict['value'] = newvalue return "" - def ParseDscFile (self, DscFile, FvDir): + def ParseDscFile (self, DscFile, FvDir, ConfigDscFile, ExtConfigDscFile): self._CfgItemList = [] self._CfgPageDict = {} self._CfgBlkDict = {} @@ -475,20 +441,6 @@ EndList IsDefSect = True IsVpdSect = False IsUpdSect = False - elif Match.group(1).lower() == "PcdsDynamicVpd".lower(): - ConfigDict = {} - ConfigDict['header'] = 'ON' - ConfigDict['region'] = 'VPD' - ConfigDict['order'] = -1 - ConfigDict['page'] = '' - ConfigDict['name'] = '' - ConfigDict['find'] = '' - ConfigDict['struct'] = '' - ConfigDict['embed'] = '' - ConfigDict['subreg'] = [] - IsDefSect = False - IsVpdSect = True - IsUpdSect = False elif Match.group(1).lower() == "PcdsDynamicVpd.Upd".lower(): ConfigDict = {} ConfigDict['header'] = 'ON' @@ -499,6 +451,7 @@ EndList ConfigDict['find'] = '' ConfigDict['struct'] = '' ConfigDict['embed'] = '' + ConfigDict['comment'] = '' ConfigDict['subreg'] = [] IsDefSect = False IsUpdSect = True @@ -537,7 +490,41 @@ EndList else: Match = re.match("!(if|elseif)\s+(.+)", DscLine) if Match: - Result = self.EvaluateExpress(Match.group(2)) + IsFoundInFile = False + MatchPcdFormat = re.match("^\s*(.+)\.(.+)\s*==\s*(.+)", Match.group(2)) + if MatchPcdFormat: + ExtConfigDsc = open(ExtConfigDscFile, "r") + ExtConfigDscLines = ExtConfigDsc.readlines() + ExtConfigDsc.close() + + while len(ExtConfigDscLines): + ExtConfigDscLine = ExtConfigDscLines.pop(0).strip() + MatchExtConfigPcd = re.match("^\s*(.+)\s*\|\s*(.+)", ExtConfigDscLine) + if MatchExtConfigPcd and IsFoundInFile == False: + PcdFormatStr = str(str(MatchPcdFormat.group(1)) + "." + str(MatchPcdFormat.group(2))) + ExtConfigPcd = str(MatchExtConfigPcd.group(1)) + Result = False + if PcdFormatStr.strip() == ExtConfigPcd.strip(): + Result = self.EvaluateExpress(str(str(MatchExtConfigPcd.group(2)) + " == " + str(MatchPcdFormat.group(3)))) + IsFoundInFile = True + break + if IsFoundInFile == False: + ConfigDsc = open(ConfigDscFile, "r") + ConfigDscLines = ConfigDsc.readlines() + ConfigDsc.close() + while len(ConfigDscLines): + ConfigDscLine = ConfigDscLines.pop(0).strip() + MatchConfigPcd = re.match("^\s*(.+)\s*\|\s*(.+)", ConfigDscLine) + if MatchConfigPcd: + PcdFormatStr = str(str(MatchPcdFormat.group(1)) + "." + str(MatchPcdFormat.group(2))) + ConfigPcd = str(MatchConfigPcd.group(1)) + Result = False + if PcdFormatStr.strip() == ConfigPcd.strip(): + Result = self.EvaluateExpress(str(str(MatchConfigPcd.group(2)) + " == " + str(MatchPcdFormat.group(3)))) + IsFoundInFile = True + break + else: + Result = self.EvaluateExpress(Match.group(2)) if Match.group(1) == "if": ElifStack.append(0) IfStack.append(Result) @@ -575,17 +562,20 @@ EndList continue if IsDefSect: - #DEFINE UPD_TOOL_GUID = 8C3D856A-9BE6-468E-850A-24F7A8D38E09 + #DEFINE UPD_TOOL_GUID = 8C3D856A-9BE6-468E-850A-24F7A8D38E09 + #DEFINE FSP_T_UPD_TOOL_GUID = 34686CA3-34F9-4901-B82A-BA630F0714C6 + #DEFINE FSP_M_UPD_TOOL_GUID = 39A250DB-E465-4DD1-A2AC-E2BD3C0E2385 + #DEFINE FSP_S_UPD_TOOL_GUID = CAE3605B-5B34-4C85-B3D7-27D54273C40F Match = re.match("^\s*(?:DEFINE\s+)*(\w+)\s*=\s*([-.\w]+)", DscLine) if Match: self._MacroDict[Match.group(1)] = Match.group(2) if self.Debug: print "INFO : DEFINE %s = [ %s ]" % (Match.group(1), Match.group(2)) else: - Match = re.match("^\s*#\s+!(BSF|HDR)\s+(.+)", DscLine) + Match = re.match("^\s*#\s+(!BSF|@Bsf|!HDR)\s+(.+)", DscLine) if Match: Remaining = Match.group(2) - if Match.group(1) == 'BSF': + if Match.group(1) == '!BSF' or Match.group(1) == '@Bsf': Match = re.match("(?:^|.+\s+)PAGES:{(.+?)}", Remaining) if Match: # !BSF PAGES:{HSW:"Haswell System Agent", LPT:"Lynx Point PCH"} @@ -613,6 +603,37 @@ EndList if Match: ConfigDict[Key.lower()] = Match.group(1) + Match = re.match("^\s*#\s+@Prompt\s+(.+)", DscLine) + if Match: + ConfigDict['name'] = Match.group(1) + + Match = re.match("^\s*#\s*@ValidList\s*(.+)\s*\|\s*(.+)\s*\|\s*(.+)\s*", DscLine) + if Match: + if Match.group(2).strip() in self._BuidinOption: + ConfigDict['option'] = Match.group(2).strip() + else: + OptionValueList = Match.group(2).split(',') + OptionStringList = Match.group(3).split(',') + Index = 0 + for Option in OptionValueList: + Option = Option.strip() + ConfigDict['option'] = ConfigDict['option'] + str(Option) + ':' + OptionStringList[Index].strip() + Index += 1 + if Index in range(len(OptionValueList)): + ConfigDict['option'] += ', ' + ConfigDict['type'] = "Combo" + + Match = re.match("^\s*#\s*@ValidRange\s*(.+)\s*\|\s*(.+)\s*-\s*(.+)\s*", DscLine) + if Match: + if "0x" in Match.group(2) or "0x" in Match.group(3): + ConfigDict['type'] = "EditNum, HEX, (%s,%s)" % (Match.group(2), Match.group(3)) + else: + ConfigDict['type'] = "EditNum, DEC, (%s,%s)" % (Match.group(2), Match.group(3)) + + Match = re.match("^\s*##\s+(.+)", DscLine) + if Match: + ConfigDict['help'] = Match.group(1) + # Check VPD/UPD if IsUpdSect: Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine) @@ -666,16 +687,20 @@ EndList ConfigDict['find'] = '' ConfigDict['struct'] = '' ConfigDict['embed'] = '' + ConfigDict['comment'] = '' ConfigDict['order'] = -1 ConfigDict['subreg'] = [] + ConfigDict['option'] = '' else: # It could be a virtual item as below # !BSF FIELD:{1:SerialDebugPortAddress0} - Match = re.match("^\s*#\s+!BSF\s+FIELD:{(.+):(\d+)}", DscLine) + # or + # @Bsf FIELD:{1:SerialDebugPortAddress0} + Match = re.match("^\s*#\s+(!BSF|@Bsf)\s+FIELD:{(.+):(\d+)}", DscLine) if Match: SubCfgDict = ConfigDict - SubCfgDict['cname'] = Match.group(1) - SubCfgDict['length'] = int (Match.group(2)) + SubCfgDict['cname'] = Match.group(2) + SubCfgDict['length'] = int (Match.group(3)) if SubCfgDict['length'] > 0: LastItem = self._CfgItemList[-1] if len(LastItem['subreg']) == 0: @@ -723,107 +748,132 @@ EndList SubItem['value'] = '{%s}' % valuestr return Error - def UpdateVpdSizeField (self): - FvDir = self._FvDir; - - if 'VPD_TOOL_GUID' not in self._MacroDict: - self.Error = "VPD_TOOL_GUID definition is missing in DSC file" - return 1 - - VpdMapFile = os.path.join(FvDir, self._MacroDict['VPD_TOOL_GUID'] + '.map') - if not os.path.exists(VpdMapFile): - self.Error = "VPD MAP file '%s' does not exist" % VpdMapFile - return 2 - - MapFd = open(VpdMapFile, "r") - MapLines = MapFd.readlines() - MapFd.close() - - VpdDict = {} - PcdDict = {} - for MapLine in MapLines: - #gPlatformFspPkgTokenSpaceGuid.PcdVpdRegionSign | DEFAULT | 0x0000 | 8 | 0x534450565F425346 - #gPlatformFspPkgTokenSpaceGuid.PcdVpdRegionSign | 0x0000 | 8 | 0x534450565F425346 - #gPlatformFspPkgTokenSpaceGuid.PcdTest | 0x0008 | 5 | {0x01,0x02,0x03,0x04,0x05} - Match = re.match("([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)(\s\|\sDEFAULT)?\s\|\s(0x[0-9A-F]{4})\s\|\s(\d+|0x[0-9a-fA-F]+)\s\|\s(\{?[x0-9a-fA-F,\s]+\}?)", MapLine) - if Match: - Space = Match.group(1) - Name = Match.group(2) - if (self._MapVer == 0) and (Match.group(3) != None): - self._MapVer = 1 - Offset = int (Match.group(4), 16) - if Match.group(5).startswith("0x"): - Length = int (Match.group(5), 16) - else : - Length = int (Match.group(5)) - PcdDict["len"] = Length - PcdDict["value"] = Match.group(6) - VpdDict[Space+'.'+Name] = dict(PcdDict) - - for Item in self._CfgItemList: - if Item['value'] == '': - Item['value'] = VpdDict[Item['space']+'.'+Item['cname']]['value'] - if Item['length'] == -1: - Item['length'] = VpdDict[Item['space']+'.'+Item['cname']]['len'] - if Item['struct'] != '': - Type = Item['struct'].strip() - if Type.endswith('*') and (Item['length'] != 4): - self.Error = "Struct pointer '%s' has invalid size" % Type - return 3 - - return 0 - - def CreateUpdTxtFile (self, UpdTxtFile): - FvDir = self._FvDir - if 'UPD_TOOL_GUID' not in self._MacroDict: - self.Error = "UPD_TOOL_GUID definition is missing in DSC file" - return 1 + def CreateSplitUpdTxt (self, UpdTxtFile): + GuidList = ['FSP_T_UPD_TOOL_GUID','FSP_M_UPD_TOOL_GUID','FSP_S_UPD_TOOL_GUID'] + SignatureList = ['0x4450555F54505346', '0x4450555F4D505346','0x4450555F53505346'] # FSPT_UPD, FSPM_UPD, and FSPS_UPD + for Index in range(len(GuidList)): + UpdTxtFile = '' + FvDir = self._FvDir + if GuidList[Index] not in self._MacroDict: + self.Error = "%s definition is missing in DSC file" % (GuidList[Index]) + return 1 - if UpdTxtFile == '': - UpdTxtFile = os.path.join(FvDir, self._MacroDict['UPD_TOOL_GUID'] + '.txt') + if UpdTxtFile == '': + UpdTxtFile = os.path.join(FvDir, self._MacroDict[GuidList[Index]] + '.txt') - ReCreate = False - if not os.path.exists(UpdTxtFile): - ReCreate = True - else: - DscTime = os.path.getmtime(self._DscFile) - TxtTime = os.path.getmtime(UpdTxtFile) - if DscTime > TxtTime: + ReCreate = False + if not os.path.exists(UpdTxtFile): ReCreate = True + else: + DscTime = os.path.getmtime(self._DscFile) + TxtTime = os.path.getmtime(UpdTxtFile) + if DscTime > TxtTime: + ReCreate = True - if not ReCreate: - # DSC has not been modified yet - # So don't have to re-generate other files - self.Error = 'No DSC file change, skip to create UPD TXT file' - return 256 + if not ReCreate: + # DSC has not been modified yet + # So don't have to re-generate other files + self.Error = 'No DSC file change, skip to create UPD TXT file' + return 256 - TxtFd = open(UpdTxtFile, "w") - TxtFd.write("%s\n" % (__copyright_txt__ % date.today().year)) + TxtFd = open(UpdTxtFile, "w") + TxtFd.write("%s\n" % (__copyright_txt__ % date.today().year)) - NextOffset = 0 - SpaceIdx = 0 - if self._MapVer == 1: + NextOffset = 0 + SpaceIdx = 0 + StartAddr = 0 + EndAddr = 0 Default = 'DEFAULT|' - else: - Default = '' - for Item in self._CfgItemList: - if Item['region'] != 'UPD': - continue - Offset = Item['offset'] - if NextOffset < Offset: - # insert one line - TxtFd.write("%s.UnusedUpdSpace%d|%s0x%04X|0x%04X|{0}\n" % (Item['space'], SpaceIdx, Default, NextOffset, Offset - NextOffset)) - SpaceIdx = SpaceIdx + 1 - NextOffset = Offset + Item['length'] - TxtFd.write("%s.%s|%s0x%04X|%s|%s\n" % (Item['space'],Item['cname'],Default,Item['offset'],Item['length'],Item['value'])) - TxtFd.close() + InRange = False + for Item in self._CfgItemList: + if Item['cname'] == 'Signature' and str(Item['value']) == SignatureList[Index]: + StartAddr = Item['offset'] + NextOffset = StartAddr + InRange = True + if Item['cname'] == 'UpdTerminator' and InRange == True: + EndAddr = Item['offset'] + InRange = False + InRange = False + for Item in self._CfgItemList: + if Item['cname'] == 'Signature' and str(Item['value']) == SignatureList[Index]: + InRange = True + if InRange != True: + continue + if Item['cname'] == 'UpdTerminator': + InRange = False + if Item['region'] != 'UPD': + continue + Offset = Item['offset'] + if StartAddr > Offset or EndAddr < Offset: + continue + if NextOffset < Offset: + # insert one line + TxtFd.write("%s.UnusedUpdSpace%d|%s0x%04X|0x%04X|{0}\n" % (Item['space'], SpaceIdx, Default, NextOffset - StartAddr, Offset - NextOffset)) + SpaceIdx = SpaceIdx + 1 + NextOffset = Offset + Item['length'] + if Item['cname'] == 'PcdSerialIoUartDebugEnable': + if self.ReleaseMode == False: + Item['value'] = 0x01 + TxtFd.write("%s.%s|%s0x%04X|%s|%s\n" % (Item['space'],Item['cname'],Default,Item['offset'] - StartAddr,Item['length'],Item['value'])) + TxtFd.close() return 0 - def CreateField (self, Item, Name, Length, Offset, Struct, BsfName, Help): + def ProcessMultilines (self, String, MaxCharLength): + Multilines = '' + StringLength = len(String) + CurrentStringStart = 0 + StringOffset = 0 + BreakLineDict = [] + if len(String) <= MaxCharLength: + while (StringOffset < StringLength): + if StringOffset >= 1: + if String[StringOffset - 1] == '\\' and String[StringOffset] == 'n': + BreakLineDict.append (StringOffset + 1) + StringOffset += 1 + if BreakLineDict != []: + for Each in BreakLineDict: + Multilines += " %s\n" % String[CurrentStringStart:Each].lstrip() + CurrentStringStart = Each + if StringLength - CurrentStringStart > 0: + Multilines += " %s\n" % String[CurrentStringStart:].lstrip() + else: + Multilines = " %s\n" % String + else: + NewLineStart = 0 + NewLineCount = 0 + FoundSpaceChar = False + while (StringOffset < StringLength): + if StringOffset >= 1: + if NewLineCount >= MaxCharLength - 1: + if String[StringOffset] == ' ' and StringLength - StringOffset > 10: + BreakLineDict.append (NewLineStart + NewLineCount) + NewLineStart = NewLineStart + NewLineCount + NewLineCount = 0 + FoundSpaceChar = True + elif StringOffset == StringLength - 1 and FoundSpaceChar == False: + BreakLineDict.append (0) + if String[StringOffset - 1] == '\\' and String[StringOffset] == 'n': + BreakLineDict.append (StringOffset + 1) + NewLineStart = StringOffset + 1 + NewLineCount = 0 + StringOffset += 1 + NewLineCount += 1 + if BreakLineDict != []: + BreakLineDict.sort () + for Each in BreakLineDict: + if Each > 0: + Multilines += " %s\n" % String[CurrentStringStart:Each].lstrip() + CurrentStringStart = Each + if StringLength - CurrentStringStart > 0: + Multilines += " %s\n" % String[CurrentStringStart:].lstrip() + return Multilines + + def CreateField (self, Item, Name, Length, Offset, Struct, BsfName, Help, Option): PosName = 28 PosComment = 30 NameLine='' HelpLine='' + OptionLine='' IsArray = False if Length in [1,2,4,8]: @@ -854,17 +904,22 @@ EndList Space1 = 1 if BsfName != '': - NameLine=" %s\n" % BsfName + NameLine=" - %s\n" % BsfName + else: + NameLine="\n" if Help != '': - HelpLine=" %s\n" % Help + HelpLine = self.ProcessMultilines (Help, 80) + + if Option != '': + OptionLine = self.ProcessMultilines (Option, 80) if Offset is None: OffsetStr = '????' else: OffsetStr = '0x%04X' % Offset - return "/** Offset %s\n%s%s**/\n %s%s%s;\n" % (OffsetStr, NameLine, HelpLine, Type, ' ' * Space1, Name,) + return "\n/** Offset %s%s%s%s**/\n %s%s%s;\n" % (OffsetStr, NameLine, HelpLine, OptionLine, Type, ' ' * Space1, Name,) def PostProcessBody (self, TextBody): NewTextBody = [] @@ -872,13 +927,32 @@ EndList IncludeLine = False StructName = '' VariableName = '' + IsUpdHdrDefined = False + IsUpdHeader = False for Line in TextBody: + SplitToLines = Line.splitlines() + MatchComment = re.match("^/\*\sCOMMENT:(\w+):([\w|\W|\s]+)\s\*/\s([\s\S]*)", SplitToLines[0]) + if MatchComment: + if MatchComment.group(1) == 'FSP_UPD_HEADER': + IsUpdHeader = True + else: + IsUpdHeader = False + if IsUpdHdrDefined != True or IsUpdHeader != True: + CommentLine = " " + MatchComment.group(2) + "\n" + NewTextBody.append("/**" + CommentLine + "**/\n") + Line = Line[(len(SplitToLines[0]) + 1):] + Match = re.match("^/\*\sEMBED_STRUCT:(\w+):(\w+):(START|END)\s\*/\s([\s\S]*)", Line) if Match: Line = Match.group(4) + if Match.group(1) == 'FSP_UPD_HEADER': + IsUpdHeader = True + else: + IsUpdHeader = False if Match and Match.group(3) == 'START': - NewTextBody.append ('typedef struct {\n') + if IsUpdHdrDefined != True or IsUpdHeader != True: + NewTextBody.append ('typedef struct {\n') StructName = Match.group(1) VariableName = Match.group(2) MatchOffset = re.search('/\*\*\sOffset\s0x([a-fA-F0-9]+)', Line) @@ -888,9 +962,10 @@ EndList Offset = None Line IncludeLine = True - OldTextBody.append (self.CreateField (None, VariableName, 0, Offset, StructName, '', '')) + OldTextBody.append (self.CreateField (None, VariableName, 0, Offset, StructName, '', '', '')) if IncludeLine: - NewTextBody.append (Line) + if IsUpdHdrDefined != True or IsUpdHeader != True: + NewTextBody.append (Line) else: OldTextBody.append (Line) @@ -898,37 +973,21 @@ EndList if (StructName != Match.group(1)) or (VariableName != Match.group(2)): print "Unmatched struct name '%s' and '%s' !" % (StructName, Match.group(1)) else: - NewTextBody.append ('} %s;\n\n' % StructName) + if IsUpdHdrDefined != True or IsUpdHeader != True: + NewTextBody.append ('} %s;\n\n' % StructName) + IsUpdHdrDefined = True IncludeLine = False NewTextBody.extend(OldTextBody) return NewTextBody - def CreateHeaderFile (self, InputHeaderFile, IsInternal): + def CreateHeaderFile (self, InputHeaderFile): FvDir = self._FvDir - if IsInternal: - HeaderFile = os.path.join(FvDir, 'FspUpdVpdInternal.h') - else: - HeaderFile = os.path.join(FvDir, 'FspUpdVpd.h') + HeaderFileName = 'FspUpd.h' + HeaderFile = os.path.join(FvDir, HeaderFileName) # Check if header needs to be recreated ReCreate = False - if IsInternal: - if not os.path.exists(HeaderFile): - ReCreate = True - else: - DscTime = os.path.getmtime(self._DscFile) - HeadTime = os.path.getmtime(HeaderFile) - if not os.path.exists(InputHeaderFile): - InpTime = HeadTime - else: - InpTime = os.path.getmtime(InputHeaderFile) - if DscTime > HeadTime or InpTime > HeadTime: - ReCreate = True - - if not ReCreate: - self.Error = "No DSC or input header file is changed, skip the header file generating" - return 256 TxtBody = [] for Item in self._CfgItemList: @@ -939,51 +998,60 @@ EndList Chars.append(chr(Value & 0xFF)) Value = Value >> 8 SignatureStr = ''.join(Chars) - if int(Item['offset']) == 0: - TxtBody.append("#define FSP_UPD_SIGNATURE %s /* '%s' */\n" % (Item['value'], SignatureStr)) - elif 'MEM' in SignatureStr: - TxtBody.append("#define FSP_MEMORY_INIT_UPD_SIGNATURE %s /* '%s' */\n" % (Item['value'], SignatureStr)) - else: - TxtBody.append("#define FSP_SILICON_INIT_UPD_SIGNATURE %s /* '%s' */\n" % (Item['value'], SignatureStr)) + if 'FSPT' in SignatureStr: + TxtBody.append("#define FSPT_UPD_SIGNATURE %s /* '%s' */\n\n" % (Item['value'], SignatureStr)) + elif 'FSPM' in SignatureStr: + TxtBody.append("#define FSPM_UPD_SIGNATURE %s /* '%s' */\n\n" % (Item['value'], SignatureStr)) + elif 'FSPS' in SignatureStr: + TxtBody.append("#define FSPS_UPD_SIGNATURE %s /* '%s' */\n\n" % (Item['value'], SignatureStr)) TxtBody.append("\n") - for Region in ['UPD', 'VPD']: - - # Write PcdVpdRegionSign and PcdImageRevision - if Region[0] == 'V': - if 'VPD_TOOL_GUID' not in self._MacroDict: - self.Error = "VPD_TOOL_GUID definition is missing in DSC file" - return 1 - - BinFile = os.path.join(FvDir, self._MacroDict['VPD_TOOL_GUID'] + ".bin") - if not os.path.exists(BinFile): - self.Error = "VPD binary file '%s' does not exist" % BinFile - return 2 - - BinFd = open(BinFile, "rb") - IdStr = BinFd.read(0x08) - ImageId = struct.unpack('<Q', IdStr) - ImageRev = struct.unpack('<I', BinFd.read(0x04)) - BinFd.close() - - TxtBody.append("#define FSP_IMAGE_ID 0x%016X /* '%s' */\n" % (ImageId[0], IdStr)) - TxtBody.append("#define FSP_IMAGE_REV 0x%08X \n\n" % ImageRev[0]) - - TxtBody.append("typedef struct _" + Region[0] + "PD_DATA_REGION {\n") - NextOffset = 0 - SpaceIdx = 0 - Offset = 0 - - LastVisible = True - ResvOffset = 0 - ResvIdx = 0 - LineBuffer = [] + for Region in ['UPD']: + UpdOffsetTable = [] + UpdSignature = ['0x4450555F54505346', '0x4450555F4D505346', '0x4450555F53505346'] #['FSPT_UPD', 'FSPM_UPD', 'FSPS_UPD'] + UpdStructure = ['FSPT_UPD', 'FSPM_UPD', 'FSPS_UPD'] for Item in self._CfgItemList: - if Item['region'] != Region: - continue + if Item["cname"] == 'Signature' and Item["value"] in UpdSignature: + UpdOffsetTable.append (Item["offset"]) + + for UpdIdx in range(len(UpdOffsetTable)): + CommentLine = "" + for Item in self._CfgItemList: + if Item["comment"] != '' and Item["offset"] >= UpdOffsetTable[UpdIdx]: + MatchComment = re.match("^(U|V)PD_DATA_REGION:([\w|\W|\s]+)", Item["comment"]) + if MatchComment and MatchComment.group(1) == Region[0]: + CommentLine = " " + MatchComment.group(2) + "\n" + TxtBody.append("/**" + CommentLine + "**/\n") + elif Item["offset"] >= UpdOffsetTable[UpdIdx] and Item["comment"] == '': + Match = re.match("^FSP([\w|\W|\s])_UPD", UpdStructure[UpdIdx]) + if Match: + TxtBody.append("/** Fsp " + Match.group(1) + " UPD Configuration\n**/\n") + TxtBody.append("typedef struct {\n") + NextOffset = 0 + SpaceIdx = 0 + Offset = 0 + + LastVisible = True + ResvOffset = 0 + ResvIdx = 0 + LineBuffer = [] + InRange = False + for Item in self._CfgItemList: + if Item['cname'] == 'Signature' and str(Item['value']) == UpdSignature[UpdIdx] or Region[0] == 'V': + InRange = True + if InRange != True: + continue + if Item['cname'] == 'UpdTerminator': + InRange = False + + if Item['region'] != Region: + continue + + if Item["offset"] < UpdOffsetTable[UpdIdx]: + continue + + NextVisible = LastVisible - NextVisible = LastVisible - if not IsInternal: if LastVisible and (Item['header'] == 'OFF'): NextVisible = False ResvOffset = Item['offset'] @@ -991,50 +1059,53 @@ EndList NextVisible = True Name = "Reserved" + Region[0] + "pdSpace%d" % ResvIdx ResvIdx = ResvIdx + 1 - TxtBody.append(self.CreateField (Item, Name, Item["offset"] - ResvOffset, ResvOffset, '', '', '')) - - if Offset < Item["offset"]: - if IsInternal or LastVisible: - Name = "Unused" + Region[0] + "pdSpace%d" % SpaceIdx - LineBuffer.append(self.CreateField (Item, Name, Item["offset"] - Offset, Offset, '', '', '')) - SpaceIdx = SpaceIdx + 1 - Offset = Item["offset"] - - if Offset != Item["offset"]: - self.Error = "Unsorted offset 0x%04X\n" % Item["offset"] - return 3 - - LastVisible = NextVisible - - Offset = Offset + Item["length"] - if IsInternal or LastVisible: - for Each in LineBuffer: - TxtBody.append (Each) - LineBuffer = [] - Embed = Item["embed"].upper() - if Embed.endswith(':START') or Embed.endswith(':END'): - Marker = '/* EMBED_STRUCT:%s */ ' % Item["embed"] - else: - if Embed == '': - Marker = ''; + TxtBody.append(self.CreateField (Item, Name, Item["offset"] - ResvOffset, ResvOffset, '', '', '', '')) + + if Offset < Item["offset"]: + if LastVisible: + Name = "Unused" + Region[0] + "pdSpace%d" % SpaceIdx + LineBuffer.append(self.CreateField (Item, Name, Item["offset"] - Offset, Offset, '', '', '', '')) + SpaceIdx = SpaceIdx + 1 + Offset = Item["offset"] + + LastVisible = NextVisible + + Offset = Offset + Item["length"] + if LastVisible: + for Each in LineBuffer: + TxtBody.append (Each) + LineBuffer = [] + Comment = Item["comment"] + Embed = Item["embed"].upper() + if Embed.endswith(':START') or Embed.endswith(':END'): + if not Comment == '' and Embed.endswith(':START'): + Marker = '/* COMMENT:%s */ \n' % Item["comment"] + Marker = Marker + '/* EMBED_STRUCT:%s */ ' % Item["embed"] + else: + Marker = '/* EMBED_STRUCT:%s */ ' % Item["embed"] else: - self.Error = "Invalid embedded structure format '%s'!\n" % Item["embed"] - return 4 - Line = Marker + self.CreateField (Item, Item["cname"], Item["length"], Item["offset"], Item['struct'], Item['name'], Item['help']) - TxtBody.append(Line) - - TxtBody.append("} " + Region[0] + "PD_DATA_REGION;\n\n") + if Embed == '': + Marker = ''; + else: + self.Error = "Invalid embedded structure format '%s'!\n" % Item["embed"] + return 4 + Line = Marker + self.CreateField (Item, Item["cname"], Item["length"], Item["offset"], Item['struct'], Item['name'], Item['help'], Item['option']) + TxtBody.append(Line) + if Item['cname'] == 'UpdTerminator': + break + TxtBody.append("} " + UpdStructure[UpdIdx] + ";\n\n") # Handle the embedded data structure TxtBody = self.PostProcessBody (TxtBody) - HeaderFd = open(HeaderFile, "w") - FileBase = os.path.basename(HeaderFile) - FileName = FileBase.replace(".", "_").upper() - HeaderFd.write("%s\n" % (__copyright_h__ % date.today().year)) - HeaderFd.write("#ifndef __%s__\n" % FileName) - HeaderFd.write("#define __%s__\n\n" % FileName) - HeaderFd.write("#pragma pack(1)\n\n") + HeaderTFileName = 'FsptUpd.h' + HeaderMFileName = 'FspmUpd.h' + HeaderSFileName = 'FspsUpd.h' + + UpdRegionCheck = ['FSPT', 'FSPM', 'FSPS'] # FSPX_UPD_REGION + UpdConfigCheck = ['FSP_T', 'FSP_M', 'FSP_S'] # FSP_X_CONFIG, FSP_X_TEST_CONFIG, FSP_X_RESTRICTED_CONFIG + UpdSignatureCheck = ['FSPT_UPD_SIGNATURE', 'FSPM_UPD_SIGNATURE', 'FSPS_UPD_SIGNATURE'] + ExcludedSpecificUpd = 'FSPM_ARCH_UPD' if InputHeaderFile != '': if not os.path.exists(InputHeaderFile): @@ -1045,11 +1116,28 @@ EndList IncLines = InFd.readlines() InFd.close() + for item in range(len(UpdRegionCheck)): + if UpdRegionCheck[item] == 'FSPT': + HeaderFd = open(os.path.join(FvDir, HeaderTFileName), "w") + FileBase = os.path.basename(os.path.join(FvDir, HeaderTFileName)) + elif UpdRegionCheck[item] == 'FSPM': + HeaderFd = open(os.path.join(FvDir, HeaderMFileName), "w") + FileBase = os.path.basename(os.path.join(FvDir, HeaderMFileName)) + elif UpdRegionCheck[item] == 'FSPS': + HeaderFd = open(os.path.join(FvDir, HeaderSFileName), "w") + FileBase = os.path.basename(os.path.join(FvDir, HeaderSFileName)) + FileName = FileBase.replace(".", "_").upper() + HeaderFd.write("%s\n" % (__copyright_h__ % date.today().year)) + HeaderFd.write("#ifndef __%s__\n" % FileName) + HeaderFd.write("#define __%s__\n\n" % FileName) + HeaderFd.write("#include <%s>\n\n" % HeaderFileName) + HeaderFd.write("#pragma pack(push, 1)\n\n") + Export = False for Line in IncLines: - Match = re.search ("!EXPORT\s+EXTERNAL_BOOTLOADER_STRUCT_(BEGIN|END)\s+", Line) + Match = re.search ("!EXPORT\s+([A-Z]+)\s+EXTERNAL_BOOTLOADER_STRUCT_(BEGIN|END)\s+", Line) if Match: - if Match.group(1) == "BEGIN": + if Match.group(2) == "BEGIN" and Match.group(1) == UpdRegionCheck[item]: Export = True continue else: @@ -1057,11 +1145,86 @@ EndList continue if Export: HeaderFd.write(Line) - HeaderFd.write("\n\n") - - for Line in TxtBody: - HeaderFd.write (Line) - HeaderFd.write("#pragma pack()\n\n") + HeaderFd.write("\n") + + Index = 0 + StartIndex = 0 + EndIndex = 0 + StructStart = [] + StructStartWithComment = [] + StructEnd = [] + for Line in TxtBody: + Index += 1 + Match = re.match("(typedef struct {)", Line) + if Match: + StartIndex = Index - 1 + Match = re.match("}\s([_A-Z0-9]+);", Line) + if Match and (UpdRegionCheck[item] in Match.group(1) or UpdConfigCheck[item] in Match.group(1)) and (ExcludedSpecificUpd not in Match.group(1)): + EndIndex = Index + StructStart.append(StartIndex) + StructEnd.append(EndIndex) + Index = 0 + for Line in TxtBody: + Index += 1 + for Item in range(len(StructStart)): + if Index == StructStart[Item]: + Match = re.match("^(/\*\*\s*)", Line) + if Match: + StructStartWithComment.append(StructStart[Item]) + else: + StructStartWithComment.append(StructStart[Item] + 1) + Index = 0 + for Line in TxtBody: + Index += 1 + for Item in range(len(StructStart)): + if Index >= StructStartWithComment[Item] and Index <= StructEnd[Item]: + HeaderFd.write (Line) + HeaderFd.write("#pragma pack(pop)\n\n") + HeaderFd.write("#endif\n") + HeaderFd.close() + + HeaderFd = open(HeaderFile, "w") + FileBase = os.path.basename(HeaderFile) + FileName = FileBase.replace(".", "_").upper() + HeaderFd.write("%s\n" % (__copyright_h__ % date.today().year)) + HeaderFd.write("#ifndef __%s__\n" % FileName) + HeaderFd.write("#define __%s__\n\n" % FileName) + HeaderFd.write("#include <FspApi.h>\n\n") + HeaderFd.write("#pragma pack(push, 1)\n\n") + + for item in range(len(UpdRegionCheck)): + Index = 0 + StartIndex = 0 + EndIndex = 0 + StructStart = [] + StructStartWithComment = [] + StructEnd = [] + for Line in TxtBody: + Index += 1 + Match = re.match("(typedef struct {)", Line) + if Match: + StartIndex = Index - 1 + Match = re.match("#define\s([_A-Z0-9]+)\s*", Line) + if Match and (UpdSignatureCheck[item] in Match.group(1) or UpdSignatureCheck[item] in Match.group(1)): + StructStart.append(Index - 1) + StructEnd.append(Index) + Index = 0 + for Line in TxtBody: + Index += 1 + for Item in range(len(StructStart)): + if Index == StructStart[Item]: + Match = re.match("^(/\*\*\s*)", Line) + if Match: + StructStartWithComment.append(StructStart[Item]) + else: + StructStartWithComment.append(StructStart[Item] + 1) + Index = 0 + for Line in TxtBody: + Index += 1 + for Item in range(len(StructStart)): + if Index >= StructStartWithComment[Item] and Index <= StructEnd[Item]: + HeaderFd.write (Line) + HeaderFd.write("#pragma pack(pop)\n\n") HeaderFd.write("#endif\n") HeaderFd.close() @@ -1233,56 +1396,50 @@ def Main(): if not os.path.exists(DscFile): print "ERROR: Cannot open DSC file '%s' !" % DscFile return 2 - - UpdateMemSiUpdInitOffsetValue(DscFile) + ConfigDscFile = sys.argv[4] + if not os.path.exists(ConfigDscFile): + print "ERROR: Cannot open Config DSC file '%s' !" % ConfigDscFile + return 2 + ExtConfigDscFile = sys.argv[5] + if not os.path.exists(ExtConfigDscFile): + print "ERROR: Cannot open Ext Config DSC file '%s' !" % ExtConfigDscFile + return 2 OutFile = '' if argc > 4: - if sys.argv[4][0] == '-': + if sys.argv[6][0] == '-': Start = 4 else: OutFile = sys.argv[4] Start = 5 + GenCfgOpt.ParseBuildMode(sys.argv[3]) if GenCfgOpt.ParseMacros(sys.argv[Start:]) != 0: print "ERROR: Macro parsing failed !" return 3 FvDir = sys.argv[3] - if not os.path.isdir(FvDir): - print "ERROR: FV folder '%s' is invalid !" % FvDir - return 4 + if not os.path.exists(FvDir): + os.makedirs(FvDir) - if GenCfgOpt.ParseDscFile(DscFile, FvDir) != 0: + if GenCfgOpt.ParseDscFile(DscFile, FvDir, ConfigDscFile, ExtConfigDscFile) != 0: print "ERROR: %s !" % GenCfgOpt.Error return 5 - if GenCfgOpt.UpdateVpdSizeField() != 0: - print "ERROR: %s !" % GenCfgOpt.Error - return 6 - if GenCfgOpt.UpdateSubRegionDefaultValue() != 0: print "ERROR: %s !" % GenCfgOpt.Error return 7 if sys.argv[1] == "UPDTXT": - Ret = GenCfgOpt.CreateUpdTxtFile(OutFile) + Ret = GenCfgOpt.CreateSplitUpdTxt(OutFile) if Ret != 0: # No change is detected if Ret == 256: print "INFO: %s !" % (GenCfgOpt.Error) else : print "ERROR: %s !" % (GenCfgOpt.Error) - return Ret + return Ret elif sys.argv[1] == "HEADER": - Ret = GenCfgOpt.CreateHeaderFile(OutFile, True) - if Ret != 0: - # No change is detected - if Ret == 256: - print "INFO: %s !" % (GenCfgOpt.Error) - else : - print "ERROR: %s !" % (GenCfgOpt.Error) - return Ret - if GenCfgOpt.CreateHeaderFile(OutFile, False) != 0: + if GenCfgOpt.CreateHeaderFile(OutFile) != 0: print "ERROR: %s !" % GenCfgOpt.Error return 8 elif sys.argv[1] == "GENBSF": diff --git a/IntelFspPkg/Tools/PatchFv.py b/IntelFspPkg/Tools/PatchFv.py index b6dab55..fc2ee19 100644 --- a/IntelFspPkg/Tools/PatchFv.py +++ b/IntelFspPkg/Tools/PatchFv.py @@ -1,6 +1,6 @@ ## @ PatchFv.py # -# Copyright (c) 2014 - 2015, Intel Corporation. All rights reserved.<BR> +# Copyright (c) 2014 - 2016, Intel Corporation. All rights reserved.<BR> # This program and the accompanying materials are licensed and made available under # the terms and conditions of the BSD License that accompanies this distribution. # The full text of the license may be found at @@ -109,6 +109,7 @@ class Symbols: self.fdBase = 0xFFFFFFFF self.fdSize = 0 self.index = 0 + self.fvList = [] self.parenthesisOpenSet = '([{<' self.parenthesisCloseSet = ')]}>' @@ -128,6 +129,24 @@ class Symbols: def getFdSize (self): return self.fdSize + def parseFvInfFile (self, infFile): + fvInfo = {} + fvFile = infFile[0:-4] + ".Fv" + fvInfo['Name'] = os.path.splitext(os.path.basename(infFile))[0] + fvInfo['Offset'] = self.getFvOffsetInFd(fvFile) + fvInfo['Size'] = readDataFromFile (fvFile, 0x20, 4) + fdIn = open(infFile, "r") + rptLines = fdIn.readlines() + fdIn.close() + fvInfo['Base'] = 0 + for rptLine in rptLines: + match = re.match("^EFI_BASE_ADDRESS\s*=\s*(0x[a-fA-F0-9]+)", rptLine) + if match: + fvInfo['Base'] = int(match.group(1), 16) + break + self.fvList.append(dict(fvInfo)) + return 0 + # # Create dictionaries # @@ -196,6 +215,7 @@ class Symbols: # Collect information from FV MAP file and FV TXT file then # put them into dictionaries # + self.fvList = [] self.dictSymbolAddress = {} self.dictFfsOffset = {} for file in fvList: @@ -209,6 +229,8 @@ class Symbols: if not os.path.exists(mapFile): raise Exception("Cannot open MAP file '%s'!" % mapFile) + infFile = fvFile[0:-3] + ".inf" + self.parseFvInfFile(infFile) self.parseFvMapFile(mapFile) # @@ -221,6 +243,8 @@ class Symbols: self.parseFvTxtFile(fvTxtFile) + for fv in self.fvList: + self.dictVariable['_BASE_%s_' % fv['Name']] = fv['Base'] # # Search all MAP files in FFS directory if it exists then parse MOD MAP file # @@ -291,6 +315,7 @@ class Symbols: # retval 0 Parsed FV TXT file successfully # def parseFvTxtFile(self, fvTxtFile): + fvName = os.path.basename(fvTxtFile)[0:-7].upper() # # Get information from .Fv.txt in order to create a dictionary # For example, @@ -302,7 +327,10 @@ class Symbols: while (rptLine != "" ): match = re.match("(0x[a-fA-F0-9]+)\s([0-9a-fA-F\-]+)", rptLine) if match is not None: - self.dictFfsOffset[match.group(2)] = "0x%08X" % (int(match.group(1), 16) + fvOffset) + if match.group(2) in self.dictFfsOffset: + self.dictFfsOffset[fvName + ':' + match.group(2)] = "0x%08X" % (int(match.group(1), 16) + fvOffset) + else: + self.dictFfsOffset[match.group(2)] = "0x%08X" % (int(match.group(1), 16) + fvOffset) rptLine = fdIn.readline() fdIn.close() return 0 @@ -327,12 +355,14 @@ class Symbols: fdIn = open(mapFile, "r") rptLine = fdIn.readline() modName = "" + foundModHdr = False while (rptLine != "" ): if rptLine[0] != ' ': #DxeIpl (Fixed Flash Address, BaseAddress=0x00fffb4310, EntryPoint=0x00fffb4958) #(GUID=86D70125-BAA3-4296-A62F-602BEBBB9081 .textbaseaddress=0x00fffb4398 .databaseaddress=0x00fffb4178) match = re.match("([_a-zA-Z0-9\-]+)\s\(.+BaseAddress=(0x[0-9a-fA-F]+),\s+EntryPoint=(0x[0-9a-fA-F]+)\)", rptLine) if match is not None: + foundModHdr = True modName = match.group(1) if len(modName) == 36: modName = self.dictGuidNameXref[modName.upper()] @@ -340,13 +370,17 @@ class Symbols: self.dictModBase['%s:ENTRY' % modName] = int (match.group(3), 16) match = re.match("\(GUID=([A-Z0-9\-]+)\s+\.textbaseaddress=(0x[0-9a-fA-F]+)\s+\.databaseaddress=(0x[0-9a-fA-F]+)\)", rptLine) if match is not None: - modName = match.group(1) - if len(modName) == 36: - modName = self.dictGuidNameXref[modName.upper()] - self.dictModBase['%s:TEXT' % modName] = int (match.group(2), 16) - self.dictModBase['%s:DATA' % modName] = int (match.group(3), 16) + if foundModHdr: + foundModHdr = False + else: + modName = match.group(1) + if len(modName) == 36: + modName = self.dictGuidNameXref[modName.upper()] + self.dictModBase['%s:TEXT' % modName] = int (match.group(2), 16) + self.dictModBase['%s:DATA' % modName] = int (match.group(3), 16) else: # 0x00fff8016c __ModuleEntryPoint + foundModHdr = False match = re.match("^\s+(0x[a-z0-9]+)\s+([_a-zA-Z0-9]+)", rptLine) if match is not None: self.dictSymbolAddress["%s:%s"%(modName, match.group(2))] = match.group(1) @@ -509,10 +543,11 @@ class Symbols: if ':' in var: partList = var.split(':') - if len(partList) != 2: + lenList = len(partList) + if lenList != 2 and lenList != 3: raise Exception("Unrecognized expression %s" % var) - modName = partList[0] - modOff = partList[1] + modName = partList[lenList-2] + modOff = partList[lenList-1] if ('-' not in modName) and (modOff[0] in '0123456789'): # MOD: OFFSET var = self.getModGuid(modName) + ":" + modOff @@ -594,31 +629,21 @@ class Symbols: # retval value # def parseAndOr(self): - values = [self.parseMul()] + value = self.parseMul() op = None - value = 0xFFFFFFFF while True: self.skipSpace() char = self.getCurr() if char == '&': self.moveNext() - values.append(self.parseMul()) - op = char + value &= self.parseMul() elif char == '|': div_index = self.index self.moveNext() - values.append(self.parseMul()) - value = 0 - op = char + value |= self.parseMul() else: break - for each in values: - if op == '|': - value |= each - else: - value &= each - return value # @@ -706,11 +731,7 @@ class Symbols: # retval value # def getContent(self, value): - if (value >= self.fdBase) and (value < self.fdBase + self.fdSize): - value = value - self.fdBase - if value >= self.fdSize: - raise Exception("Invalid file offset 0x%08x !" % value) - return readDataFromFile (self.fdFile, value, 4) + return readDataFromFile (self.fdFile, self.toOffset(value), 4) # # Change value to address @@ -732,9 +753,18 @@ class Symbols: # retval value # def toOffset(self, value): - if value > self.fdBase: - value = value - self.fdBase - return value + offset = None + for fvInfo in self.fvList: + if (value >= fvInfo['Base']) and (value < fvInfo['Base'] + fvInfo['Size']): + offset = value - fvInfo['Base'] + fvInfo['Offset'] + if not offset: + if (value >= self.fdBase) and (value < self.fdBase + self.fdSize): + offset = value - self.fdBase + else: + offset = value + if offset >= self.fdSize: + raise Exception("Invalid file offset 0x%08x !" % value) + return offset # # Get GUID offset @@ -746,8 +776,15 @@ class Symbols: def getGuidOff(self, value): # GUID:Offset symbolName = value.split(':') - if len(symbolName) == 2 and self.dictFfsOffset.has_key(symbolName[0]): - value = (int(self.dictFfsOffset[symbolName[0]], 16) + int(symbolName[1], 16)) & 0xFFFFFFFF + if len(symbolName) == 3: + fvName = symbolName[0].upper() + keyName = '%s:%s' % (fvName, symbolName[1]) + offStr = symbolName[2] + elif len(symbolName) == 2: + keyName = symbolName[0] + offStr = symbolName[1] + if keyName in self.dictFfsOffset: + value = (int(self.dictFfsOffset[keyName], 16) + int(offStr, 16)) & 0xFFFFFFFF else: raise Exception("Unknown GUID %s !" % value) return value @@ -783,8 +820,7 @@ class Symbols: if isOffset: if self.synUsed: # Consider it as an address first - if (value >= self.fdBase) and (value < self.fdBase + self.fdSize): - value = value - self.fdBase + value = self.toOffset(value) if value & 0x80000000: # Consider it as a negative offset next offset = (~value & 0xFFFFFFFF) + 1 diff --git a/IntelFspPkg/Tools/UserManuals/GenCfgOptUserManual.docx b/IntelFspPkg/Tools/UserManuals/GenCfgOptUserManual.docx index 1cbc459eba96d17de31188f409bea2a40be2f2a2..c8766d5775158a3a6d17d9a0ffda598c8ea516ca 100644 GIT binary patch delta 20831 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 U3H0xDyksY&k_``WD-`$jKWoJ?jsO4v delta 16856 zcmajHV_+pgyYCxLY)@?4wylY6+gP!^V%s(+_QalvZ9AFdX5Mqo-us>p_tuwHy}J6T ze^po4@2Re;uHOJX{s~&|2Lm|de_6vN2Lb7W0R=$;0Ri!Fa5iN$bue+WGqZPP@U*i% z*W0k)8%z0VV7L`n6m4j}>n?j6zn-ZXXezz2>G~(^4?nLkGMi;&V|u9+z4ThmJtEUp zJ*So-*4UDO6oFZ;X|GFVl(DjSWTR=@1-5W0{ReuLLJp7M7Ps4}M=#)$@3z{8GG@I` zkWRgIl6>;UM|kU@4pEknp_36&<_G#8|E))`Ep9sbobvm(k=~EVEBn*G`J6c1{<Z}$ zf|Xk_KH()sc=2b91`<65tc^t%iGSdNsj!Fs^cdc=V8gHH-rZ6_zcE%F$Ygwe4;P+^ zB=Nl(d#t-CC(FW}YQqG?;1h0rhu9g5E4m=+_U3LJR0Q6@H4C(wJk39>dq%ZRzEx&y z*R#R=?L422E*8C)(Q}phDB6TRk}!I%^=zW2?lbVk;URcMex94fp9ypNv&=M9a611` zD6AXL_pm<I-pKpZV>%{&n2-OPGd)Tx*kuYQVh)QjmQNTeb88JyZLU+DjD;XE4b7f- zUm%5b?@*Z=W4of5*@160KQtVr8QyCU-fx=iYH?n>^1I=a+A13J5SE|z>TW_7iC()R zu%qP7-!eWYiT!jbzV4l<nUVkgxU|s*MKbj}GG>}EK;|>9CW|LT{`0+FjiF5^#r;fO zTF;sn+-G<A&SM&ItkTlDP{ZEQaHBP5*Aris8{AfBcU6Bs(jaZEF2ji1>l29-Y4P0O z?qnQ~*ZKX9U$>Yt_2CG<&~R9x)%|HwQkzn*H=e+I8f33heR5>8$Njyif9Zs4lR&Y? z0Pw<iW_+x<XT=BaOM4o6$<?}BfUlzwcNOA(F18%r)QSNB#MK0z?O>6S5IEAI2Dc;I zYl$Kc!mzdhhGY}=-<^+j6U%?^!Zo?uRbfChJZwgq%(66gT28;VKKy*kk>LMTkjX}( z>{X{*b$+)vgur&<wN@6nSWR_0w|W`KJyL+iz0;y<tr^UOU4Z+e>@U>W^D0j;Iys6% z3qG^^{lY7t_3y_qFTr%{vTLGMw`i-k;6t#}z69-)bbabRruf7#evSn_3As5;-Jjr9 zVsrA#&I(*jL^O&OQO@hV2>nQJ2C-RXCKx@(iAWdGGso5YqL(WEi17R1_Y~O=hkK3g z;WNe^yN`KWND&N(U<E07G{w>MSIcwH&9_xVlerB*ND5owtk7l03>iUENx3|ANT?G! ze(&e?h|srWqYPnF-FH_bjPEO=Ir!wbzm?<3R0|sTO1Wv+^22$!Ya-7cx>oEs>DXdM z*%$#dY=z-G%vgDQ3)1iHSaN$a%X#|EH69$?$$;h3q6mci3QS894&l59dK?Gpu3fFI zTH7PQepZVE&y@SoyVlm@Ov~_-+j%EmmK2cGOJY+L<80>jG4K_6^urUi*K4`0YKNDa zpR6J4?astVgwLnOU<Gw7akh?^QYbIC<+us62RtDPB#*LMoUqLe4S}F%A(M3BhqI~K z>fm<M>G91ga}xSytyA1w?<RGcK?^c&u15*rfp@*e-ov4{b?^qX;`dShSyVuL?Dhk^ zEan^@s}|aRY7mvZ=^A$dP=6a~y$WWR3SAR=1g~+5CLIDuf#g-E?XT`fK8TB6Gdl(j zOGPs~_{QraSr<1nco?VsSoqWu{}P=`cU@vkoN_LS31QQUg<7dqmGUHZPfNM#f~Nxz z<rS%?amBM?y77_wYd4fJK(*Ppz6v(GnMXtla#seX3)ibvlj=be)aLwiynvNyh|KwQ z`9Li?ncmj>#j*Xwl11cu(3{46Mvx<VW;7nb>cDbKq`RL#mNEmPvzZ0il;NaLOYAd# zd-u$J_V8m2v+ybXFa$LRl}XSaXu(c^(c{JY7?F3}swommx2z=TZ37|iCY;=H%O9u{ zomz)*)iWs_0G9AJai6E_O%4VW+si%O;Rj9gsA!vo&!Vwqhb0Xjk@Hx?bmB$V9*mg| z(JMxUW=U~@lh}9RSTOksDsihot>eaXK1l4-_^rI;qOp346H{MJTtf-6aVA~><Jm|I z;rbjGbmH%#!ZE&B_hZc!d-%?Hl>F#wLqmBKl4b^&^|F@&)132Bu}N=Jj)m|d`vc1> z!vf9Xf^i}Yr{bX*roY=Y(9t$*k;cXi0;q}CjK)KVjG13gKkn1zior(~$+ltK2a>Po zD`F+2jj1B=slTBWNo(8u7Hk%95xN`C!wt>Hn9qjOGEF-9!uOb9JCKJlAn(UM5g|u< zcR=d}o;My|kwr@>lFptqqO>-yP8*c0bGqOp`YYwJ-O2I7xkiVdB?I=g{>lSIS^@2K z>ogScNPQJf{PDxTM5RVXl*rxmBh+ngL{!Fmdrh+IyEMz05UL4iJn<)hK|+e40!Jz= z$Z?PmB^gePC>hIHP;BCx$R!%(q)ru4I$O@kbT$6VG>4@*I~7isViYRu+JiU^;hIr4 zgvjaLV?nDe)OU*>|Ha%_4wh|yyr(w7n753KKrYr|ieFF^P4rpej~3*JQkL^dv+o9m z&_WF2sq^Q>x;gt;PV$Zbw8RMVhwxt!xxDF>kru;pE{rU`cwL;Phv`A=39!;&mMoYC zunEG{jf5wAv#GY?b7&fmTtSP`4tS#))hQ^_@qu>VYL8GxXB`VrJk`1{>o*tc9g&Rv zEDhj#`ZA|4v<yL%uD@<V+3b1N?8o^#r@%Avl^@fh&xAP-_2M<axz+mpb@Ef2QxwYk z0z%dO;8kjocumDz!O>5OTBd!tk&+^*6LEfLqHPamvyhL8dS$)5_CZGa@Oj>3&E+K0 zm*{=D@tA?i<I3%Lq8oGl!-FayYjD1*=X;X1I-=rd6FsWIbsw`u`wohX1FOId2eiP> zw=$R%kF58_0J;yr9Y~$}G5JhtuQ_Ktm;*~;v8kJZ-nQiXwme)WfvRZR-)+eo*^<u! zgK-cmF1BOZ_h^pm_V4jh7(-2`iCjY)TK<Kl9SmIfOb3%A{1j%2{^e~UDm|Eu+KE<E zjF8FD;}AD`iU~>15JwF9-<)FZ+s~T{eAs^p#8AgAY(<0tlD)>Zj=kdWQFFWlu?(>v z$q<hzYbpm-+GIY9wlsA~p#tG_yPJLTY7-hxpE5qi@r%756Nf^N$Zz(qABGfS9a%uA z@Ckg)8D4AobAwHpgyKCoGyA?}*1)`%t+(@D5F4dL=9JQ3@ZH#9a%~iy>PN4@&jek& zTsk3}Z&VBceiQ^|h~4+Z7d4FQHrFYBN{T%r5I-8^9ky}0UFH4ZOcr&I7c<CY9(ir- zk3q`I^N0CknMff^EClt|bzvRbOaU`rGF=5iXE+5-Ds66y72GrBFY^;SS&vlIWROSt zM0Q@{3`VFm$T>Ms#M{tSQ-2mH)#F&1Oqycr4RTEac!cYV461@;O`jur7O-jOHV)iC z!grvb!AwNHY?0vK`7v7}3p%*jH$loq7Zq6a5GqOBhinM2u?~JK_){Z3kJ`C*&fGYe z!bIrxL#*2nm9#C~_dG><$e^bMrY(QQ`0~~GF1g?;3B!MHSg?=#-P6hY^>ObEY4l=C zcUaICpc*5-o9f32p^XAkh{Y8`J3vm0q=xex?}q@>8Ar93{%Zke$6TtJ)zME$8kGq} z-VoD^+Ce<jAXtjTi-$EYdfR>yvCcF4if=X@KA=AF_vNO27p<dmlhhTmdkV7XtQtGR z7`2d_zG(<7s(x^eqh0jsqu$-|`~9BuonE*n;4y*W@2c-xAOMtge9Y^({H4aPF`8%1 z?VTHcbB^$HV&d;KS$L@3?;gcj^Z){sc(>3naxr?C9+l?hN9g8~RTv_%z~m<_zD7hC zUk6yVr_gp`V%;@F`f%KCfdTtlP#CmUuVc_onm^Pm9xn^cYsb(gN^wXrBPV%l8VYcO zfRCZBb!U>vNI?Z^s5Q3|%~;F`Oiq+Q`U@x4$;(BaE_yRV>-CBat%)4kwan=Ken_@_ z_(`)}iW~X-EAEurczI^n^uY2zt2(eus&JyBHo`%-m7h{9tsC8z{(M*60s{O-h;I?? z&F&2jqb|&u2Zs%s!pe9G-cB6eR{lQU0J(Ha-7%)6;o@tQzwZx;*(l(0*Gjb4>?RDY zig05=*+>I~ckXA~dM=C#Ml48YxtN!lwbY_lsadcSgdHVG$L|g5bxjVOFtTxeeJepS z)O%X98uQnqoT1Aq7KV7zHzhQ<R)2+`N?^|o*$XgUCc{Ii7ezA02~TKMpXrpC20#a1 zn3j{;_7m<QKg8l9NRt7avl}9!`%gonVTK4%t_>lewbCh^<3M(KbUgYUM-uA<<gH$1 z?v<-Y>Gz<1>1N>PnFB0qydO(~l~AhPh9b?yM)0a}!R7^A;tm@Cex$-gHAb*tPS@e! zzIdP9+;q`6oZ-I3QqyZ;49c;t0HJ;ET_Dy?A}g`hBloUoL%s+KVUFX`ccBUhAy~cz zLM+S}Xt@MSr54`HLeoHz`mue~i!1~Xp#8FhIQ|AxXBZ1LNjb$IJ;7a<EB@)kO%*XS zh!4@yckkm;<Ck!ef6uedU;$F>VLWwrQvrQ+JVSTUSwVCB#>wS*3y=%&qC)s)Kgc_v zudhhvgv-5~acif6iBhQAre6#97EGm(Hz}w$y{(=c)9D^8T|9ptBAp!&)pq{*J6jR) z&A#TfQd<imAPS$F`drl0FF6TblDBl-D2xLe*=aa8nT$9qY0C0w;8#}SHLS23o5$Mu zU#VbHuI$pEBFgZO!aJn^f9Fy)u6VwpUV2hO)LTi4EGl+iJ&-c1`RCHRg|(9x@x=Ia zC%8534@zgQuDb>Cqxa&MN0YPr``rub6w~MDkKOmdx#3Yv!&2MR+`}K%r1|I0BoBNQ za##CLs)+;IvrX_Ba)g@o-aM3cr*xVj>*9%wtHP0ri@>4~r4%>-4W233B`Sv!v#{xR zZ?qs;vMeVG^4p>}t5%wzb#rt20}#9v4<4T`a_O>?UAH3S!?6>DhG4SqB`)?@bKO5` z*b=WPAP7h*i1Z@(X{@=mA3bB<c|Z=%OyGZ)aYQl~<8sttfD!D6SVSnVD^OY`e5jZp z#|r9`zIfyRV%f+CbeviJB$JLdqQaphMvH8W%upFrI};_htpd>xYuP!fc#zw(@$k^` zp&v)UqISykjoQIY?Br!+IE?6!YP8;ZJ(PdLqxD(aSMo6@&a|JB8>5$y3e7})b!%NC zr)Zv^ICm#~As(%HR533vGsd0aO8&)HdwxL>5o174{F1K=IGD`Gk}^|}s%7`+;+5`S zEWAbsAmh<ZFitGmy~a?xX?%+PCRx6!AM<Z9u-6zeUm?f!v2ctQdvhr=TxZ5?IlQ+n zUC4<kU0&N-!!nc8z>D=__5%TtKTL%XTx0=l79?ZaLWW_AHjsZU=E30{0m>rr;;EP) zxJ&RrTW7ri(%4^IdQJTQu43tc6VF;^$u?G)dC?gs4lRs84SM9<AY<9#6?Ww{ze0eL z7338JDhL0P^$p}NU~*_8ab_jprv9Z%rJ?b6(yHyvB}@q}gOHh1JG)6KdcCFO(S9M9 z4kK#s!P+qih(T(pSG)AMPq8AYa5ln8p(5}oJjGZ7pyUml#GD+rTZT10?(`?FVNfY6 zsFHSy#z<32*$6B%X@cGs(!u!-IaZ9CDr+vg8fKe1VQ*K~s3F5Ub&KlfM#=&A!fKoz zspYU)6m?6}t%Gq01+HPL+jz%x7u{APC=#Y(1buUiE^x=SO$Kw@ZFf^Bp%63BPRSX| z#~g|aAjv43on&32(#KGUyIUiO<(G72NeLe+LaABEm9a{~xZY!u>Z$E`{#^qqal<3b zjoLU4|A^W}bMlT>ir_gQ&mn3&cxH66ca|T@?>n%dG!ukA4$NL_RT9U<cFm=xL@x}> zuL%X6Zc8KG!R)h^=yQ-bAV1#pMY0D!xH<X-(8Frii+ifJlbJgZ`YpL|RUhAK1v7_o zR+@Xp4=qw6@U}YjjitXtsT$z*B$*gb6|(=-`5ogoh=^R&v5QK;Yxeq`Z6ZWiM)Yye zSo30+;ygX6dep=g`|$5m_aV?@&jg75ONh5w!NgqZztf$ol1F+~MH(<2;H)B7w;h@( zfEN3bHMV8rIl?2930bnns6UudcP&>i#ImiBNc^MC+d1rG-$ouIQ81ngjxlXa(&0@R zsAxr2W?5-XQ1{*$E0cqM3#bXkwARJL{3!{ugo-b{<)$gk6x&miODE;4*DyT3h1(~p zlw>hjN{w~eI79FG1GCCC9f;DfTADb{2B?0|n3}T~Mh`WWHcq50ph_P+Ghq7wK}pMO z>Dc!>SDRB;_`A^@i}&%A9gPIu_3=^I!N>Hg_2E|>ps|_8P+`4Hle+m8_w)I!@l#6! ztDkc8$eO#3l3~TT@_p}W8K8YIvx={Z2~gX2usiW}oAXX%E1$L`g8F2#s&|@$2Nc#; z>8IY{NmVhU{w$A<wQriYik>Kl*BfdKZ^RkC3n_0DyXo7SW{S{~KB~x}6suTTQlLqZ z)R6Mn;ZDDpT!IYn@?TgjWFS^4wOVH?uLrg~OWOFALRVJN48|yyaqC<2Rh-Nd_8z$M zW7oVJ^01WYt1NUGnmB=T>vP>L19YnugA?z~+<)3;K8!kWPep%AX`+11)gTYW2eGrW zCC(I=Ury4LO<1RKF2HQ$xPa?3mGuxJ9A=4(_C#gu{oLd^7-*mOm@WM{iMe~bG7y+c z^y3of;L>EE7(&B*{HfvZS(rR_>wFLycbAcdLrkn+`_Xjm{E(5RUN~aX3RwDqw){K0 z4$^ziGtdbT{ILYobYJdXe#eO`igV$cO%y<!{AK$w1LXHHD?%J-QZ&i^W>YJ+h$X+x zhlokLyZ>+Sz(1F0Cck-$hJUoj<~4Q3Kzn`&uux0zG%R9a#%EvFq?nl)6TMKa*IoC_ z?{p?Uch|Tjg+$rnch@upAPc%IEvbnME!YYls!p^24OaW{I;u3p+Z$k7)uc*KIjYGb zlUcb$#cD93-BZn39vbPlc$$y$@N51Z5$p^Tv|pNe9AnJ~1-f0Yyv7wIM8_tkEL1<c zra?Ssj4egXh?<i{+!md8w<)(}G?vD%uG3|fg`(la!;?q#sD(}%V63xut~_B2ESnl1 zNVlHkJ1DRsm8r2K5qf1C{ie`4EzlKFKc1nb_7T{aMm-~+OCXVvGuA2z#s!-}63*ub zrv8Wvt5f=M-dl;+jp{?>0`jqkO?mCKBx2hvp$waihOMjzIt>(6jx@!(4?v7pez|i^ zkwN+8(DLm<NsuEQpo^d^>25kog)V31A*~En8w46*ummH{yar0syt^vWp-O9owm)gA zXc5t~FvQK-Mc%*1W??N`>K~GnJ$~fv?Tj^P%h}j!l%W`s6b>>)FsG=Lgr0`?gUyQb zs=DI-MB-*aKv<eGTP(rUi@Zf&^}7xr8`8iGP}jv6HC71^P{<g2@eP{injRB!5ppQq zLLV1@z95*?&7gNKv4xE#soCuP&9%%&zhCvh8~za=aoxr+`(SE0o#hz9_HJ!r-CWpz zLMFk#WGfy#O5y!+UuYKzB>SkY0W+!uLbWau0xPBEC1m1HJW{`3pQ&x=G~c0=&_9Q0 z^LTnkPbu30%DvJ>wdY|;@0Oc>j$MWi$KvNwu`86si5v-1aJ84@o#Y>yu|LtaM2)#Z z9tyvi6dE!0du?)khTC4u-Z+k+#>wOVp_+~F;b>Y2GE^-izzl~p6Ku(^!|_=^o<3=n z^<GKwx1i=+FO<>@hAYxylR1DJL+Gu<Or}C6Ml$XJ42Nm<Lv3xt(Wcp>)?v~Nl65O+ z?Zc8f?Hl3RxN-5$Sob9H(O)I=)?~Vt*mJYE%>Qx8DStQ<bF=AsgTluEFr2#As8Py% zhPW!RV5tV_^$yBv)!OggmW;_!>881Q*(Q;X<XGLRF4pd_A5#u!Rg;3C_Q|mktf-|c zncf=!PQF#$`DxG`=EuJ|C00yfXpXAIu;s}WUx(5xsl_pLUWI@^DALX04rkC0Ylwj4 zu^V`79-$es`p%0P#oMdatlk^CB5FGwm_nJjTX=@_Z91xS`*}&5xxb8wmjgkd658Xo zo=O>wp(KYR3%h@z!klw_8yF+UvYRhDvwD{YAi9ID+Ea-3Pkg((HFL30+468=P72|B zdtkJvqjgILDG9EthW%J=oYvO>(yHq+U!WEo2sS-QV;0vy_(g6up|?}&TO<GFASbXt z4do7)aZJ0esz}(=NTI?KZKqJn4nx|Vi>_AI+=X@XQ1cpU9!u=HIBDiu-4S7s9_)?) z7|xaXny9cblMx(<FqVRUM1~Sh3IH90Lo-s%aV}9c9S?U)<u(_KD;O-dWVPC0R38fm z=arcP2M}w}j>2dJ3vjH!M`I}v-$UE#B5Bt3d&rv}<w)Z3>SbT@!K6_RITX-CZN3*o zF#X`Fh66zcHKL~)krFYQ%zUQW?avPZC_a9Z206&E!KZwU@caZ{66ieDS+KQnjc3hL zZ@JlOfqAX$G+3L9ZsGjXwQ)%M_?gD#K&@cz^ZOS+n~+gp5G^#2WD??7eC6Ry4UAqH z3|g>?<$>#^i<4HRt^umONJ&Iu9XXV$-WIM$FdCX38}7Q9_F0Z*QJ%EmxO-?FFf=hn zEg*y9>zzvSx^~ThTtPo6j@^TFw=P_-o1Z6FLl!_!Or+A|ORBSpP9CY=l>urKrep2B zC&3Xd*6wrY)hwo&Gp+NhFeaU0B^+1zzUvT4hzfbv%g*N>QoU)Z=k@}7Nen_WRcX-C z|DbSMu@EK-U!evZF9t5A1uB9a;B(uc>47txU-!_w=7dCBmcN_I&n)f3d)iT3%aiXC zCNG%TYdy<MyIQTcZ?H`S*&IhM;C$@bF*r}2q*ks5+^VzmXgr!GkBaG2i-!1y{`7Lv z+au_&H`mg(Xw3~n=PeLNA$7CYABfW#*icJi)x*#K8r%xT4z}JxF_(i1Ac*M}i|Ig- z7XgcqtZ)r0{n1>2wSr17<@&iw2tm(y>_@XG#jIXXDK9JhZQ2|~;s@%7Vi_$L!4FuL zveU7n0cr*mD@*`OXECW%KJ~V|bVubNtN40}Z-*Pwotlz;$*L%9z|f{&vU9YS%f+ps zVgwE4lt+#?byIPS{<ujN;L4-QeC{-Nb0Nc|iCi*>RzhoJ4lVmF8|5wHkA>u$8fB02 z??(Ug>=oIA3mNIQ88+Y^@o(L(Hy<Bf>9Chh4lHRdyXBUfI&<e5Lt94hh$o(8Oc4ss zm?;0>Hq7t_`oGgtB-k2i6I?`KZmHqICYnoW$5<2K=`x5w<AiQ*0GH1&LlI6OUWHgi z;;20NMTmoaq!1l$uF6Bm^HAN)DrDO_#*MyqPmx@T-=?rYup#A}&=vV$7GmfV83lhA zOj72g$cxbKy-Y4dEq+g0@*3h+IW>kwT_&{XNFp(IdXi+X@tfKIjr)v1m(OR_l*&VJ zZM{I2dSBkO(B1wr0`PrQ24ksNyZdGW;u7@Gx*>Nh-q13Pqy&>W<En;@yAl#NSn+#W zgbSZ+>3m&1RIH+zdHQ?u7)Li}_xQZ^vU6&;wTu_qw7y&Kl98hudsG&snp<X$>_t~5 z$v`|5l>c#%m!7!q&1h42dBlUTz1$l*@BtzXSr4@tVj^`07vQdYyq@6h+2OSB3BLP; zt822;cgh<(ey0P*f6!)8N1UdXD<IT0RG+|xNQc<(`fBgXGR^~Mp5BZ7$l{2<-<LEj zybM`~$ipCv;cEd5R%ij`Af24O3l6Xa17U2U(+x5@-kE(&<P9tXRp|12y%HlEag-J1 zJO_yD6k~Iu0~X)sUD>R%M-PjeND3h9IVkQzBAmNgO4ZV5FBBv2bhu2f%jnN^Xny=u z|5(59*XU#W1*PHU)eBRSnk_Wd?;7$Pz^X}bkv=SpP3AX$o7wp0H|B{sO7_SYjFr}J z!9PJs7aY@4<>6}*MZ+wWw^5AiJWA-fPrpZ@?r;890QfkF;Hu~w@N8*Qy*Q_A4^F_$ zh^Eey?j5WW*Vo36p)!=O1F5$q%e3NHV>I;MA;JZdT>R<HrAis7=<`A^V53MjC0AdP zi|Of%*|YxBnU?enG|8goJqC)4;Ldf%BF0uL4Ncjye>8@a7q++F0j|mBO*I<mYIdBk z8)}%G2>2Tr%~Gtn^E+0TrxCv%+mUIGggYOV!o{$4;0IEy1T}Xs5?hnZipW{z3};T8 z#cyMjOvN|=yJM;W>UCx+m#0H12n1*><;}sXE(dtlN~2>%GOFk(E}Ujc{Ai=H4=H%= zcd4d0Wg_Z^&iy#tdG<LT(`$kzD3$p#XH?<acmNX_@}Xtf@_NR_7_zmDzAtK=B8tp3 zo|W19Cu>63ZL}r+Qm?CTP>{jnMg;Lr+Z0-h5FfMxu;DHMvWy{Y86&&Q0&BF4@;ll_ z)iK@G%HvZKf1FL+NOh`FsMgo8P;3cF^isZ_)cal}Ld*LQm3pe*0@hl%!&npp-Thn_ z6d(aU#^x$OD+m2ub4!E#>FT?>lf>rfo&vAYYYuOLl(4VV<Gyr+H0rk?m{Ws^hdp<T z$;!nnhq$=5YU{t<Uh5pvfo_L%;@!I_#6Mh~DoH~{<lH!H>;!qplz?X3rNiAKzRs?* z*aQ(8J+?hIc5_y^DX$9ZfAwmvu67;J?f{HG0^g>3{%Wf@-zYpnoaRYIOZ}bBBth+B z;c<9i9l8_(Kh^u$)W2^dL||43;uEoLxPl1fl|NE^I!Tv9_DgnoxBv1|p>25XS34-* z;K^K^a};pZR8AZ;h<tT+wrx7D{=`V+;S-a6MPRk=qGAGVe+BtnxUSNUm}xmdi3Nbc zD@p>z-Bd0!){r|kFl~Mtszo0TZcEs2pd1sGyNI7pn~FswH4OR(%(EOXgC#bUY)n^< zSnCzv{3p6yadlZWz5r#@hHzH|abQR@@T^pz%O$C2;S&x%(dEe+n{e=Q)?hN~Kp!F~ zHhXY>>>>CX!R)2XBMJoycao;KvlCzp>_F5MwlVHcTUlhruEvw4V}VUyX{gCxameG` z>LKJ+=*?ZzB}(;_=s#1<(l36^(UHNEuig%)R@m}Y|FdR8tNEa?U-=PvPxZjthF9`y zQ?uTo*U5YHGTUZem~_f~`@0{LY#23RDP(71ODdz2SF#uC%Hi;|ilPh;7Z$*;9UEfD zvKW*<#gc?um!FD|jaCFPt5-gW?GG4k>fz|PlB0=52*1h?8=1<!5oPyv);ZF+fSH!` zm9lR~A2rv12k4JHww_zQAw=~*MYt!C^QsHyp5J+yFar`{?|&9&6Myuz?P53C0^y+T z{T$Sv9((_^*2Acr?tq-qyS@SZ#O@QE=RCX#=2!9uTrpAm1!l1x+=gqs4+FS@JEHr7 zkEYVteD-|(Ek4Pi+fI;Fh%-RZC&nz_V}y&Ns;8J^RSvf>t@(S?LiO`-9^~Ffp7L)U zmac^^;i}<s8`*TK2pY-B2CJ2kN7Oo2Pff5m%mXo#waHU;YdZBBZodPTGB(R&SZ-}H zv<d~M8-4W4r()tQn)Y>zs2b#!iGF8m8Do-VHzyfgbgi<0KgygJc`aq_Zuk^SnJ_3H z)k-Q@5q2G1H2U*xX?*V)-<ifPdJWPy?yV+BoTFoSCo)O?V8-jtC!IdYJ@(K%F2?H$ zN=-hNHovcXoVd_#^dJTNmH&$fP}MJF@7~X{$wr(FHKH&w4GLXA>4fHeO|&`p>POgp z1`Ya4;1B$r&xL3AMyA1DFTqs3&TQO~yf|p~bRm!<!rYa32YLer&o*zgYj)%^gEK0< zNeUl9y}rj3_44yz@Mx!Yh$9)t!k6S>yDiy+s3@$_22V09vTp+r3vSykBuQ=e-DE$L zkn1=#C7<e;skGC$AHr<V*LGngB+ZH7Dc-%Z>3i6T*W%UsFdv8IME1-hG})1yJ#7@I z@~pu60shJw-G<ZOX$qd)u_H1!YxyOYx)VmWD;C|{n&m`<JsMFRs?4$wo<V^{Pgpi) z6ch6z0d<vh`5Z04`2mO0eef*8$jh%zI$RFoD>r=67fzP461sz3UZ$)opI_YYSu~1h zP>0_~wcQhHF>)Uu(?Xds0ba{kV5R=cSoJF|Nu8<3(O{Pgciwc`F(+R90(GN9;99K& ztRiO&LRoJdAm4kZ5M|a5(H#=)PZ!u-Z@+!+`?7%qz|HFcSOmi26pE*~{~~sM;_~Fo zL+2kOXDy>N^y;#lQSgFU3BBi%!a)ZYZiworNITq27tkxDE-Ep{*@I%Jc3mH}wjh87 z{Zfm7d`2hUg-{+hA*J3zrTUIf#VyZ{%H3?k`9li(n#Kd-3Z8N+WDK-{Q7_+Nfn;0l zA-~nb6awb~z$>TYMGeV<$*ueYx$$QCg4@3siXe8O2B0}Y^);Xxr+US|Jn9#2`GaW& zb=#+u5fxNeZAdVKGZbE;ox(V63%YhwWXOj=O+NhE`jn$iui@T`d@H)6g4cr*2QCNR z7|>cnVV*%)R1{N>d+__@426YVa{`xV5~8_98rhf<Ab(!<1Q6$RK$K0fc59QXKoNcK z@d)VUE;9Fhm@dV1N+qkkijr(+ZOTg%jzQmAb6~c7xJI_!3kia@v_ys|$H{!}&K!DI z^{>V9T#ZtWcch`6cOcvNw@B&fJF)IMvG3!rVVLspk{myAb!PjpRmR|t<j+1@G%!-! zPeu@+qcKL4AOD|k01fWLKmte*5Pc+&|6YjYV&>{<WpCl~Zy{E*hE9AI7rMWJ)u-st z-86)4Jt(`X6HHeP&xQ5E2MAO)1O^k!bn${g@2D;X{*;xvU5g^~k^jqi_TA}2TB{bn z6#ru7AFj=svuaa~%F4?)+@pojT?*G9!6<s<Umd}C>>U-<(Zj}7Z%^k?Fx$7GFpTu$ zuy%UR>G5;{{krg5G9BjiN`3b}KB^5^@VwV{`_K>HO9z>y$AQJS&WfqM%ytGBb5`rU zY&WbK3j5!ki`Gg|ChEp+T#F^y08E6xtp1c!x+zCL+|BRXmm34I)i|?H{WYswK9{~1 zdjhm$_}-}^!&|lX8FJr9$GJD6=a+yZGa(ij-Ze7Z@U!`?8S{4H8~hnJ?^t2EGJbF+ z`iehSmeI?7E<HOX1lSnw6YUlCd^N6GW>vF5K{%pXW47>kONM`fD(UfUb%2ZcsaNXh z?dqSRW<ogL>sr|1B*gHX$1tI;iZW)I4*@LJq(Da)Zj_@j6Iz@oCdyUEgSj{li&n2< zPKA$!g??rKqL>8E7P6^ac`FCC$hk>WnI~2)`k1i@RfLf!2+O6n?8udEgaBrb#o)B$ z7I7AyDw8-Gn`#ZvPrE`}VNH^i;+dh=6NBC=&tycbW7jiAB`}fSYd>g!wcb7iasY%j zQ?W-zvFjobQwx4Tz@NW24QZk1hwx^vdD*c!RGFQ<buB+$<uyP#{Fm0etqScZ`TUpa z@jjF%=<FOf0=`vKm1=7?(2kZ|avV@mI(CdIA@0$w7UE3@6Af_@lyKiD#m!V;<>O(Z z!?#F8$nr;>An(4w)DBwpqp4B44gw%zV?+4HSmeSx?UM`rscwMuwZMd+UmCk!SiYDL z=u0=;k=LCsI;jv4(ey<2VkFt3gXj-;ejyM#hfd8rG2gU8v`s*bsH2ca_~P@Go5tWs z(H#;f_bh9)B%~u=L60d2qC6G4i-kzHlO6t|SK&IsM!Cnm1?KJfr=K~mYXijef=F^H z{e|bIZ|IBQYY-W`$%MO*y#<htJ|mAj;r#M2Ep3VKT!MZ=8A(Oq)wqeJhig#6u4flD zh=Y>ru;oQ`(23e0WB3ovb&_8Pp&xH3(0NiXFNC;o_PLQL1mi7@|0NMNk*!d~*3Mb9 zun&8c23KHhUOq`h#S-^DoCnwikT4hV6_n>yU$eZcPwVL;Hw$`=c?OK6mg(mfvX8+D zA}FcZ!BJ5~4F$cy_x6kL+PO5lh)V>axBcjoWj^f_+<OPCj<^^9)!FLFRB#{DY*`3M zigd5DY3OVdWsIS*>t4xQ=+t@kff4O}Pi^A8!aAIq8p76|V}q14f&-Yq*ThzNAG>&A zPjNhn5Z1X{z{IK;?fSf#PAVj*d{!)V<aO&cAD8VIIUT)x*w$VC>gv=x9`x$fY39M! z?KR5jIvosd{kF1lt7{DYHq8~%gSO$6fnw@$XJrviLj4U0-?GYOpvbxAQ-!c6S>)j^ zpY&9CW%L2j@ptYImps6a4`-$l=eg2Q@4MimB+K2{STN%xuArC|5c1^35VDk1YAT#I zmVwQ?q@RUp{6#~%em00UmYz*>h2aJ!Ob`M1Y?1DmR#ZZ$woo2j{h=L!djx4kGvJ8Y z6Y}GaFg^tnMtq2uGvN=UhIcTVfNE=S9ZLg^nrASbK-rNehg4Dfp(36vi^k<7jLSS_ zjTEL?aEJ3vR<mP@YK$3^j-eDf8=>RNh7E(Kpg+{tr3Ake9_vN0ONbAgtUpbfef46l zJ|KZk#^{g~xO6PCKn@vp=usIyb(T1MN}!7j6`<`&aG=j?SDMDO*^#ntq;`;0@s-3| zv;E0qh0;Nna!PZ*Nl3zX=jZjJ-{;q&C4V0Q#iq1?GDPf%`MaZ4o%Src=o!hP#Xr{x zC3PY@<SZ#?=P8tfiMn;6qmnZ$H<AlCrZIxUk@udNV86+ub2U#f4+l!waqgh&msw!6 zQUO1c#s{fP$4v<G0v^)MjLE?gdjc+Qw5%i;*7+i*;ldSSM6D+}+{>S-EXZQ19=A_3 zH0CEo3Ctq6WIRCx#;7(TJaI=FmWhVsPv#d2h;j$=eJ{ClvpKHi#**=L$<J|A8rWBv z+ANrT`KjUWdCK4Io3z`wa0p>b$0Asad;$6G_0d}1s4OTrtfTbuZcavm_Z3%)k-tp# z6zE>Hx*`c;>09XO`Ec`grmMnIwQ($MHx9yv*WE;}-VceBV00k2=kE3wl`&3!f>&Ob zcnq)5AvLP$QEVmcHntDM+u)d0-1&a_)DgV-$n~hOOwTVQGNEuDT(V8kYV|#woPg7c z9*=IeD$O?{-<>#g<5$L2U*rCP9EHh5*@OOR&A!5L=NL)Kr^!l6xE1)CzRLl#@$p*` z99*OH0}JwXPfE{r5e?(R{%LT$)@iN;<ofghn2?D}SHzf7#-B&lPFlI+U=Ro(noD%L z%RlhiVbZNDaBwegA8PIiI(mO?j{pY5>9D(dUs9nnKw=rz+Z-?`;2FHA1mL!gDuvNa zp5`Q;-|G<KFI6ucMnBsnycy`cS>jn7KW*dGk&8b@2?^pc<bzHkD&PU?XaR#n_!xMu zR9HF=_d*C5P@mMqlsw@+${g7hsfYQWP=QRR-JG*(;%W#W7uqj8XAZl~y@1G-zw(AR zk2=v_0LE>i1y0gV4jOf7MQaLvJ>Mm_^d5etMr(<pC>{&8s8Sb+B8jhvOyO4q>??x0 z$z7DVHH{PNN|3$7OjhfSG3CfEh5M#p3VJ_|SekWT+*!E9wWT>gfQspWx1@PyPe?Lp z_H)9rmL!KjF?EtI@f6U$)<80wv$V{g2^0*w2`d3pIM5T~Py0~7_sdp{4A`@QTC~yg zjZMEcY-|hlgG(RfWV!a*UB4mZDZ;$#m9eN$#L1a{kUOjKTs1|^hE<=ufZ(;!x_?ZW zeRL3yvT>}>%4-RkH_J{QY)b94<@^YJBSD%hA<mYVX3Ntl?tuCh$UvZ9s0zeYAOIvj z!hD>KxE1?V#}B@=m}k;HYY!9K)pa~<_9O?&Eqf_l1}b=`>s%Rv3&Zt$qv5`hC4C)j z<G;Ex@FDrT6fIV`F|_ho2O8%f#s+1@5=6VHST}PvM2ztWvsYI@{t&Ef0coVzxO$f^ zXAHE<0&Q&OwsE&V(wc-7f+NW(0DO*_E&GM#wmK4E98d(+$*Nq=&YIKn#z*~F1YUYz za$l}I(*?Y`3Bl$2*UW;Imk9ix;VY$_7k=fGgrl?3=Fo$mjQjP=#m1aN$erFW|EDw; zpyhn?QQPuS9xTc&DANxZV4Rbq&B*!{Vb|OuGi-5<eZ*7eUZS8n`>6#9V4(yR5gSb4 z1hvGzr2s(?-Gs>h*xx{4!Sh0^ukPq~v`*=U5qgf8s(bxbF0%<iWMe;TtKn;*_Q>Dt z{G9d&bS&%3em;d4no@Psg2W~So$pX7s<md=DYWMQ+p5u676==>7h1q8(1UZ#abLc# z@$lOqphEpCTdjp)(8?rEP-w!2ov@z$*shN<ASdWBkFfkLMZTs*nMC<vf=pkdf)c{y zmr^*w2I?>{xadC=<zGgT=a}~8LB5B5$^4gLxuBOvFudfK5F2}@Y;AG}umpB~?0lN9 zjH^407`^t)-%s}Cg&Gu;fZZ@u%Yd~Cl*%1$uvE98FVWpFnwpF~<5?gf@py2iX#Xrn z{D0NU^1Wd|<yY>FhyPUj?El}Y{~26{sWif=BsKh3wM2BkHouY&NXCH-=$Qgbjm7}~ zTmPxz<_6JVA}8o%CN6y0k1q+P@<{)u#+9zt2HpzyS5ztK#fAK4@_nlU;koRjQ8DVF zuXzpbAk;#;-!2kz1I$7&Bj^u=5pIH$;}I{0FSTR?6AFfCkWmdz{QKKnX+%QS-UE`h zFP~(OnlSs=4vnNQ06)FJPYWt_p%0{GxFGz^8JVTrcW<Yh%@80&WLDIr0+i6a!GrQe zmWwE8bCCEV3M-AVKkaq$cW3eiA0{nE_SS~)y}h-evu91i(Z01py99X`(Omxk3Gt-; z)>yKY0$Z{31LEq(6)qI@vI)2TfUdn}A(lVw8C4#2Gya9L0^q3WjN5uxsi#E5cc1SA z_W8J|vmNrUMwCW{azqW6r6DmwnZtFwL8zf^_1^@qQZ|z|Md1^u%~>#JNYLNB@2gd< zCICXEKNVSoaFDqZ?{VAq7Kc9^%fi}%x>EGtWbqTr^daecvU7TV`bT#Wws?>A=erE1 z5w;DUUIin+gad5wZN>YZO7SY7pB>>zMiX|LN|N}bNbZE(Jo2;QltaB{5XdpC@vGRE z3%mRBvmTVGm|Blq|E^E_^f4`7dXz4HgYuPNAL#cx-X`@v$V3bRLDoCHMYc4;IIavL zv?pmv%!7l>r-1*2S}5KAAV&&fgtuV=I=vNv8dTXqhY?Yj@R=>i)tIJ^*ZcVgk%asJ zudi0OxG#Y%^y?|&)4c)Cs9*xh!g}~ZOSEXFYZq=2otDnp+E3)N>FbsvoUa7>${*)U zY|Kt@yNV4;20x`M4Qb0A^q$O{vVMg7Xy7a3E`@nC!^5bmP7-g{a_k;1L%E?&uTRY` zdHVDBEPDKQom@>)g92N-s_0m))7~VHL&|>*{}e5VQb7ZEH-D%jhMnVC@14Cx->6W4 zKAhuvOsFCFuE3DV5tnI0bx^)WzJtfE72v*clt<i2u<*%e-+3s|^!`>}PnLVX0r)qj z1ZLSHHub4KgF;&A%j=B+d(>&6_9)6Ob9GOFSL*lxM6fB&A{;+bUSGmbPXzFzjM2!1 zxgq@Svo6GVzLV%<r(J<{#dv{sg`~zYi@=!YA-}!Ix6!gKz}vHPYOw>TA|4YmxkUk| zQ<_yL&)Ea2!?<W_H;|T~e=0OaG<Cr2eHBjYy;&dMi$%9V?Uy*weA{OImY@?WEAw!a zE?o%tXk9Td?gu}hccAaf#W?@k8o-<dRqx3dZ1RLwoz2)o`_u4@NPf}G7t11T4Z~l~ zESX|@5=WMg_|izaUx=!2Iqi-Lg<*Y%PI?Bd-+fcJvdn$+CaKx*Lgabif_iA5a@E(o ze+VD(?w@<H{N-MZ`o>5~A~h9KqOHJB^<NH|-OQcq;s9kdh(NJ{HX4-N6b#1w+k+2! zbf)m)JWh&=Uk<{wC)H2rtt0DRQxl{)mFM>>@^}K&ONWtO#fE3r=v-Y%>UIFj2yjwE z5zEPkF<|A7x!8(C0nLfDd`Ve!T{~jY0+3m=9^kIhbo@YDaIqBuv0`{-&Qk$XGR{RE zPRZ`3<?v4XG|clWEzI#d_nfbN;vp(TXFDJ=PCcr!tBM&m1w;nAQ*Sjq;2$s#jU5aQ z(sz69#i4#vKG)%{1U@zhJRYrNTGejLR*C-BH#zI^p$5lcTo(Qb_5H-iwkH??f*#@w zAa8pPlrsK0Y&%}ye&TyRl&DZW)3x<n1RLhT{|?EC%a3_hoNVb}h?3|QB%>(`SM9EA z18OSYDfrx^)El|R0d8vLvTVCPU6hFtfGNf!3NV;#eu3w&-r=K}aDxy`BU>*hh_!Yn zz@V?9+_<81Ua!5;0%<u5z!bXBE`S6m$@?wldC4gj2RA#!VQMZn+je57;%8)xF8{I( zIM)`E^aDD6RIc?rv?yI<L7<;OsJ~Hsfrz(6-~5#Vw5sW+bYJDk_Xlno=Sq2{Wxpn9 zQw6Y)h)!Y$FeT2fp8fP6hAzgtaEf_vDGaq-f_D~4zQ}WplJ$?DQk#JP1<<s==E+a| z&5fUvc2NHdMqiYrT3)LlbpXQ@Fjj>jrxSI9sJ8z?<|4w^_5XkNt55ntF#dzBRmVYv z+Y!Cb$?^Yi`3rkrTvjDfbCDu9)S@g0gqVE+J2ZY3T{J9o*bYtYOLRmz?iYgH{*nJb z8Rm9p33q-8NoD86R-pf)@>nJTyi(#<!IszSf!$85AI|@%O!?0pKtDTAhF-eH1D)g% z{?hzK-RiXE{}2ulvN7_XVX^-%fX}TxP(m9CVB^gsB>s>3F8{Rh-~QiP|Cu?1NU4Wk zLaF~(a%4<%o3G?Mq);F`42{yN|E0Nw^2$8n@~c2*QwU+I(f>eP3I(o4_K*3T<xjRd ze#*CJ6~UP#`9CcZdY6R0;VqLz!UEDEpMx1a;YnyW-`%BT`}joQhB9yS!#s%(fTBJP zK5D8&rc}&GAwwFSTi5hCLC8jIn?+>ppYA8Vb71Kcd~_8ToDM?#YRPZ~e9|!CeKYrV zkV!e}ALbp6-@u4V&L+r&s1m!r2`f>YDFaicp$Np3)j485+oqIm91`|kUI46L+q`_a z@ChuyDB0GwLjtD;@bWjyp}SE)h&QtXZBMdPh}GAlwl_}o_K1ePCq`Y;C&{B91{_TB zUsy9*iv%WiJlXJze!ws3&<s&dYqSN#3sgqq&H~6iQ?Nr<vx5ha=N;ccKVMQ{MqYm- zbtt5mpC59M{q4imCMqQo@&SYs@-Dfb=kZ(fy4|^y_NiuUp|0CatwUcfUQ0M~uGj!7 zNN*Rb@;lOsfKLmm>wn9aL=nH`;QoAX$NBL|7&69K%Cj02a3w0Bf_%M$Efz;SXdzGT zl^}K)cxtc6ZBYn&my0Vyv&e&KQ!U{YD6DZts%mIEee&8-^AuoIzR7Z>R)O&N9Ah2Q z^ETTrc|W0!4*^Qlxwua}JHR)q4hhhev?dKk#)wu$3;UdC{_5Apf$4e*0M_9(Gwn^1 zTIav!oH>C_J1wZnm+Mg2OAOxCHFSO^S@+Sd&w|oswvZ1sXPdU}3#X1(m88}szpvv` ziH)pfxQG4>OXzOA<|_<j{K4rwjV~LN^(#|oi|NvLBTD#7kQKdAd0@BO4nog8cB`&z zZQM6qO?H}Lc}dyK*Yco7$pUd_iRUvWfrLPbC65a=#h&aud!Frxm`Nwpb$Q2#tQUO( zz~|6&$<zAKQ%grvygd>82S3O(6UWRgV7<NqKxT*uIqWLvO6f3HzICKTE0ZC$v0g*@ zI#YguW7bqADT?H07j^D(QQ1zG(cxRh>;2B*?na2^ob2iqHu{Loc?}_X_s?u;NO8G| zCu<1@GS3iI7l6jo&1zM#@qi)2csmKFZEc#uz)FGfQQ70yEQ_}Av*@_=q_zZ&KFr*W zMkAWf2ZH#IH(v^wbQSHmWC?zpvRy>1y*tOgZM6w(iKs!tmJ_AA>1Q$75k$2*t*gy- z_^H$*kGD1W8_YIKoBGcd-3zx?u5T@Jc|uv4AIf&VcTtB>|9R7)#mrA^-gId|0SYj% z31&hc-hz5VOggeI{40vmG8-pgOACLfxu)~Av{<f)Osi(gUs-3Rv0>D6`tLzDZqZT& zT{bBdJq-@&7U>x1I1R%-v4q3PvL1;fE)6$54GgojaFMz*0IPw=G$ja2eJMW%<U%uP zk+{kv8CNwN+a^_1y2S#2K1!=Z#v12b8Q)QUjglV(5n5{-75-pRX&rRKo2-x(SZ#y{ z;FdM|{o9>qw8z|qBckZ7c-Yc5bHt|94?b&a^ljtL#$XAf@ec-gvRHPg94LE=%(D6< z<us{jMi9q<hD_UVkabfN+ziCETb8YJ0@_g|Y}&yi$|K6h#mNXdp7@jHxb2$GNK;P- zBabf9dc)jc!Lhhko;|@ly$sl{qF!D*Z9m;Z+72Bw%**lZ#xD=`Fr)+SgU_K3*+ct! zDR5NO&s0xPGQZtFTa+)~nw*T?!C~?(HwXZrARuzUYcqVnr2POBa<|5*pxDn~AgpqP zR048grJA}rA>lTcLcO&Q{hx$^6%Xlv`FZf{?#&#Wt4lKAN!pJ&Z7#-sQ?ip$GSo0p zYWbYR_$P4(F%rUb>I^VamPV9Zxw}7E(m;+vPc&kpdC78P-%2luRN~cSm6<dV)-5Pn zcuZQyOvr9PXQuU`fJW=6unwjvaSA-HH`b_VW13T1xsbXv!(pF>cKYmX1sW_y|8q(6 zNB**I;c@_N4LC#hs8{ppJsa?l3J5<P(!7tzr&bU@I9p5F2^8^0ZA<(?HqYl*jllJT zYTXbBFBNx2d+_^erd--ABXP%)j{srstyW*CBV!W4zPv0~z#eir+ulv794g8oU~XX6 z^G^8}@ez+V7+L6Z+(fWx{$dka5HGkC4dT^K;(DR4k0nmDnHdW57()m0__q>ej@~Gp zznd%mN{)g*LP{$|v#5uWz{&L2^p&u62L_xYD^x62Eqeep<kaLHxIVJ)Ez<th*KYLB zPjC>RhcP0IvAb}?{ns6NXsWOv1_kIj{ANP;%WM8bKIq{phyi7(rKqd`wqis!`#omk z)*r5>`bAIp|E&ckh1n_Yovb6_6HfE*a5C16#~~NGyXT|<NoM+JG;F#Ij*ym)s>E}} zlUq|qv^0+>BVJ-XKjR#N?~s*5e?<(8xcLj+(~cDK2+{6^jHFV;!qte;3<7dHO6Oqh zbG+^QNwP49v-{zKY6^bdf*WnJ*2X>e7PqKM8~9c$<U+vd3!lY`A#VZkSS#>bvk~<4 zxsyxh=_8!Arma{OeQBSNhoPpu`|@<li>NGEtsl}I5Lu)`_X{=U>I9uRtgjU}acjtu z`ggN_^oy?;x^HPEl7mf;&;qj0*8;r}zbh7Q+v?ud{Z2}K32GupY$tjWeg&u2ric_B z_mKI%ngCarNC9TyfZz|sqUTA=E`2Dei!WRNr?uF7JhH&|Moda}wfv&|jh;R!+^U9= z$JTd<h$c3y-D-ISAFnR9Avdy7hsd!b9oid#tzB)U2l)#3)!`Yb;Q*j%TUL<9lf)V{ zhzqE<*F7uYk7UH`H^s&3hT9lFPOU34{g`>)ruE1Qy0<<xb4d=Nj1A98i@I#do~awn zz>a;6D+Fi9f027$-u7?}Fr2SSrtAuT?Y?^=DmicXjH1p22Knn%PxD8OTzf_SpN60$ z3kpULf)4Tx1UP0r0sG%yE&yiRD1vn`055DP@&4~=w68|=ze6%Rkja)5??3N{VL(8b z#Xvy5|DSXJ`Q70GI@n5pnTi41Y?-M4Gj4(g0TB`g0r{Ge`yatt;(vc8!R2dtr<qHV zEjr$Rfb*3^{i}&$|Bs{~AO?y+Av+q#|Mb;B2RjU~Y$afr9X}YiDsb3N5R5<#_|2XS s@4q@6aFDM8{?Cy011Monf%jkS|0@CV|2z*OrwNR(C;8^C^Y7pP2LQ)n%>V!Z -- 2.7.4.windows.1 _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel