Date   

Re: Progress on getting Uncrustify working for EDK2?

Andrew Fish
 

MIke,

I could not figure out how to download uncrustify tool from the provided link. 99% of the people are just going to want to install the tool, not be a developer of the fork. We should have some simple instructions on how to download the tool. 

The link points to something git web view looking Azure DevOps page and talks about this Nuget thing I know nothing about. I ran out of time and had to give up trying to download the tool. 

Thanks,

Andrew Fish

On Nov 8, 2021, at 4:23 PM, Michael D Kinney <michael.d.kinney@...> wrote:

Hello,
 
Good information in this thread on code style.
 
Some of the topics apply to uncrustify and some are out of scope for what uncrustify can fix on its own.
 
I would like to focus on a date to convert all source code in edk2 repo using the uncrustify tool and to capture the other code style topics into their own thread andbugzillas.
 
I would like to propose a conversion date for uncrustify immediately after the edk2-stable202111 release on 2021-11-26.
 
I have been working with Michael Kubacki on a build comparison tool that verifies that the build generate the same obj/lib/dll/efi/fv/fd files before and after the uncrustify changes.  We would run and publish the results from this tool before committing the changes.
 
We need TianoCore community approval of the following:
 
  1. Approve format of C source generated by the uncrustify.
  2. Approve uncrustify changes right after edk2-stable-202111 release. 
    1. Extend code freeze until these changes are committed.
  3. Require use of uncrustify tool before submitting patch review emails or PRs.
    1. The required version would be a formally released version  from the fork maintained by Michael Kubacki until the changes can be upstreamed.
    2. https://dev.azure.com/projectmu/Uncrustify
  4. Add EDK II CI check to verify that all PRs submitted exactly match uncrustified version.  Reject PRs that do not match exactly.
  5. Implement a git hook available that would automatically run uncristufy before committing changes to a local branch of an edk2 repo.
 
Thanks,
 
Mike
 
From: Andrew Fish <afish@...> 
Sent: Thursday, October 7, 2021 2:09 PM
To: Marvin Häuser <mhaeuser@...>
Cc: edk2-devel-groups-io <devel@edk2.groups.io>; Kinney, Michael D <michael.d.kinney@...>; Leif Lindholm <leif@...>; mikuback@...; rebecca@...; Michael Kubacki <Michael.Kubacki@...>; Bret Barkelew <Bret.Barkelew@...>
Subject: Re: [edk2-devel] Progress on getting Uncrustify working for EDK2?
 
 


On Oct 7, 2021, at 1:43 PM, Marvin Häuser <mhaeuser@...> wrote:
 
Hey Mike,
Hey Andrew,

I'll just reply to both mails at once :)

On 07/10/2021 19:36, Andrew Fish wrote:




On Oct 7, 2021, at 1:19 PM, Michael D Kinney <michael.d.kinney@...> wrote:

Hi Marvin,

Some comments below.

Mike


-----Original Message-----
From:devel@edk2.groups.io<devel@edk2.groups.io> On Behalf Of Marvin Häuser
Sent: Thursday, October 7, 2021 11:31 AM
To: Leif Lindholm <leif@...>;devel@edk2.groups.io;mikuback@...
Cc:rebecca@...; Michael Kubacki <Michael.Kubacki@...>; Bret Barkelew <Bret.Barkelew@...>;
Kinney, Michael D <michael.d.kinney@...>
Subject: Re: [edk2-devel] Progress on getting Uncrustify working for EDK2?

Good day,

+1, but while you're at it, can we have arguments not align to the
function name...

  Status = Test (
             a
             );

... but to the next natural indentation level?

  Status = Test (
    a
    );

Basically no IDE I have seen supports EDK II's style, and I wouldn't be
keen on writing known-broken style to then rely on Uncrustify to fix it.

I also have heard some controversy regarding casts off-list, where some
prefer no spaces after casts to stress the evaluation order, and some
prefer spaces to have clearer visuals (as a cast *ideally* would be
something rare that requires good justification). Just throwing that out
there.


For things unrelated to autoformat (so semi-offtopic) but still relevant
to the coding spec:

1. Allow STATIC functions (if the debugging concerns are still relevant,
there could be another level of indirection, like RELEASE_STATIC)?

Debugging concerns are no longer relevant.  The suggestion in the BZ below
is to remove the STATIC macro and allow EDK II sources to add 'static'
to any functions it makes sense to use on.

https://bugzilla.tianocore.org/show_bug.cgi?id=1766

Thanks! I'd keep STATIC actually just for the sake of not doing no-op changes that do not really do anything and for consistency with CONST, but whatever works really.





2. Allow variable assignments on definition (basically non-static CONST
variables are banned...)?

Are referring to use of pre-initialized CONST variables declared within
a function?  I think Bret brought this topic up when implementing some
unit tests and the suggestion to pass ECCC was to promote them to
pre-initialized CONST global variables.

Yes.



The challenges we have seen in the past with pre-initialized variables within
a function is that they can cause compilers to inject use of memcpy() calls,
especially if the variable being initialized on the stack is a structure.
These cause build breaks today.

This issue is independent of CONST. I’m not sure a coding style tool is smart enough to catch this generically? You need an understanding of C types to know if the local variable assignment is going to trigger a memcpy().

What I’ve seen in the real world is the firmware compiles with -Os or LTO to fit int he ROM for DEBUG and RELEASE, and the optimizer optimizes away the call to memcpy. Then if you try to build NOOPT (or over ride the compiler flags on an individual driver/lib) you fail to link as only the NOOPT build injects the memcpy.

+1



Thus I think the best way to enforce this rule is to compile a project NOOPT. I’m trying to remember are there flags to built to tell it to compile and skip the FD construction? Maybe we should advocate platforms add a NOOPT build target that just compiles the code, but does not create the FD?


I know there were stability concerns with intrinsics in the past, but memcpy() is in the standard, and the rest remained stable to my knowledge. Maybe it's time to fix the issues at the root? Works for us:
https://github.com/acidanthera/OpenCorePkg/tree/master/Library/OcCompilerIntrinsicsLib

 
Marvin,
 
Good point. This would make the rule moot. So maybe just removing the requirement would be the easiest long term fix. 
 
Other embedded projects I know of do this too, and as you point out the compilers keep these APIs standard for folks the provide their own runtimes.
 
Thanks,
 
Andrew Fish


Best regards,
Marvin



Thanks,

Andrew Fish





3. Allow variable declarations at any scope (I had some nasty shadowing
bugs before, probably prohibit shadowing with warnings)?

By shadowing do you mean the declaration of the same variable name in
multiple scoped within the same function?



4. Require that exactly all function declarations and all function
definitions with no prior declaration must be documented (first
direction is enforcing docs, second is prohibiting doc duplication, I've
seen them go out-of-sync plenty of times)?

I agree that this can reduce duplication and sync issues.  The uncrustify
tool being discussed here could not help clean this up or enforce this
type of rule.  It is a good topic, but may need to be split out into its
own thread.



The latter bunch would not require any autoformat rules or reformatation
of existing code, but would be target only new submissions in my
opinion. Thoughts?


Thanks for your efforts!

Best regards,
Marvin


Am 07.10.2021 um 12:48 schrieb Leif Lindholm:

Hi Michael,

Apologies, I've owed you a response (promised off-list) for a while
now.

First, let me say I hugely appreciate this effort. Apart from aligning
the codebase(s), this will reduce manual reviewing effort
substantially, as well as cutting down on number of rework cycles for
developers.

Looking at the changes to (well, the comments in) uncrustify, this
seems to be constrained to:
- Newline after '(' for multi-line function calls.
- Dealing with "(("/"))" for DEBUG macros.
- Function pointer typedefs:
  - typedef\nEFIAPI
  - closing parentheses indentation

I don't think I've made any secret over the years that I am not a
massive fan of the EDK2 coding style in general. So I think for any
of its quirks that are substantial enough that they require not just
custom configuration but actual new function added to existing code
conformance tools, this would be an excellent point to sanitise the
coding style instead.

Taking these in order:

Newline after '('
-----------------
I think we already reached a level of flexibility around this, where
we don't actually enforce this (or single argument per
line). Personally, I'd be happy to update the coding style as
required instead.

DEBUG macro parentheses
-----------------------
How does uncrustify treat DEBUG macros without this modification?
Do we start getting everything turned into multi-level indented
multi-line statements without this change?

Function pointer typedefs:
--------------------------
I don't see that function pointer typedefs need to rigidly follow the
same pattern as the declaration of functions implementing them. Could
we update the coding style (if needed) instead?

Best Regards,

Leif

On Mon, Aug 16, 2021 at 16:00:38 -0400, Michael Kubacki wrote:

The edk2 branch was created here:
https://github.com/makubacki/edk2/tree/uncrustify_poc_2

We have a Project Mu fork with custom changes to the Uncrustify tool to help
comply with EDK II formatting here:
https://dev.azure.com/projectmu/_git/Uncrustify

The latest information about the status and how to experiment with the
configuration file and the tool are in that fork:

That said, I have also finished a CI plugin to run Uncrustify that should be
ready soon to initially deploy in Project Mu. Before doing so, I am trying
to settle on an initial configuration file that less strictly but more
reliably formats the code than in the examples in those branches. For
example, remove heuristics that when run against the same set of code
multiple times can produce different results. An example would be a rule
that reformats code because it exceeds a specified column width on one run
but on the next run that reformatted code triggers a different rule to
further align the code and so on. At least initially, some rules might be
tweaked in a more conservative approach that can be tightened in the future.
Once this configuration file is ready, we will baseline Project Mu code as
an example and turn on the plugin. The CI plugin runs Uncrustify against
modified files and if there's any changes, indicating a formatting
deviation, the diff chunks are saved in a log so they can be viewed as a
build artifact.

I am making progress on the updated config file and I should be able to post
a "uncrustify_poc_3" branch soon with the results.

Regarding indentation, Marvin is right that Uncrustify cannot support edk2
indentation style out-of-box. Some changes are made in that fork to handle
the formatting. At this point, it can handle the indentation in the cases
I've seen. Uncrustify does potentially give us the ability to massively
deploy changes across the codebase in case a decision were made to change
the style.

Thanks,
Michael

On 8/16/2021 3:39 PM, Marvin Häuser wrote:

Hey Rebecca,

I think even Uncrustify has issues with the EDK II indentation style.
You might want to check the UEFI Talkbox Discord server, I had a brief
chat with Michael about it there. I don't think realistically any tool
supports EDK II's indentation style however, so I'd propose it is
changed. This could be for new submissions only, or actually the entire
codebase could be reformatted at once with a good tool setup. While this
screws with git blame, the (to my understanding) decided on CRLF -> LF
change does that anyway, so at least two evils could be dealt with in
one go really.

Best regards,
Marvin

On 16/08/2021 21:34, Rebecca Cran wrote:


cc devel@ .

On 8/16/21 1:33 PM, Rebecca Cran wrote:


I noticed a message on Twitter about an idea of using Uncrustify
for EDK2 instead of the ECC tool, and came across https://www.mail-
archive.com/search?l@...&q=subject:%22Re%5C%3A+%5C%5Bedk2%5C-
devel%5C%5D+TianoCore+Community+Meeting+Minutes+%5C-+2%5C%2F4%22&o=newest&f=1

.

I was wondering if there's been any progress on it that I could
check out?


Michael Kubacki: in that message, you said:

"I'm planning to put up a branch that we can use as a reference
for a conversation around uncrustify in the next couple of
weeks."


Did you end up creating that branch, and if so could you provide
a link to it please?


--
Rebecca Cran

 
















 


回复: [edk2-devel] Soft Feature Freeze will start on 2021-11-08 for edk2-stable202111

gaoliming
 

Pierre:
Got it. I will remove it.

Thanks
Liming

-----邮件原件-----
发件人: Pierre Gondois <pierre.gondois@...>
发送时间: 2021年11月8日 21:55
收件人: devel@edk2.groups.io; gaoliming@...
抄送: 'Kinney, Michael D' <michael.d.kinney@...>; 'Teng, Lynn L'
<lynn.l.teng@...>; afish@...; leif@...
主题: Re: [edk2-devel] Soft Feature Freeze will start on 2021-11-08 for
edk2-stable202111

Hi Liming,

The "Add SSDT PCI generator in DynamicTablesPkg" feature at
https://bugzilla.tianocore.org/show_bug.cgi?id=3682 won't be merged
before hard feature freeze, so I think it should be removed from the
list of proposed features,

Regards,

Pierre


On 11/5/21 05:53, gaoliming via groups.io wrote:

Hi, all

We will enter into Soft Feature Freeze phase on 2021-11-08. In this
phase, the feature under review will not be allowed to be pushed. The
feature passed review can still be merged.



The patch review can continue without break in edk2 community. If the
patch is sent before Soft Feature Freeze, and plans to catch this
stable tag, the patch contributor need reply to his patch and notify
edk2 community. If the patch is sent after Soft Feature Freeze, and
plans to catch this stable tag, please add edk2-stable202111 key words
in the patch title and BZ, so the community know this patch target and
give the feedback.



Below is edk2-stable202111 tag planning
https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Release-Planni
ng
Proposed Schedule

Date (00:00:00 UTC-8) Description

2021-08-30 Beginning of development

2021-11-08 Soft Feature Freeze

2021-11-12 Hard Feature Freeze

2021-11-26 Release



Thanks

Liming


Re: [PATCH V5 2/3] SecurityPkg: Support CcMeasurementProtocol in DxeTpm2MeasureBootLib

Min Xu
 

Hi, Sami

Please see my comments inline.

 

From: Sami Mujawar <sami.mujawar@...>
Sent: Monday, November 8, 2021 10:18 PM
To: Xu, Min M <min.m.xu@...>; devel@edk2.groups.io
Cc: Kinney, Michael D <michael.d.kinney@...>; Liming Gao <gaoliming@...>; Liu, Zhiguang <zhiguang.liu@...>; Yao, Jiewen <jiewen.yao@...>; Wang, Jian J <jian.j.wang@...>; Gerd Hoffmann <kraxel@...>; nd <nd@...>
Subject: Re: [PATCH V5 2/3] SecurityPkg: Support CcMeasurementProtocol in DxeTpm2MeasureBootLib

 

Hi Min,

Thank you for the updated patch.

I have a minor suggestion marked inline as [SAMI]. Otherwise, this patch looks good to me.

Reviewed-by: Sami Mujawar <sami.mujawar@...>

Regards,

Sami Mujawar

 

On 07/11/2021 12:35 PM, Min Xu wrote:

BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3625
 
   //
-  Status = Tcg2Protocol->HashLogExtendEvent (
-             Tcg2Protocol,
-             0,
-             (EFI_PHYSICAL_ADDRESS) (UINTN) (VOID *) GptData,
-             (UINT64) EventSize,
-             Tcg2Event
-             );
-  if (!EFI_ERROR (Status)) {
-    mTcg2MeasureGptCount++;
+  if (CcProtocol != NULL) {
+    //
+    // EFI_CC_EVENT share the same data structure with EFI_TCG2_EVENT
+    // except the MrIndex and PCRIndex in Header.

[SAMI] Since we are now sharing the same data structures between TCG2 and CC, would it be better to typedef the CC data structures?

This would also avoid potential issues should any one of the data structures were to be changed in the future.
I think EFI_CC_EVENT, EFI_CC_EVENT_HEADER and EFI_CC_MR_INDEX could be type defined. Similarly, EFI_CC_EVENT_HEADER_VERSION could
also be defined to the TCG2 equivalent.

If not then we should at least add an ASSERT () to check if the size of EFI_CC_EVENT and EFI_TCG2_EVENT is not different.

[/SAMI]

 

[Min]

In the original version EFI_CC_EVENT is type defined as EFI_TCG2_EVENT. But in the definition of EFI_TCG2_EVENT / EFI_TCG2_EVENT_HEADER,  field of PCRIndex will be weird when it is used in CC Measurement. So we have to define a new data structure EFI_CC_EVENT / EFI_CC_EVENT_HEADER.

typedef struct {

  UINT32                        HeaderSize;

  UINT16                        HeaderVersion;

  TCG_PCRINDEX         PCRIndex;  ß It is PCR specific

  TCG_EVENTTYPE       EventType;

} EFI_TCG2_EVENT_HEADER;

 

I think we can add ASSERT (sizeof (EFI_CC_EVENT) == sizeof (EFI_TCG2_EVENT)).

[/Min]


Re: [PATCH 1/1] BaseTools: Add FMMT Tool

Michael D Kinney
 

Hi Christine,

Is this the same version of the sources that were submitted to the
edk2-basetools repo as the following PR?

https://github.com/tianocore/edk2-basetools/pull/11

The goal is to delete the sources from BaseTools/Source/Python and have all
edk2 developers use the python tools published by edk2-basetools project.

The edk2-basetools project also has more extensive CI checks for python sources
and we do not to accept source changes into BaseTools/Source/Python that do
not pass all the edk2-basetools CI checks.

Thanks,

Mike

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Yuwei Chen
Sent: Monday, November 8, 2021 4:09 PM
To: devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Liming Gao <gaoliming@...>
Subject: [edk2-devel] [PATCH 1/1] BaseTools: Add FMMT Tool

From: Bob Feng <bob.c.feng@...>

The FMMT python tool is used for firmware files operation, which has
the Fv/FFs-based 'View'&'Add'&'Delete'&'Replace' operation function.
Currently the FMMT C tool is saved in edk2-staging repo, but its
quality and coding style can't meet the Edk2 quality, which is hard to
maintain (Hard/Duplicate Code; Regression bugs; Restrict usage).
The new Python version keeps same functions with origin C version. It
has higher quality and better coding style, and it is much easier to
extend new functions and to maintain.

RFC: https://edk2.groups.io/g/devel/message/82877
Staging Link: https://github.com/tianocore/edk2-staging/tree/PyFMMT

Cc: Bob Feng <bob.c.feng@...>
Cc: Liming Gao <gaoliming@...>
Signed-off-by: Yuwei Chen <yuwei.chen@...>
---
BaseTools/BinWrappers/PosixLike/FMMT | 14 +
BaseTools/BinWrappers/WindowsLike/FMMT.bat | 4 +
BaseTools/Source/Python/FMMT/FMMT.py | 117 ++++
BaseTools/Source/Python/FMMT/FMMTConfig.ini | 5 +
.../Python/FMMT/Img/FirmwareVolumeFormat.png | Bin 0 -> 29515 bytes
.../Source/Python/FMMT/Img/NodeTreeFormat.png | Bin 0 -> 79906 bytes
BaseTools/Source/Python/FMMT/PI/Common.py | 81 +++
.../Source/Python/FMMT/PI/FfsFileHeader.py | 66 +++
BaseTools/Source/Python/FMMT/PI/FvHeader.py | 112 ++++
.../Source/Python/FMMT/PI/SectionHeader.py | 110 ++++
BaseTools/Source/Python/FMMT/PI/__init__.py | 6 +
BaseTools/Source/Python/FMMT/README.md | 180 ++++++
BaseTools/Source/Python/FMMT/__init__.py | 0
.../Python/FMMT/core/BinaryFactoryProduct.py | 371 +++++++++++++
BaseTools/Source/Python/FMMT/core/BiosTree.py | 199 +++++++
.../Source/Python/FMMT/core/BiosTreeNode.py | 191 +++++++
.../Source/Python/FMMT/core/FMMTOperation.py | 140 +++++
.../Source/Python/FMMT/core/FMMTParser.py | 86 +++
.../Source/Python/FMMT/core/FvHandler.py | 521 ++++++++++++++++++
.../Source/Python/FMMT/core/GuidTools.py | 152 +++++
.../Source/Python/FMMT/utils/FmmtLogger.py | 18 +
.../Source/Python/FMMT/utils/FvLayoutPrint.py | 54 ++
22 files changed, 2427 insertions(+)
create mode 100644 BaseTools/BinWrappers/PosixLike/FMMT
create mode 100644 BaseTools/BinWrappers/WindowsLike/FMMT.bat
create mode 100644 BaseTools/Source/Python/FMMT/FMMT.py
create mode 100644 BaseTools/Source/Python/FMMT/FMMTConfig.ini
create mode 100644 BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png
create mode 100644 BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png
create mode 100644 BaseTools/Source/Python/FMMT/PI/Common.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/FvHeader.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/SectionHeader.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/__init__.py
create mode 100644 BaseTools/Source/Python/FMMT/README.md
create mode 100644 BaseTools/Source/Python/FMMT/__init__.py
create mode 100644 BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
create mode 100644 BaseTools/Source/Python/FMMT/core/BiosTree.py
create mode 100644 BaseTools/Source/Python/FMMT/core/BiosTreeNode.py
create mode 100644 BaseTools/Source/Python/FMMT/core/FMMTOperation.py
create mode 100644 BaseTools/Source/Python/FMMT/core/FMMTParser.py
create mode 100644 BaseTools/Source/Python/FMMT/core/FvHandler.py
create mode 100644 BaseTools/Source/Python/FMMT/core/GuidTools.py
create mode 100644 BaseTools/Source/Python/FMMT/utils/FmmtLogger.py
create mode 100644 BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py

diff --git a/BaseTools/BinWrappers/PosixLike/FMMT b/BaseTools/BinWrappers/PosixLike/FMMT
new file mode 100644
index 000000000000..5f5519e1e1b6
--- /dev/null
+++ b/BaseTools/BinWrappers/PosixLike/FMMT
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+#python `dirname $0`/RunToolFromSource.py `basename $0` $*
+
+# If a ${PYTHON_COMMAND} command is available, use it in preference to python
+if command -v ${PYTHON_COMMAND} >/dev/null 2>&1; then
+ python_exe=${PYTHON_COMMAND}
+fi
+
+full_cmd=${BASH_SOURCE:-$0} # see http://mywiki.wooledge.org/BashFAQ/028 for a discussion of why $0 is not a good choice
here
+dir=$(dirname "$full_cmd")
+cmd=${full_cmd##*/}
+
+export PYTHONPATH="$dir/../../Source/Python/FMMT:$dir/../../Source/Python${PYTHONPATH:+:"$PYTHONPATH"}"
+exec "${python_exe:-python}" -m $cmd.$cmd "$@"
diff --git a/BaseTools/BinWrappers/WindowsLike/FMMT.bat b/BaseTools/BinWrappers/WindowsLike/FMMT.bat
new file mode 100644
index 000000000000..259d6cd6ddea
--- /dev/null
+++ b/BaseTools/BinWrappers/WindowsLike/FMMT.bat
@@ -0,0 +1,4 @@
+@setlocal
+@set ToolName=%~n0%
+@set PYTHONPATH=%PYTHONPATH%;%BASE_TOOLS_PATH%\Source\Python;%BASE_TOOLS_PATH%\Source\Python\FMMT
+@%PYTHON_COMMAND% -m %ToolName%.%ToolName% %*
diff --git a/BaseTools/Source/Python/FMMT/FMMT.py b/BaseTools/Source/Python/FMMT/FMMT.py
new file mode 100644
index 000000000000..1c41face7308
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/FMMT.py
@@ -0,0 +1,117 @@
+# @file
+# Firmware Module Management Tool.
+#
+# Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+# Import Modules
+#
+import argparse
+import sys
+from core.FMMTOperation import *
+
+parser = argparse.ArgumentParser(description='''
+View the Binary Structure of FD/FV/Ffs/Section, and Delete/Extract/Add/Replace a Ffs from/into a FV.
+''')
+parser.add_argument("--version", action="version", version='%(prog)s Version 1.0',
+ help="Print debug information.")
+parser.add_argument("-v", "--View", dest="View", nargs='+',
+ help="View each FV and the named files within each FV: '-v inputfile outputfile,
inputfiletype(.Fd/.Fv/.ffs/.sec)'")
+parser.add_argument("-d", "--Delete", dest="Delete", nargs='+',
+ help="Delete a Ffs from FV: '-d inputfile TargetFfsName outputfile TargetFvName(Optional,\
+ If not given, wil delete all the existed target Ffs)'")
+parser.add_argument("-e", "--Extract", dest="Extract", nargs='+',
+ help="Extract a Ffs Info: '-e inputfile TargetFfsName outputfile'")
+parser.add_argument("-a", "--Add", dest="Add", nargs='+',
+ help="Add a Ffs into a FV:'-a inputfile TargetFvName newffsfile outputfile'")
+parser.add_argument("-r", "--Replace", dest="Replace", nargs='+',
+ help="Replace a Ffs in a FV: '-r inputfile TargetFfsName newffsfile outputfile
TargetFvName(Optional,\
+ If not given, wil replace all the existed target Ffs with new Ffs file)'")
+parser.add_argument("-l", "--LogFileType", dest="LogFileType", nargs='+',
+ help="The format of log file which saves Binary layout. Currently supports: json, txt. More formats
will be added in the future")
+
+def print_banner():
+ print("")
+
+class FMMT():
+ def __init__(self) -> None:
+ self.firmware_packet = {}
+
+ def CheckFfsName(self, FfsName:str) -> str:
+ try:
+ return uuid.UUID(FfsName)
+ except:
+ return FfsName
+
+ def View(self, inputfile: str, logfiletype: str=None, outputfile: str=None) -> None:
+ # ParserFile(inputfile, ROOT_TYPE, logfile, outputfile)
+ filetype = os.path.splitext(inputfile)[1].lower()
+ if filetype == '.fd':
+ ROOT_TYPE = ROOT_TREE
+ elif filetype == '.fv':
+ ROOT_TYPE = ROOT_FV_TREE
+ elif filetype == '.ffs':
+ ROOT_TYPE = ROOT_FFS_TREE
+ elif filetype == '.sec':
+ ROOT_TYPE = ROOT_SECTION_TREE
+ else:
+ ROOT_TYPE = ROOT_TREE
+ ParserFile(inputfile, ROOT_TYPE, logfiletype, outputfile)
+
+ def Delete(self, inputfile: str, TargetFfs_name: str, outputfile: str, Fv_name: str=None) -> None:
+ if Fv_name:
+ DeleteFfs(inputfile, self.CheckFfsName(TargetFfs_name), outputfile, uuid.UUID(Fv_name))
+ else:
+ DeleteFfs(inputfile, self.CheckFfsName(TargetFfs_name), outputfile)
+
+ def Extract(self, inputfile: str, Ffs_name: str, outputfile: str) -> None:
+ ExtractFfs(inputfile, self.CheckFfsName(Ffs_name), outputfile)
+
+ def Add(self, inputfile: str, Fv_name: str, newffsfile: str, outputfile: str) -> None:
+ AddNewFfs(inputfile, self.CheckFfsName(Fv_name), newffsfile, outputfile)
+
+ def Replace(self,inputfile: str, Ffs_name: str, newffsfile: str, outputfile: str, Fv_name: str=None) -> None:
+ if Fv_name:
+ ReplaceFfs(inputfile, self.CheckFfsName(Ffs_name), newffsfile, outputfile, uuid.UUID(Fv_name))
+ else:
+ ReplaceFfs(inputfile, self.CheckFfsName(Ffs_name), newffsfile, outputfile)
+
+
+def main():
+ args=parser.parse_args()
+ status=0
+
+ try:
+ fmmt=FMMT()
+ if args.View:
+ if args.LogFileType:
+ fmmt.View(args.View[0], args.LogFileType[0])
+ else:
+ fmmt.View(args.View[0])
+ if args.Delete:
+ if len(args.Delete) == 4:
+ fmmt.Delete(args.Delete[0],args.Delete[1],args.Delete[2],args.Delete[3])
+ else:
+ fmmt.Delete(args.Delete[0],args.Delete[1],args.Delete[2])
+ if args.Extract:
+ fmmt.Extract(args.Extract[0],args.Extract[1],args.Extract[2])
+ if args.Add:
+ fmmt.Add(args.Add[0],args.Add[1],args.Add[2],args.Add[3])
+ if args.Replace:
+ if len(args.Replace) == 5:
+ fmmt.Replace(args.Replace[0],args.Replace[1],args.Replace[2],args.Replace[3],args.Replace[4])
+ else:
+ fmmt.Replace(args.Replace[0],args.Replace[1],args.Replace[2],args.Replace[3])
+ # TODO:
+ '''Do the main work'''
+ except Exception as e:
+ print(e)
+
+ return status
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/BaseTools/Source/Python/FMMT/FMMTConfig.ini b/BaseTools/Source/Python/FMMT/FMMTConfig.ini
new file mode 100644
index 000000000000..e4bf03c41f4e
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/FMMTConfig.ini
@@ -0,0 +1,5 @@
+a31280ad-481e-41b6-95e8-127f4c984779 TIANO TianoCompress
+ee4e5898-3914-4259-9d6e-dc7bd79403cf LZMA LzmaCompress
+fc1bcdb0-7d31-49aa-936a-a4600d9dd083 CRC32 GenCrc32
+d42ae6bd-1352-4bfb-909a-ca72a6eae889 LZMAF86 LzmaF86Compress
+3d532050-5cda-4fd0-879e-0f7f630d5afb BROTLI BrotliCompress
diff --git a/BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png
b/BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png
new file mode 100644
index 0000000000000000000000000000000000000000..7cc806a0133413a4aa037f1d43d80000546a5246
GIT binary patch
literal 29515
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

literal 0
HcmV?d00001

diff --git a/BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png b/BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png
new file mode 100644
index 0000000000000000000000000000000000000000..6335653ece1605cd0c53bd2cc2bc21d57d213dae
GIT binary patch
literal 79906
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
RBat2OV|2<CTVmi8`9DHIo&o>>

literal 0
HcmV?d00001

diff --git a/BaseTools/Source/Python/FMMT/PI/Common.py b/BaseTools/Source/Python/FMMT/PI/Common.py
new file mode 100644
index 000000000000..683341094429
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/Common.py
@@ -0,0 +1,81 @@
+## @file
+# This file is used to define the common C struct and functions.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from ctypes import *
+import uuid
+
+# ZeroGuid = uuid.UUID('{00000000-0000-0000-0000-000000000000}')
+# EFI_FIRMWARE_FILE_SYSTEM2_GUID = uuid.UUID('{8C8CE578-8A3D-4f1c-9935-896185C32DD3}')
+# EFI_FIRMWARE_FILE_SYSTEM3_GUID = uuid.UUID('{5473C07A-3DCB-4dca-BD6F-1E9689E7349A}')
+# EFI_FFS_VOLUME_TOP_FILE_GUID = uuid.UUID('{1BA0062E-C779-4582-8566-336AE8F78F09}')
+
+EFI_FIRMWARE_FILE_SYSTEM2_GUID = uuid.UUID("8c8ce578-8a3d-4f1c-9935-896185c32dd3")
+EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE = b'x\xe5\x8c\x8c=\x8a\x1cO\x995\x89a\x85\xc3-\xd3'
+# EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE = EFI_FIRMWARE_FILE_SYSTEM2_GUID.bytes
+EFI_FIRMWARE_FILE_SYSTEM3_GUID = uuid.UUID("5473C07A-3DCB-4dca-BD6F-1E9689E7349A")
+# EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE = b'x\xe5\x8c\x8c=\x8a\x1cO\x995\x89a\x85\xc3-\xd3'
+EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE = b'z\xc0sT\xcb=\xcaM\xbdo\x1e\x96\x89\xe74\x9a'
+EFI_SYSTEM_NVDATA_FV_GUID = uuid.UUID("fff12b8d-7696-4c8b-a985-2747075b4f50")
+EFI_SYSTEM_NVDATA_FV_GUID_BYTE = b"\x8d+\xf1\xff\x96v\x8bL\xa9\x85'G\x07[OP"
+EFI_FFS_VOLUME_TOP_FILE_GUID = uuid.UUID("1ba0062e-c779-4582-8566-336ae8f78f09")
+EFI_FFS_VOLUME_TOP_FILE_GUID_BYTE = b'.\x06\xa0\x1by\xc7\x82E\x85f3j\xe8\xf7\x8f\t'
+ZEROVECTOR_BYTE = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+PADVECTOR = uuid.UUID("ffffffff-ffff-ffff-ffff-ffffffffffff")
+FVH_SIGNATURE = b'_FVH'
+
+class GUID(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Guid1', c_uint32),
+ ('Guid2', c_uint16),
+ ('Guid3', c_uint16),
+ ('Guid4', ARRAY(c_uint8, 8)),
+ ]
+
+ def from_list(self, listformat: list) -> None:
+ self.Guid1 = listformat[0]
+ self.Guid2 = listformat[1]
+ self.Guid3 = listformat[2]
+ for i in range(8):
+ self.Guid4[i] = listformat[i+3]
+
+ def __cmp__(self, otherguid) -> bool:
+ if not isinstance(otherguid, GUID):
+ return 'Input is not the GUID instance!'
+ rt = False
+ if self.Guid1 == otherguid.Guid1 and self.Guid2 == otherguid.Guid2 and self.Guid3 == otherguid.Guid3:
+ rt = True
+ for i in range(8):
+ rt = rt & (self.Guid4[i] == otherguid.Guid4[i])
+ return rt
+
+def ModifyGuidFormat(target_guid: str) -> GUID:
+ target_guid = target_guid.replace('-', '')
+ target_list = []
+ start = [0,8,12,16,18,20,22,24,26,28,30]
+ end = [8,12,16,18,20,22,24,26,28,30,32]
+ num = len(start)
+ for pos in range(num):
+ new_value = int(target_guid[start[pos]:end[pos]], 16)
+ target_list.append(new_value)
+ new_format = GUID()
+ new_format.from_list(target_list)
+ return new_format
+
+
+# Get data from ctypes to bytes.
+def struct2stream(s) -> bytes:
+ length = sizeof(s)
+ p = cast(pointer(s), POINTER(c_char * length))
+ return p.contents.raw
+
+
+
+def GetPadSize(Size: int, alignment: int) -> int:
+ if Size % alignment == 0:
+ return 0
+ Pad_Size = alignment - Size % alignment
+ return Pad_Size
diff --git a/BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py b/BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py
new file mode 100644
index 000000000000..fb0100420ecc
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py
@@ -0,0 +1,66 @@
+## @file
+# This file is used to define the Ffs Header C Struct.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from struct import *
+from ctypes import *
+from PI.Common import *
+
+EFI_FFS_FILE_HEADER_LEN = 24
+EFI_FFS_FILE_HEADER2_LEN = 32
+
+class CHECK_SUM(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Header', c_uint8),
+ ('File', c_uint8),
+ ]
+
+class EFI_FFS_INTEGRITY_CHECK(Union):
+ _pack_ = 1
+ _fields_ = [
+ ('Checksum', CHECK_SUM),
+ ('Checksum16', c_uint16),
+ ]
+
+
+class EFI_FFS_FILE_HEADER(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Name', GUID),
+ ('IntegrityCheck', EFI_FFS_INTEGRITY_CHECK),
+ ('Type', c_uint8),
+ ('Attributes', c_uint8),
+ ('Size', ARRAY(c_uint8, 3)),
+ ('State', c_uint8),
+ ]
+
+ @property
+ def FFS_FILE_SIZE(self) -> int:
+ return self.Size[0] | self.Size[1] << 8 | self.Size[2] << 16
+
+ @property
+ def HeaderLength(self) -> int:
+ return 24
+
+class EFI_FFS_FILE_HEADER2(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Name', GUID),
+ ('IntegrityCheck', EFI_FFS_INTEGRITY_CHECK),
+ ('Type', c_uint8),
+ ('Attributes', c_uint8),
+ ('Size', ARRAY(c_uint8, 3)),
+ ('State', c_uint8),
+ ('ExtendedSize', c_uint64),
+ ]
+
+ @property
+ def FFS_FILE_SIZE(self) -> int:
+ return self.ExtendedSize
+
+ @property
+ def HeaderLength(self) -> int:
+ return 32
diff --git a/BaseTools/Source/Python/FMMT/PI/FvHeader.py b/BaseTools/Source/Python/FMMT/PI/FvHeader.py
new file mode 100644
index 000000000000..51cb15ddac28
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/FvHeader.py
@@ -0,0 +1,112 @@
+## @file
+# This file is used to define the FV Header C Struct.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from ast import Str
+from struct import *
+from ctypes import *
+from PI.Common import *
+
+class EFI_FV_BLOCK_MAP_ENTRY(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('NumBlocks', c_uint32),
+ ('Length', c_uint32),
+ ]
+
+
+class EFI_FIRMWARE_VOLUME_HEADER(Structure):
+ _fields_ = [
+ ('ZeroVector', ARRAY(c_uint8, 16)),
+ ('FileSystemGuid', GUID),
+ ('FvLength', c_uint64),
+ ('Signature', c_uint32),
+ ('Attributes', c_uint32),
+ ('HeaderLength', c_uint16),
+ ('Checksum', c_uint16),
+ ('ExtHeaderOffset', c_uint16),
+ ('Reserved', c_uint8),
+ ('Revision', c_uint8),
+ ('BlockMap', ARRAY(EFI_FV_BLOCK_MAP_ENTRY, 1)),
+ ]
+
+def Refine_FV_Header(nums):
+ class EFI_FIRMWARE_VOLUME_HEADER(Structure):
+ _fields_ = [
+ ('ZeroVector', ARRAY(c_uint8, 16)),
+ ('FileSystemGuid', GUID),
+ ('FvLength', c_uint64),
+ ('Signature', c_uint32),
+ ('Attributes', c_uint32),
+ ('HeaderLength', c_uint16),
+ ('Checksum', c_uint16),
+ ('ExtHeaderOffset', c_uint16),
+ ('Reserved', c_uint8),
+ ('Revision', c_uint8),
+ ('BlockMap', ARRAY(EFI_FV_BLOCK_MAP_ENTRY, nums)),
+ ]
+ return EFI_FIRMWARE_VOLUME_HEADER
+
+class EFI_FIRMWARE_VOLUME_EXT_HEADER(Structure):
+ _fields_ = [
+ ('FvName', GUID),
+ ('ExtHeaderSize', c_uint32)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY(Structure):
+ _fields_ = [
+ ('ExtEntrySize', c_uint16),
+ ('ExtEntryType', c_uint16)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE_0(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('TypeMask', c_uint32)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('TypeMask', c_uint32),
+ ('Types', ARRAY(GUID, 1))
+ ]
+
+def Refine_FV_EXT_ENTRY_OEM_TYPE_Header(nums: int) -> EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE:
+ class EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('TypeMask', c_uint32),
+ ('Types', ARRAY(GUID, nums))
+ ]
+ return EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE(Structure)
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE_0(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('FormatType', GUID)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('FormatType', GUID),
+ ('Data', ARRAY(c_uint8, 1))
+ ]
+
+def Refine_FV_EXT_ENTRY_GUID_TYPE_Header(nums: int) -> EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE:
+ class EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('FormatType', GUID),
+ ('Data', ARRAY(c_uint8, nums))
+ ]
+ return EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE(Structure)
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_USED_SIZE_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('UsedSize', c_uint32)
+ ]
diff --git a/BaseTools/Source/Python/FMMT/PI/SectionHeader.py b/BaseTools/Source/Python/FMMT/PI/SectionHeader.py
new file mode 100644
index 000000000000..8c6229819a18
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/SectionHeader.py
@@ -0,0 +1,110 @@
+## @file
+# This file is used to define the Section Header C Struct.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from struct import *
+from ctypes import *
+from PI.Common import *
+
+EFI_COMMON_SECTION_HEADER_LEN = 4
+EFI_COMMON_SECTION_HEADER2_LEN = 8
+
+class EFI_COMMON_SECTION_HEADER(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Size', ARRAY(c_uint8, 3)),
+ ('Type', c_uint8),
+ ]
+
+ @property
+ def SECTION_SIZE(self) -> int:
+ return self.Size[0] | self.Size[1] << 8 | self.Size[2] << 16
+
+ def Common_Header_Size(self) -> int:
+ return 4
+
+class EFI_COMMON_SECTION_HEADER2(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Size', ARRAY(c_uint8, 3)),
+ ('Type', c_uint8),
+ ('ExtendedSize', c_uint32),
+ ]
+
+ @property
+ def SECTION_SIZE(self) -> int:
+ return self.ExtendedSize
+
+ def Common_Header_Size(self) -> int:
+ return 8
+
+class EFI_COMPRESSION_SECTION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('UncompressedLength', c_uint32),
+ ('CompressionType', c_uint8),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 5
+
+class EFI_FREEFORM_SUBTYPE_GUID_SECTION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('SubTypeGuid', GUID),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 16
+
+class EFI_GUID_DEFINED_SECTION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('SectionDefinitionGuid', GUID),
+ ('DataOffset', c_uint16),
+ ('Attributes', c_uint16),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 20
+
+def Get_USER_INTERFACE_Header(nums: int):
+ class EFI_SECTION_USER_INTERFACE(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('FileNameString', ARRAY(c_uint16, nums)),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 2 * nums
+
+ def GetUiString(self) -> str:
+ UiString = ''
+ for i in range(nums):
+ if self.FileNameString[i]:
+ UiString += chr(self.FileNameString[i])
+ return UiString
+
+ return EFI_SECTION_USER_INTERFACE
+
+def Get_VERSION_Header(nums: int):
+ class EFI_SECTION_VERSION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('BuildNumber', c_uint16),
+ ('VersionString', ARRAY(c_uint16, nums)),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 2 * (nums+1)
+
+ def GetVersionString(self) -> str:
+ VersionString = ''
+ for i in range(nums):
+ if self.VersionString[i]:
+ VersionString += chr(self.VersionString[i])
+ return VersionString
+
+ return EFI_SECTION_VERSION
diff --git a/BaseTools/Source/Python/FMMT/PI/__init__.py b/BaseTools/Source/Python/FMMT/PI/__init__.py
new file mode 100644
index 000000000000..21cbd82effc5
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/__init__.py
@@ -0,0 +1,6 @@
+## @file
+# This file is used to define the FMMT dependent external tool management class.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/README.md b/BaseTools/Source/Python/FMMT/README.md
new file mode 100644
index 000000000000..cfbc966a6af6
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/README.md
@@ -0,0 +1,180 @@
+# FMMT
+## Overview
+This FMMT tool is the python implementation of the edk2 FMMT tool which locates at https://github.com/tianocore/edk2-
staging/tree/FceFmmt.
+This implementation has the same usage as the edk2 FMMT, but it's more readable and relaiable.
+
+# FMMT User Guide
+
+#### Last updated October 13, 2021
+
+Important Changes and Updates:
+
+- Oct 13, 2021 Initial Draft of FMMT Python Tool
+
+#### Note:
+
+- FMMT Python Tool keeps same function with origin FMMT C Tool. It is much easier to maintain and extend other functions.
+
+
+
+# 1. Introduction
+
+## 1.1 Overview
+
+The Firmware Device is a persistent physical repository that contains firmware code and/or data. The firmware code and/or
data stored in Firmware Volumes. Detail layout of Firmware Volumes is described in “Figure 1. The Firmware Volume Format”.
+
+![](Img/FirmwareVolumeFormat.png)
+
+​ Figure 1. The Firmware
Volume Format
+
+In firmware development, binary file has its firmware layout following the Platform-Initialization Specification. Thus,
operation on FV file / FFS file (Firmware File) is an efficient and convenient way for firmware function testing and
developing. FMMT Python tool is used for firmware files operation.
+
+## 1.2 Tool Capabilities
+
+The FMMT tool is capable of:
+
+- Parse a FD (Firmware Device) / FV (Firmware Volume) / FFS (Firmware Files)
+
+- Add a new FFS into a FV file (both included in a FD file or not)
+
+- Replace an FFS in a FV file with a new FFS file
+
+- Delete an FFS in a FV file (both included in a FD file or not)
+
+- Extract the FFS from a FV file (both included in a FD file or not)
+
+## 1.3 References
+
+| Document |
+| ------------------------------------------------ |
+| UEFI Platform Initialization (PI) Specification |
+
+
+
+# 2. FMMT Python Tool Usage
+
+## 2.1 Required Files
+
+### 2.1.1 Independent use
+
+When independent use the FMMT Python Tool, the following files and settings are required:
+
+- GuidTool executable files used for Decompress/Compress Firmware data.
+
+- Environment variables path with GuidTool path setting.
+
+### 2.1.2 Use with Build System
+
+When use the FMMT Python Tool with Build System:
+
+- If only use Edk2 based GuidTool, do not need other preparation.
+
+- If use other customized GuidTool, need prepare the config file with GuidTool info. The syntax for GuidTool definition
shown as follow:
+
+ ***ToolsGuid ShortName Command***
+
+ -- Example: ***3d532050-5cda-4fd0-879e-0f7f630d5afb BROTLI BrotliCompress***
+
+## 2.2 Syntax
+
+### 2.2.1 Syntax for Parse file
+
+​ ***-v < Inputfile > < Outputfile > -l < LogFileType >***
+
+- Parse *Inputfile*, show its firmware layout with log file. *Outputfile* is optional, if inputs, the *Inputfile* will be
encapsulated into *Outputfile* following the parsed firmware layout. *"-l LogFileType"* is optional, it decides the format
of log file which saves Binary layout. Currently supports: json, txt. More formats will be added in the future.
+
+### 2.2.2 Syntax for Add a new FFS
+
+​ ***-a < Inputfile > < TargetFvName/TargetFvGuid > < NewFfsFile > < Outputfile >***
+
+- Add the *NewFfsFile* into *Inputfile*. *TargetFvName/TargetFvGuid* (Name or Guid) is the TargetFv which *NewFfsFile*
will be added into.
+
+### 2.2.3 Syntax for Delete an FFS
+
+​ ***-d < Inputfile > < TargetFfsName > < Outputfile > < TargetFvName/TargetFvGuid >***
+
+- Delete the Ffs from *Inputfile*. TargetFfsName (Guid) is the TargetFfs which will be deleted.
*TargetFvName/TargetFvGuid* is optional, which is the parent of TargetFfs*.*
+
+### 2.2.4 Syntax for Replace an FFS
+
+​ ***-r < Inputfile > < TargetFfsName > < NewFfsFile > < Outputfile > < TargetFvName/TargetFvGuid >***
+
+- Replace the Ffs with the NewFfsFile. TargetFfsName (Guid) is the TargetFfs which will be replaced.
*TargetFvName/TargetFvGuid* is optional, which is the parent of TargetFfs*.*
+
+### 2.2.5 Syntax for Extract an FFS
+
+​ ***-e < Inputfile > < TargetFfsName > < Outputfile >***
+
+- Extract the Ffs from the Inputfile. TargetFfsName (Guid) is the TargetFfs which will be extracted.
+
+
+
+# 3. FMMT Python Tool Design
+
+FMMT Python Tool uses the NodeTree saves whole Firmware layout. Each Node have its Data field, which saves the
FirmwareClass(FD/FV/FFS/SECTION/BINARY) Data. All the parse/add/delete/replace/extract operations are based on the
NodeTree (adjusting the layout and data).
+
+## 3.1 NodeTree
+
+A whole NodeTree saves all the Firmware info.
+
+- Parent & Child relationship figured out the Firmware layout.
+
+- Each Node have several fields. ‘Data’ field saves an FirmwareClass instance which contains all the data info of the
info.
+
+### 3.1.1 NodeTree Format
+
+The NodeTree will be created with parse function. When parse a file, a Root Node will be initialized firstly. The Data
split and Tree construction process is described with an FD file shown as ‘Figure 2. The NodeTree format’:
+
+- A Root Node is initialized.
+
+- Use the ‘FV Signature’ as FV key to split Whole FD Data. ‘FV0’, ‘FV1’, ‘FV2’… Node created.
+
+- After FV level Node created, use the ‘Ffs Data Size’ as FFS key to split each FV Data. ‘Ffs0’...Node created.
+
+- After FFS level Node created, use the ‘Section Data Size’ as Section key to split each Ffs Data. ‘Section0’...Node
created.
+
+- If some of Section includes other Sections, continue use the ‘Section Data Size’ as Section key to split each Section
Data.
+
+- After all Node created, the whole NodeTree saves all the info. (Can be used in other functions or print the whole
firmware layout into log file)
+
+![](Img/NodeTreeFormat.png)
+
+​
Figure 2. The NodeTree format
+
+### 3.1.2 Node Factory and Product
+
+As 3.1.1, Each Node is created by data split and recognition. To extend the NodeTree usage, Factory pattern is used in
Node created process.
+
+Each Node have its Factory to create Product and use Product ParserData function to deal with the data.
+
+## 3.2 GuidTool
+
+There are two ways to set the GuidTool. One from Config file, another from environment variables.
+
+Current GuidTool first check if has Config file.
+
+- If have, load the config GuidTool Information.
+
+- Else get from environment variables.
+
+### 3.2.1 Get from Config file
+
+- Config file should in same folder with FMMT.py or the path in environment variables.
+
+- Content should follow the format:
+
+ ***ToolsGuid ShortName Command***
+
+### 3.2.2 Get from Environment Variables
+
+- The GuidTool Command used must be set in environment variables.
+
+### 3.2.3 Edk2 Based GuidTool
+
+| ***Guid*** | ***ShortName*** | ***Command*** |
+| ------------------------------------------ | --------------- | --------------------- |
+| ***a31280ad-481e-41b6-95e8-127f4c984779*** | ***TIANO*** | ***TianoCompress*** |
+| ***ee4e5898-3914-4259-9d6e-dc7bd79403cf*** | ***LZMA*** | ***LzmaCompress*** |
+| ***fc1bcdb0-7d31-49aa-936a-a4600d9dd083*** | ***CRC32*** | ***GenCrc32*** |
+| ***d42ae6bd-1352-4bfb-909a-ca72a6eae889*** | ***LZMAF86*** | ***LzmaF86Compress*** |
+| ***3d532050-5cda-4fd0-879e-0f7f630d5afb*** | ***BROTLI*** | ***BrotliCompress*** |
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/__init__.py b/BaseTools/Source/Python/FMMT/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
b/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
new file mode 100644
index 000000000000..afc0a90bd0cd
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
@@ -0,0 +1,371 @@
+## @file
+# This file is used to implement of the various bianry parser.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from re import T
+import copy
+import os
+from PI.Common import *
+from core.BiosTreeNode import *
+from core.BiosTree import *
+from core.GuidTools import *
+
+ROOT_TREE = 'ROOT'
+ROOT_FV_TREE = 'ROOT_FV_TREE'
+ROOT_FFS_TREE = 'ROOT_FFS_TREE'
+ROOT_SECTION_TREE = 'ROOT_SECTION_TREE'
+
+FV_TREE = 'FV'
+DATA_FV_TREE = 'DATA_FV'
+FFS_TREE = 'FFS'
+FFS_PAD = 'FFS_PAD'
+FFS_FREE_SPACE = 'FFS_FREE_SPACE'
+SECTION_TREE = 'SECTION'
+SEC_FV_TREE = 'SEC_FV_IMAGE'
+BINARY_DATA = 'BINARY'
+Fv_count = 0
+
+## Abstract factory
+class BinaryFactory():
+ type:list = []
+
+ def Create_Product():
+ pass
+
+class BinaryProduct():
+ ## Use GuidTool to decompress data.
+ def DeCompressData(self, GuidTool, Section_Data: bytes) -> bytes:
+ ParPath = os.path.abspath(os.path.dirname(os.path.abspath(__file__))+os.path.sep+"..")
+ ToolPath = os.path.join(ParPath, r'FMMTConfig.ini')
+ guidtool = GUIDTools(ToolPath).__getitem__(struct2stream(GuidTool))
+ DecompressedData = guidtool.unpack(Section_Data)
+ return DecompressedData
+
+ def ParserData():
+ pass
+
+class SectionFactory(BinaryFactory):
+ type = [SECTION_TREE]
+
+ def Create_Product():
+ return SectionProduct()
+
+class FfsFactory(BinaryFactory):
+ type = [ROOT_SECTION_TREE, FFS_TREE]
+
+ def Create_Product():
+ return FfsProduct()
+
+class FvFactory(BinaryFactory):
+ type = [ROOT_FFS_TREE, FV_TREE, SEC_FV_TREE]
+
+ def Create_Product():
+ return FvProduct()
+
+class FdFactory(BinaryFactory):
+ type = [ROOT_FV_TREE, ROOT_TREE]
+
+ def Create_Product():
+ return FdProduct()
+
+class SectionProduct(BinaryProduct):
+ ## Decompress the compressed section.
+ def ParserData(self, Section_Tree, whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ if Section_Tree.Data.Type == 0x01:
+ Section_Tree.Data.OriData = Section_Tree.Data.Data
+ self.ParserFfs(Section_Tree, b'')
+ # Guided Define Section
+ elif Section_Tree.Data.Type == 0x02:
+ Section_Tree.Data.OriData = Section_Tree.Data.Data
+ DeCompressGuidTool = Section_Tree.Data.ExtHeader.SectionDefinitionGuid
+ Section_Tree.Data.Data = self.DeCompressData(DeCompressGuidTool, Section_Tree.Data.Data)
+ Section_Tree.Data.Size = len(Section_Tree.Data.Data) + Section_Tree.Data.HeaderLength
+ self.ParserFfs(Section_Tree, b'')
+ elif Section_Tree.Data.Type == 0x03:
+ Section_Tree.Data.OriData = Section_Tree.Data.Data
+ self.ParserFfs(Section_Tree, b'')
+ # SEC_FV Section
+ elif Section_Tree.Data.Type == 0x17:
+ global Fv_count
+ Sec_Fv_Info = FvNode(Fv_count, Section_Tree.Data.Data)
+ Sec_Fv_Tree = BIOSTREE('FV'+ str(Fv_count))
+ Sec_Fv_Tree.type = SEC_FV_TREE
+ Sec_Fv_Tree.Data = Sec_Fv_Info
+ Sec_Fv_Tree.Data.HOffset = Section_Tree.Data.DOffset
+ Sec_Fv_Tree.Data.DOffset = Sec_Fv_Tree.Data.HOffset + Sec_Fv_Tree.Data.Header.HeaderLength
+ Sec_Fv_Tree.Data.Data = Section_Tree.Data.Data[Sec_Fv_Tree.Data.Header.HeaderLength:]
+ Section_Tree.insertChild(Sec_Fv_Tree)
+ Fv_count += 1
+
+ def ParserFfs(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ Rel_Offset = 0
+ Section_Offset = 0
+ # Get the Data from parent tree, if do not have the tree then get it from the whole_data.
+ if ParTree.Data != None:
+ Data_Size = len(ParTree.Data.Data)
+ Section_Offset = ParTree.Data.DOffset
+ Whole_Data = ParTree.Data.Data
+ else:
+ Data_Size = len(Whole_Data)
+ # Parser all the data to collect all the Section recorded in its Parent Section.
+ while Rel_Offset < Data_Size:
+ # Create a SectionNode and set it as the SectionTree's Data
+ Section_Info = SectionNode(Whole_Data[Rel_Offset:])
+ Section_Tree = BIOSTREE(Section_Info.Name)
+ Section_Tree.type = SECTION_TREE
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.HeaderLength: Rel_Offset+Section_Info.Size]
+ Section_Info.DOffset = Section_Offset + Section_Info.HeaderLength + Rel_Whole_Offset
+ Section_Info.HOffset = Section_Offset + Rel_Whole_Offset
+ Section_Info.ROffset = Rel_Offset
+ if Section_Info.Header.Type == 0:
+ break
+ # The final Section in parent Section does not need to add padding, else must be 4-bytes align with parent
Section start offset
+ Pad_Size = 0
+ if (Rel_Offset+Section_Info.HeaderLength+len(Section_Info.Data) != Data_Size):
+ Pad_Size = GetPadSize(Section_Info.Size, 4)
+ Section_Info.PadData = Pad_Size * b'\x00'
+ if Section_Info.Header.Type == 0x02:
+ Section_Info.DOffset = Section_Offset + Section_Info.ExtHeader.DataOffset + Rel_Whole_Offset
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.ExtHeader.DataOffset:
Rel_Offset+Section_Info.Size]
+ if Section_Info.Header.Type == 0x14:
+ ParTree.Data.Version = Section_Info.ExtHeader.GetVersionString()
+ if Section_Info.Header.Type == 0x15:
+ ParTree.Data.UiName = Section_Info.ExtHeader.GetUiString()
+ Section_Offset += Section_Info.Size + Pad_Size
+ Rel_Offset += Section_Info.Size + Pad_Size
+ Section_Tree.Data = Section_Info
+ ParTree.insertChild(Section_Tree)
+
+class FfsProduct(BinaryProduct):
+ # ParserFFs / GetSection
+ def ParserData(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ Rel_Offset = 0
+ Section_Offset = 0
+ # Get the Data from parent tree, if do not have the tree then get it from the whole_data.
+ if ParTree.Data != None:
+ Data_Size = len(ParTree.Data.Data)
+ Section_Offset = ParTree.Data.DOffset
+ Whole_Data = ParTree.Data.Data
+ else:
+ Data_Size = len(Whole_Data)
+ # Parser all the data to collect all the Section recorded in Ffs.
+ while Rel_Offset < Data_Size:
+ # Create a SectionNode and set it as the SectionTree's Data
+ Section_Info = SectionNode(Whole_Data[Rel_Offset:])
+ Section_Tree = BIOSTREE(Section_Info.Name)
+ Section_Tree.type = SECTION_TREE
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.HeaderLength: Rel_Offset+Section_Info.Size]
+ Section_Info.DOffset = Section_Offset + Section_Info.HeaderLength + Rel_Whole_Offset
+ Section_Info.HOffset = Section_Offset + Rel_Whole_Offset
+ Section_Info.ROffset = Rel_Offset
+ if Section_Info.Header.Type == 0:
+ break
+ # The final Section in Ffs does not need to add padding, else must be 4-bytes align with Ffs start offset
+ Pad_Size = 0
+ if (Rel_Offset+Section_Info.HeaderLength+len(Section_Info.Data) != Data_Size):
+ Pad_Size = GetPadSize(Section_Info.Size, 4)
+ Section_Info.PadData = Pad_Size * b'\x00'
+ if Section_Info.Header.Type == 0x02:
+ Section_Info.DOffset = Section_Offset + Section_Info.ExtHeader.DataOffset + Rel_Whole_Offset
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.ExtHeader.DataOffset:
Rel_Offset+Section_Info.Size]
+ # If Section is Version or UI type, it saves the version and UI info of its parent Ffs.
+ if Section_Info.Header.Type == 0x14:
+ ParTree.Data.Version = Section_Info.ExtHeader.GetVersionString()
+ if Section_Info.Header.Type == 0x15:
+ ParTree.Data.UiName = Section_Info.ExtHeader.GetUiString()
+ Section_Offset += Section_Info.Size + Pad_Size
+ Rel_Offset += Section_Info.Size + Pad_Size
+ Section_Tree.Data = Section_Info
+ ParTree.insertChild(Section_Tree)
+
+class FvProduct(BinaryProduct):
+ ## ParserFv / GetFfs
+ def ParserData(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ Ffs_Offset = 0
+ Rel_Offset = 0
+ # Get the Data from parent tree, if do not have the tree then get it from the whole_data.
+ if ParTree.Data != None:
+ Data_Size = len(ParTree.Data.Data)
+ Ffs_Offset = ParTree.Data.DOffset
+ Whole_Data = ParTree.Data.Data
+ else:
+ Data_Size = len(Whole_Data)
+ # Parser all the data to collect all the Ffs recorded in Fv.
+ while Rel_Offset < Data_Size:
+ # Create a FfsNode and set it as the FFsTree's Data
+ if Data_Size - Rel_Offset < 24:
+ Ffs_Tree = BIOSTREE('Free_Space')
+ Ffs_Tree.type = FFS_FREE_SPACE
+ Ffs_Tree.Data = FreeSpaceNode(Whole_Data[Rel_Offset:])
+ Ffs_Tree.Data.HOffset = Ffs_Offset + Rel_Whole_Offset
+ Ffs_Tree.Data.DOffset = Ffs_Tree.Data.HOffset
+ ParTree.Data.Free_Space = Data_Size - Rel_Offset
+ ParTree.insertChild(Ffs_Tree)
+ Rel_Offset = Data_Size
+ else:
+ Ffs_Info = FfsNode(Whole_Data[Rel_Offset:])
+ Ffs_Tree = BIOSTREE(Ffs_Info.Name)
+ Ffs_Info.HOffset = Ffs_Offset + Rel_Whole_Offset
+ Ffs_Info.DOffset = Ffs_Offset + Ffs_Info.Header.HeaderLength + Rel_Whole_Offset
+ Ffs_Info.ROffset = Rel_Offset
+ if Ffs_Info.Name == PADVECTOR:
+ Ffs_Tree.type = FFS_PAD
+ Ffs_Info.Data = Whole_Data[Rel_Offset+Ffs_Info.Header.HeaderLength: Rel_Offset+Ffs_Info.Size]
+ Ffs_Info.Size = len(Ffs_Info.Data) + Ffs_Info.Header.HeaderLength
+ # if current Ffs is the final ffs of Fv and full of b'\xff', define it with Free_Space
+ if struct2stream(Ffs_Info.Header).replace(b'\xff', b'') == b'':
+ Ffs_Tree.type = FFS_FREE_SPACE
+ Ffs_Info.Data = Whole_Data[Rel_Offset:]
+ Ffs_Info.Size = len(Ffs_Info.Data)
+ ParTree.Data.Free_Space = Ffs_Info.Size
+ else:
+ Ffs_Tree.type = FFS_TREE
+ Ffs_Info.Data = Whole_Data[Rel_Offset+Ffs_Info.Header.HeaderLength: Rel_Offset+Ffs_Info.Size]
+ # The final Ffs in Fv does not need to add padding, else must be 8-bytes align with Fv start offset
+ Pad_Size = 0
+ if Ffs_Tree.type != FFS_FREE_SPACE and (Rel_Offset+Ffs_Info.Header.HeaderLength+len(Ffs_Info.Data) !=
Data_Size):
+ Pad_Size = GetPadSize(Ffs_Info.Size, 8)
+ Ffs_Info.PadData = Pad_Size * b'\xff'
+ Ffs_Offset += Ffs_Info.Size + Pad_Size
+ Rel_Offset += Ffs_Info.Size + Pad_Size
+ Ffs_Tree.Data = Ffs_Info
+ ParTree.insertChild(Ffs_Tree)
+
+class FdProduct(BinaryProduct):
+ type = [ROOT_FV_TREE, ROOT_TREE]
+
+ ## Create DataTree with first level /fv Info, then parser each Fv.
+ def ParserData(self, WholeFvTree, whole_data: bytes=b'', offset: int=0) -> None:
+ # Get all Fv image in Fd with offset and length
+ Fd_Struct = self.GetFvFromFd(whole_data)
+ data_size = len(whole_data)
+ Binary_count = 0
+ global Fv_count
+ # If the first Fv image is the Binary Fv, add it into the tree.
+ if Fd_Struct[0][1] != 0:
+ Binary_node = BIOSTREE('BINARY'+ str(Binary_count))
+ Binary_node.type = BINARY_DATA
+ Binary_node.Data = BinaryNode(str(Binary_count))
+ Binary_node.Data.Data = whole_data[:Fd_Struct[0][1]]
+ Binary_node.Data.Size = len(Binary_node.Data.Data)
+ Binary_node.Data.HOffset = 0 + offset
+ WholeFvTree.insertChild(Binary_node)
+ Binary_count += 1
+ # Add the first collected Fv image into the tree.
+ Cur_node = BIOSTREE(Fd_Struct[0][0]+ str(Fv_count))
+ Cur_node.type = Fd_Struct[0][0]
+ Cur_node.Data = FvNode(Fv_count, whole_data[Fd_Struct[0][1]:Fd_Struct[0][1]+Fd_Struct[0][2][0]])
+ Cur_node.Data.HOffset = Fd_Struct[0][1] + offset
+ Cur_node.Data.DOffset = Cur_node.Data.HOffset+Cur_node.Data.Header.HeaderLength
+ Cur_node.Data.Data =
whole_data[Fd_Struct[0][1]+Cur_node.Data.Header.HeaderLength:Fd_Struct[0][1]+Cur_node.Data.Size]
+ WholeFvTree.insertChild(Cur_node)
+ Fv_count += 1
+ Fv_num = len(Fd_Struct)
+ # Add all the collected Fv image and the Binary Fv image between them into the tree.
+ for i in range(Fv_num-1):
+ if Fd_Struct[i][1]+Fd_Struct[i][2][0] != Fd_Struct[i+1][1]:
+ Binary_node = BIOSTREE('BINARY'+ str(Binary_count))
+ Binary_node.type = BINARY_DATA
+ Binary_node.Data = BinaryNode(str(Binary_count))
+ Binary_node.Data.Data = whole_data[Fd_Struct[i][1]+Fd_Struct[i][2][0]:Fd_Struct[i+1][1]]
+ Binary_node.Data.Size = len(Binary_node.Data.Data)
+ Binary_node.Data.HOffset = Fd_Struct[i][1]+Fd_Struct[i][2][0] + offset
+ WholeFvTree.insertChild(Binary_node)
+ Binary_count += 1
+ Cur_node = BIOSTREE(Fd_Struct[i+1][0]+ str(Fv_count))
+ Cur_node.type = Fd_Struct[i+1][0]
+ Cur_node.Data = FvNode(Fv_count, whole_data[Fd_Struct[i+1][1]:Fd_Struct[i+1][1]+Fd_Struct[i+1][2][0]])
+ Cur_node.Data.HOffset = Fd_Struct[i+1][1] + offset
+ Cur_node.Data.DOffset = Cur_node.Data.HOffset+Cur_node.Data.Header.HeaderLength
+ Cur_node.Data.Data =
whole_data[Fd_Struct[i+1][1]+Cur_node.Data.Header.HeaderLength:Fd_Struct[i+1][1]+Cur_node.Data.Size]
+ WholeFvTree.insertChild(Cur_node)
+ Fv_count += 1
+ # If the final Fv image is the Binary Fv, add it into the tree
+ if Fd_Struct[-1][1] + Fd_Struct[-1][2][0] != data_size:
+ Binary_node = BIOSTREE('BINARY'+ str(Binary_count))
+ Binary_node.type = BINARY_DATA
+ Binary_node.Data = BinaryNode(str(Binary_count))
+ Binary_node.Data.Data = whole_data[Fd_Struct[-1][1]+Fd_Struct[-1][2][0]:]
+ Binary_node.Data.Size = len(Binary_node.Data.Data)
+ Binary_node.Data.HOffset = Fd_Struct[-1][1]+Fd_Struct[-1][2][0] + offset
+ WholeFvTree.insertChild(Binary_node)
+ Binary_count += 1
+
+ ## Get the first level Fv from Fd file.
+ def GetFvFromFd(self, whole_data: bytes=b'') -> list:
+ Fd_Struct = []
+ data_size = len(whole_data)
+ cur_index = 0
+ # Get all the EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE FV image offset and length.
+ while cur_index < data_size:
+ if EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE in whole_data[cur_index:]:
+ target_index = whole_data[cur_index:].index(EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE) + cur_index
+ if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE and whole_data[target_index-
16:target_index] == ZEROVECTOR_BYTE:
+ Fd_Struct.append([FV_TREE, target_index - 16, unpack("Q",
whole_data[target_index+16:target_index+24])])
+ cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]
+ else:
+ cur_index = target_index + 16
+ else:
+ cur_index = data_size
+ cur_index = 0
+ # Get all the EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE FV image offset and length.
+ while cur_index < data_size:
+ if EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE in whole_data[cur_index:]:
+ target_index = whole_data[cur_index:].index(EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE) + cur_index
+ if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE and whole_data[target_index-
16:target_index] == ZEROVECTOR_BYTE:
+ Fd_Struct.append([FV_TREE, target_index - 16, unpack("Q",
whole_data[target_index+16:target_index+24])])
+ cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]
+ else:
+ cur_index = target_index + 16
+ else:
+ cur_index = data_size
+ cur_index = 0
+ # Get all the EFI_SYSTEM_NVDATA_FV_GUID_BYTE FV image offset and length.
+ while cur_index < data_size:
+ if EFI_SYSTEM_NVDATA_FV_GUID_BYTE in whole_data[cur_index:]:
+ target_index = whole_data[cur_index:].index(EFI_SYSTEM_NVDATA_FV_GUID_BYTE) + cur_index
+ if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE and whole_data[target_index-
16:target_index] == ZEROVECTOR_BYTE:
+ Fd_Struct.append([DATA_FV_TREE, target_index - 16, unpack("Q",
whole_data[target_index+16:target_index+24])])
+ cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]
+ else:
+ cur_index = target_index + 16
+ else:
+ cur_index = data_size
+ # Sort all the collect Fv image with offset.
+ Fd_Struct.sort(key=lambda x:x[1])
+ tmp_struct = copy.deepcopy(Fd_Struct)
+ tmp_index = 0
+ Fv_num = len(Fd_Struct)
+ # Remove the Fv image included in another Fv image.
+ for i in range(1,Fv_num):
+ if tmp_struct[i][1]+tmp_struct[i][2][0] < tmp_struct[i-1][1]+tmp_struct[i-1][2][0]:
+ Fd_Struct.remove(Fd_Struct[i-tmp_index])
+ tmp_index += 1
+ return Fd_Struct
+
+class ParserEntry():
+ FactoryTable:dict = {
+ SECTION_TREE: SectionFactory,
+ ROOT_SECTION_TREE: FfsFactory,
+ FFS_TREE: FfsFactory,
+ ROOT_FFS_TREE: FvFactory,
+ FV_TREE: FvFactory,
+ SEC_FV_TREE: FvFactory,
+ ROOT_FV_TREE: FdFactory,
+ ROOT_TREE: FdFactory,
+ }
+
+ def GetTargetFactory(self, Tree_type: str) -> BinaryFactory:
+ if Tree_type in self.FactoryTable:
+ return self.FactoryTable[Tree_type]
+
+ def Generate_Product(self, TargetFactory: BinaryFactory, Tree, Data: bytes, Offset: int) -> None:
+ New_Product = TargetFactory.Create_Product()
+ New_Product.ParserData(Tree, Data, Offset)
+
+ def DataParser(self, Tree, Data: bytes, Offset: int) -> None:
+ TargetFactory = self.GetTargetFactory(Tree.type)
+ if TargetFactory:
+ self.Generate_Product(TargetFactory, Tree, Data, Offset)
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/BiosTree.py b/BaseTools/Source/Python/FMMT/core/BiosTree.py
new file mode 100644
index 000000000000..b2cdb49f9f3f
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BiosTree.py
@@ -0,0 +1,199 @@
+## @file
+# This file is used to define the Bios layout tree structure and related operations.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import collections
+from typing import OrderedDict
+from PI.Common import *
+
+ROOT_TREE = 'ROOT'
+ROOT_FV_TREE = 'ROOT_FV_TREE'
+ROOT_FFS_TREE = 'ROOT_FFS_TREE'
+ROOT_SECTION_TREE = 'ROOT_SECTION_TREE'
+
+FV_TREE = 'FV'
+DATA_FV_TREE = 'DATA_FV'
+FFS_TREE = 'FFS'
+FFS_PAD = 'FFS_PAD'
+FFS_FREE_SPACE = 'FFS_FREE_SPACE'
+SECTION_TREE = 'SECTION'
+SEC_FV_TREE = 'SEC_FV_IMAGE'
+BINARY_DATA = 'BINARY'
+
+RootType = [ROOT_TREE, ROOT_FV_TREE, ROOT_FFS_TREE, ROOT_SECTION_TREE]
+FvType = [FV_TREE, SEC_FV_TREE]
+FfsType = FFS_TREE
+SecType = SECTION_TREE
+
+class BIOSTREE:
+ def __init__(self, NodeName: str) -> None:
+ self.key = NodeName
+ self.type = None
+ self.Data = None
+ self.Child = []
+ self.Findlist = []
+ self.Parent = None
+ self.NextRel = None
+ self.LastRel = None
+
+ def HasChild(self) -> bool:
+ if self.Child == []:
+ return False
+ else:
+ return True
+
+ def isFinalChild(self) -> bool:
+ ParTree = self.Parent
+ if ParTree:
+ if ParTree.Child[-1] == self:
+ return True
+ return False
+
+ # FvTree.insertChild()
+ def insertChild(self, newNode, pos: int=None) -> None:
+ if len(self.Child) == 0:
+ self.Child.append(newNode)
+ else:
+ if not pos:
+ LastTree = self.Child[-1]
+ self.Child.append(newNode)
+ LastTree.NextRel = newNode
+ newNode.LastRel = LastTree
+ else:
+ newNode.NextRel = self.Child[pos-1].NextRel
+ newNode.LastRel = self.Child[pos].LastRel
+ self.Child[pos-1].NextRel = newNode
+ self.Child[pos].LastRel = newNode
+ self.Child.insert(pos, newNode)
+ newNode.Parent = self
+
+ # lastNode.insertRel(newNode)
+ def insertRel(self, newNode) -> None:
+ if self.Parent:
+ parentTree = self.Parent
+ new_index = parentTree.Child.index(self) + 1
+ parentTree.Child.insert(new_index, newNode)
+ self.NextRel = newNode
+ newNode.LastRel = self
+
+ def deleteNode(self, deletekey: str) -> None:
+ FindStatus, DeleteTree = self.FindNode(deletekey)
+ if FindStatus:
+ parentTree = DeleteTree.Parent
+ lastTree = DeleteTree.LastRel
+ nextTree = DeleteTree.NextRel
+ if parentTree:
+ index = parentTree.Child.index(DeleteTree)
+ del parentTree.Child[index]
+ if lastTree and nextTree:
+ lastTree.NextRel = nextTree
+ nextTree.LastRel = lastTree
+ elif lastTree:
+ lastTree.NextRel = None
+ elif nextTree:
+ nextTree.LastRel = None
+ return DeleteTree
+ else:
+ print('Could not find the target tree')
+ return None
+
+ def FindNode(self, key: str, Findlist: list) -> None:
+ if self.key == key or (self.Data and self.Data.Name == key) or (self.type == FFS_TREE and self.Data.UiName ==
key):
+ Findlist.append(self)
+ else:
+ for item in self.Child:
+ item.FindNode(key, Findlist)
+
+ def GetTreePath(self):
+ BiosTreePath = [self]
+ while self.Parent:
+ BiosTreePath.insert(0, self.Parent)
+ self = self.Parent
+ return BiosTreePath
+
+ def parserTree(self, TargetDict: dict=None, Info: list=None, space: int=0) -> None:
+ Key = list(TargetDict.keys())[0]
+ if TargetDict[Key]["Type"] in RootType:
+ Info.append("Image File: {}".format(Key))
+ Info.append("FilesNum: {}".format(TargetDict.get(Key).get('FilesNum')))
+ Info.append("\n")
+ elif TargetDict[Key]["Type"] in FvType:
+ space += 2
+ if TargetDict[Key]["Type"] == SEC_FV_TREE:
+ Info.append("{}Child FV named {} of {}".format(space*" ", Key, self.FvId))
+ space += 2
+ else:
+ Info.append("FvId: {}".format(Key))
+ self.FvId = Key
+ Info.append("{}FvNameGuid: {}".format(space*" ", TargetDict.get(Key).get('FvNameGuid')))
+ Info.append("{}Attributes: {}".format(space*" ", TargetDict.get(Key).get('Attributes')))
+ Info.append("{}Total Volume Size: {}".format(space*" ", TargetDict.get(Key).get('Size')))
+ Info.append("{}Free Volume Size: {}".format(space*" ", TargetDict.get(Key).get('FreeSize')))
+ Info.append("{}Volume Offset: {}".format(space*" ", TargetDict.get(Key).get('Offset')))
+ Info.append("{}FilesNum: {}".format(space*" ", TargetDict.get(Key).get('FilesNum')))
+ elif TargetDict[Key]["Type"] in FfsType:
+ space += 2
+ if TargetDict.get(Key).get('UiName') != "b''":
+ Info.append("{}File: {} / {}".format(space*" ", Key, TargetDict.get(Key).get('UiName')))
+ else:
+ Info.append("{}File: {}".format(space*" ", Key))
+ if "Files" in list(TargetDict[Key].keys()):
+ for item in TargetDict[Key]["Files"]:
+ self.parserTree(item, Info, space)
+
+ def ExportTree(self,TreeInfo: dict=None) -> dict:
+ if TreeInfo is None:
+ TreeInfo =collections.OrderedDict()
+
+ if self.type == ROOT_TREE or self.type == ROOT_FV_TREE or self.type == ROOT_FFS_TREE or self.type ==
ROOT_SECTION_TREE:
+ key = str(self.key)
+ TreeInfo[self.key] = collections.OrderedDict()
+ TreeInfo[self.key]["Name"] = key
+ TreeInfo[self.key]["Type"] = self.type
+ TreeInfo[self.key]["FilesNum"] = len(self.Child)
+ elif self.type == FV_TREE or self.type == SEC_FV_TREE:
+ key = str(self.Data.FvId)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ if self.Data.FvId != self.Data.Name:
+ TreeInfo[key]["FvNameGuid"] = str(self.Data.Name)
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Attributes"] = hex(self.Data.Header.Attributes)
+ TreeInfo[key]["Size"] = hex(self.Data.Header.FvLength)
+ TreeInfo[key]["FreeSize"] = hex(self.Data.Free_Space)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+ elif self.type == FFS_TREE:
+ key = str(self.Data.Name)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ TreeInfo[key]["UiName"] = '{}'.format(self.Data.UiName)
+ TreeInfo[key]["Version"] = '{}'.format(self.Data.Version)
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Size"] = hex(self.Data.Size)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+ elif self.type == SECTION_TREE and self.Data.Type == 0x02:
+ key = str(self.Data.Name)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Size"] = hex(len(self.Data.OriData) + self.Data.HeaderLength)
+ TreeInfo[key]["DecompressedSize"] = hex(self.Data.Size)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+ elif self is not None:
+ key = str(self.Data.Name)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Size"] = hex(self.Data.Size)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+
+ for item in self.Child:
+ TreeInfo[key].setdefault('Files',[]).append( item.ExportTree())
+
+ return TreeInfo
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/BiosTreeNode.py b/BaseTools/Source/Python/FMMT/core/BiosTreeNode.py
new file mode 100644
index 000000000000..b77d9100e7b3
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BiosTreeNode.py
@@ -0,0 +1,191 @@
+## @file
+# This file is used to define the BIOS Tree Node.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from PI.FvHeader import *
+from PI.FfsFileHeader import *
+from PI.SectionHeader import *
+from PI.Common import *
+import uuid
+
+SectionHeaderType = {
+ 0x01:'EFI_COMPRESSION_SECTION',
+ 0x02:'EFI_GUID_DEFINED_SECTION',
+ 0x03:'EFI_SECTION_DISPOSABLE',
+ 0x10:'EFI_SECTION_PE32',
+ 0x11:'EFI_SECTION_PIC',
+ 0x12:'EFI_SECTION_TE',
+ 0x13:'EFI_SECTION_DXE_DEPEX',
+ 0x14:'EFI_SECTION_VERSION',
+ 0x15:'EFI_SECTION_USER_INTERFACE',
+ 0x16:'EFI_SECTION_COMPATIBILITY16',
+ 0x17:'EFI_SECTION_FIRMWARE_VOLUME_IMAGE',
+ 0x18:'EFI_FREEFORM_SUBTYPE_GUID_SECTION',
+ 0x19:'EFI_SECTION_RAW',
+ 0x1B:'EFI_SECTION_PEI_DEPEX',
+ 0x1C:'EFI_SECTION_MM_DEPEX'
+}
+HeaderType = [0x01, 0x02, 0x14, 0x15, 0x18]
+
+class BinaryNode:
+ def __init__(self, name: str) -> None:
+ self.Size = 0
+ self.Name = "BINARY" + str(name)
+ self.HOffset = 0
+ self.Data = b''
+
+class FvNode:
+ def __init__(self, name, buffer: bytes) -> None:
+ self.Header = EFI_FIRMWARE_VOLUME_HEADER.from_buffer_copy(buffer)
+ Map_num = (self.Header.HeaderLength - 56)//8
+ self.Header = Refine_FV_Header(Map_num).from_buffer_copy(buffer)
+ self.FvId = "FV" + str(name)
+ self.Name = "FV" + str(name)
+ if self.Header.ExtHeaderOffset:
+ self.ExtHeader = EFI_FIRMWARE_VOLUME_EXT_HEADER.from_buffer_copy(buffer[self.Header.ExtHeaderOffset:])
+ self.Name = uuid.UUID(bytes_le=struct2stream(self.ExtHeader.FvName))
+ self.ExtEntryOffset = self.Header.ExtHeaderOffset + 20
+ if self.ExtHeader.ExtHeaderSize != 20:
+ self.ExtEntryExist = 1
+ self.ExtEntry = EFI_FIRMWARE_VOLUME_EXT_ENTRY.from_buffer_copy(buffer[self.ExtEntryOffset:])
+ self.ExtTypeExist = 1
+ if self.ExtEntry.ExtEntryType == 0x01:
+ nums = (self.ExtEntry.ExtEntrySize - 8) // 16
+ self.ExtEntry =
Refine_FV_EXT_ENTRY_OEM_TYPE_Header(nums).from_buffer_copy(buffer[self.ExtEntryOffset:])
+ elif self.ExtEntry.ExtEntryType == 0x02:
+ nums = self.ExtEntry.ExtEntrySize - 20
+ self.ExtEntry =
Refine_FV_EXT_ENTRY_GUID_TYPE_Header(nums).from_buffer_copy(buffer[self.ExtEntryOffset:])
+ elif self.ExtEntry.ExtEntryType == 0x03:
+ self.ExtEntry =
EFI_FIRMWARE_VOLUME_EXT_ENTRY_USED_SIZE_TYPE.from_buffer_copy(buffer[self.ExtEntryOffset:])
+ else:
+ self.ExtTypeExist = 0
+ else:
+ self.ExtEntryExist = 0
+ self.Size = self.Header.FvLength
+ self.HeaderLength = self.Header.HeaderLength
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.Data = b''
+ if self.Header.Signature != 1213613663:
+ print('Invalid! Fv Header Signature {} is not "_FVH".'.format(self.Header.Signature))
+ with open(str(self.Name)+'.fd', "wb") as f:
+ f.write(struct2stream(self.Header))
+ assert False
+ self.PadData = b''
+ self.Free_Space = 0
+ self.ModCheckSum()
+
+ def ModCheckSum(self) -> None:
+ # Fv Header Sums to 0.
+ Header = struct2stream(self.Header)[::-1]
+ Size = self.HeaderLength // 2
+ Sum = 0
+ for i in range(Size):
+ Sum += int(Header[i*2: i*2 + 2].hex(), 16)
+ if Sum & 0xffff:
+ self.Header.Checksum = int(hex(0x10000 - int(hex(Sum - self.Header.Checksum)[-4:], 16)), 16)
+
+ def ModFvExt(self) -> None:
+ # If used space changes and self.ExtEntry.UsedSize exists, self.ExtEntry.UsedSize need to be changed.
+ if self.Header.ExtHeaderOffset and self.ExtEntryExist and self.ExtTypeExist and self.ExtEntry.Hdr.ExtEntryType ==
0x03:
+ self.ExtEntry.UsedSize = self.Header.FvLength - self.Free_Space
+
+ def ModFvSize(self) -> None:
+ # If Fv Size changed, self.Header.FvLength and self.Header.BlockMap[i].NumBlocks need to be changed.
+ BlockMapNum = len(self.Header.BlockMap)
+ for i in range(BlockMapNum):
+ if self.Header.BlockMap[i].Length:
+ self.Header.BlockMap[i].NumBlocks = self.Header.FvLength // self.Header.BlockMap[i].Length
+
+ def ModExtHeaderData(self) -> None:
+ if self.Header.ExtHeaderOffset:
+ ExtHeaderData = struct2stream(self.ExtHeader)
+ ExtHeaderDataOffset = self.Header.ExtHeaderOffset - self.HeaderLength
+ self.Data = self.Data[:ExtHeaderDataOffset] + ExtHeaderData + self.Data[ExtHeaderDataOffset+20:]
+ if self.Header.ExtHeaderOffset and self.ExtEntryExist:
+ ExtHeaderEntryData = struct2stream(self.ExtEntry)
+ ExtHeaderEntryDataOffset = self.Header.ExtHeaderOffset + 20 - self.HeaderLength
+ self.Data = self.Data[:ExtHeaderEntryDataOffset] + ExtHeaderEntryData +
self.Data[ExtHeaderEntryDataOffset+len(ExtHeaderEntryData):]
+
+class FfsNode:
+ def __init__(self, buffer: bytes) -> None:
+ self.Header = EFI_FFS_FILE_HEADER.from_buffer_copy(buffer)
+ # self.Attributes = unpack("<B", buffer[21:22])[0]
+ if self.Header.Size != 0 and self.Header.Attributes == 0x01:
+ print('Error Ffs Header! Ffs Header Size and Attributes is not matched!')
+ if self.Header.Size == 0 and self.Header.Attributes == 0x01:
+ self.Header = EFI_FFS_FILE_HEADER2.from_buffer_copy(buffer)
+ self.Name = uuid.UUID(bytes_le=struct2stream(self.Header.Name))
+ self.UiName = b''
+ self.Version = b''
+ self.Size = self.Header.FFS_FILE_SIZE
+ self.HeaderLength = self.Header.HeaderLength
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.Data = b''
+ self.PadData = b''
+
+ def ModCheckSum(self) -> None:
+ HeaderData = struct2stream(self.Header)
+ HeaderSum = 0
+ for item in HeaderData:
+ HeaderSum += item
+ HeaderSum -= self.Header.State
+ HeaderSum -= self.Header.IntegrityCheck.Checksum.File
+ if HeaderSum & 0xff:
+ Header = self.Header.IntegrityCheck.Checksum.Header + 0x100 - int(hex(HeaderSum)[-2:], 16)
+ self.Header.IntegrityCheck.Checksum.Header = int(hex(Header)[-2:], 16)
+
+class SectionNode:
+ def __init__(self, buffer: bytes) -> None:
+ if buffer[0:3] != b'\xff\xff\xff':
+ self.Header = EFI_COMMON_SECTION_HEADER.from_buffer_copy(buffer)
+ else:
+ self.Header = EFI_COMMON_SECTION_HEADER2.from_buffer_copy(buffer)
+ if self.Header.Type in SectionHeaderType:
+ self.Name = SectionHeaderType[self.Header.Type]
+ elif self.Header.Type == 0:
+ self.Name = "EFI_SECTION_RAW"
+ else:
+ self.Name = "SECTION"
+ if self.Header.Type in HeaderType:
+ self.ExtHeader = self.GetExtHeader(self.Header.Type, buffer[self.Header.Common_Header_Size():],
(self.Header.SECTION_SIZE-self.Header.Common_Header_Size()))
+ self.HeaderLength = self.Header.Common_Header_Size() + self.ExtHeader.ExtHeaderSize()
+ else:
+ self.ExtHeader = None
+ self.HeaderLength = self.Header.Common_Header_Size()
+ self.Size = self.Header.SECTION_SIZE
+ self.Type = self.Header.Type
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.Data = b''
+ self.OriData = b''
+ self.OriHeader = b''
+ self.PadData = b''
+
+ def GetExtHeader(self, Type: int, buffer: bytes, nums: int=0) -> None:
+ if Type == 0x01:
+ return EFI_COMPRESSION_SECTION.from_buffer_copy(buffer)
+ elif Type == 0x02:
+ return EFI_GUID_DEFINED_SECTION.from_buffer_copy(buffer)
+ elif Type == 0x14:
+ return Get_VERSION_Header((nums - 2)//2).from_buffer_copy(buffer)
+ elif Type == 0x15:
+ return Get_USER_INTERFACE_Header(nums//2).from_buffer_copy(buffer)
+ elif Type == 0x18:
+ return EFI_FREEFORM_SUBTYPE_GUID_SECTION.from_buffer_copy(buffer)
+
+class FreeSpaceNode:
+ def __init__(self, buffer: bytes) -> None:
+ self.Name = 'Free_Space'
+ self.Data = buffer
+ self.Size = len(buffer)
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.PadData = b''
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/FMMTOperation.py b/BaseTools/Source/Python/FMMT/core/FMMTOperation.py
new file mode 100644
index 000000000000..e42565965909
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/FMMTOperation.py
@@ -0,0 +1,140 @@
+## @file
+# This file is used to define the functions to operate bios binary file.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from core.FMMTParser import *
+from core.FvHandler import *
+from utils.FvLayoutPrint import *
+
+global Fv_count
+Fv_count = 0
+
+# The ROOT_TYPE can be 'ROOT_TREE', 'ROOT_FV_TREE', 'ROOT_FFS_TREE', 'ROOT_SECTION_TREE'
+def ParserFile(inputfile: str, ROOT_TYPE: str, logfile: str=None, outputfile: str=None) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TYPE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ # 3. Log Output
+ InfoDict = FmmtParser.WholeFvTree.ExportTree()
+ FmmtParser.WholeFvTree.parserTree(InfoDict, FmmtParser.BinaryInfo)
+ GetFormatter("").LogPrint(FmmtParser.BinaryInfo)
+ if logfile:
+ GetFormatter(logfile.lower()).dump(InfoDict,
FmmtParser.BinaryInfo,"Parser_Log_{}{}".format(os.path.basename(inputfile),".{}".format(logfile.lower())))
+ # 4. Data Encapsultion
+ if outputfile:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def DeleteFfs(inputfile: str, TargetFfs_name: str, outputfile: str, Fv_name: str=None) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ # 3. Data Modify
+ FmmtParser.WholeFvTree.FindNode(TargetFfs_name, FmmtParser.WholeFvTree.Findlist)
+ # Choose the Specfic DeleteFfs with Fv info
+ if Fv_name:
+ for item in FmmtParser.WholeFvTree.Findlist:
+ if item.Parent.key != Fv_name and item.Parent.Data.Name != Fv_name:
+ FmmtParser.WholeFvTree.Findlist.remove(item)
+ if FmmtParser.WholeFvTree.Findlist != []:
+ for Delete_Ffs in FmmtParser.WholeFvTree.Findlist:
+ FfsMod = FvHandler(None, Delete_Ffs)
+ Status = FfsMod.DeleteFfs()
+ else:
+ print('Target Ffs not found!!!')
+ # 4. Data Encapsultion
+ if Status:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def AddNewFfs(inputfile: str, Fv_name: str, newffsfile: str, outputfile: str) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ # Get Target Fv and Target Ffs_Pad
+ FmmtParser.WholeFvTree.FindNode(Fv_name, FmmtParser.WholeFvTree.Findlist)
+ # Create new ffs Tree
+ with open(newffsfile, "rb") as f:
+ new_ffs_data = f.read()
+ NewFmmtParser = FMMTParser(newffsfile, ROOT_FFS_TREE)
+ Status = False
+ # 3. Data Modify
+ if FmmtParser.WholeFvTree.Findlist:
+ for TargetFv in FmmtParser.WholeFvTree.Findlist:
+ TargetFfsPad = TargetFv.Child[-1]
+ if TargetFfsPad.type == FFS_FREE_SPACE:
+ NewFmmtParser.ParserFromRoot(NewFmmtParser.WholeFvTree, new_ffs_data, TargetFfsPad.Data.HOffset)
+ else:
+ NewFmmtParser.ParserFromRoot(NewFmmtParser.WholeFvTree, new_ffs_data,
TargetFfsPad.Data.HOffset+TargetFfsPad.Data.Size)
+ FfsMod = FvHandler(NewFmmtParser.WholeFvTree.Child[0], TargetFfsPad)
+ Status = FfsMod.AddFfs()
+ else:
+ print('Target Fv not found!!!')
+ # 4. Data Encapsultion
+ if Status:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def ReplaceFfs(inputfile: str, Ffs_name: str, newffsfile: str, outputfile: str, Fv_name: str=None) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ with open(newffsfile, "rb") as f:
+ new_ffs_data = f.read()
+ newFmmtParser = FMMTParser(newffsfile, FV_TREE)
+ newFmmtParser.ParserFromRoot(newFmmtParser.WholeFvTree, new_ffs_data)
+ Status = False
+ # 3. Data Modify
+ new_ffs = newFmmtParser.WholeFvTree.Child[0]
+ new_ffs.Data.PadData = GetPadSize(new_ffs.Data.Size, 8) * b'\xff'
+ FmmtParser.WholeFvTree.FindNode(Ffs_name, FmmtParser.WholeFvTree.Findlist)
+ if Fv_name:
+ for item in FmmtParser.WholeFvTree.Findlist:
+ if item.Parent.key != Fv_name and item.Parent.Data.Name != Fv_name:
+ FmmtParser.WholeFvTree.Findlist.remove(item)
+ if FmmtParser.WholeFvTree.Findlist != []:
+ for TargetFfs in FmmtParser.WholeFvTree.Findlist:
+ FfsMod = FvHandler(newFmmtParser.WholeFvTree.Child[0], TargetFfs)
+ Status = FfsMod.ReplaceFfs()
+ else:
+ print('Target Ffs not found!!!')
+ # 4. Data Encapsultion
+ if Status:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def ExtractFfs(inputfile: str, Ffs_name: str, outputfile: str) -> None:
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ FmmtParser.WholeFvTree.FindNode(Ffs_name, FmmtParser.WholeFvTree.Findlist)
+ if FmmtParser.WholeFvTree.Findlist != []:
+ TargetNode = FmmtParser.WholeFvTree.Findlist[0]
+ TargetFv = TargetNode.Parent
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ TargetNode.Data.Header.State = c_uint8(
+ ~TargetNode.Data.Header.State)
+ FinalData = struct2stream(TargetNode.Data.Header) + TargetNode.Data.Data
+ with open(outputfile, "wb") as f:
+ f.write(FinalData)
+ else:
+ print('Target Ffs not found!!!')
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/FMMTParser.py b/BaseTools/Source/Python/FMMT/core/FMMTParser.py
new file mode 100644
index 000000000000..6f35d4dd76cf
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/FMMTParser.py
@@ -0,0 +1,86 @@
+## @file
+# This file is used to define the interface of Bios Parser.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from PI.Common import *
+from core.BinaryFactoryProduct import ParserEntry
+from core.BiosTreeNode import *
+from core.BiosTree import *
+from core.GuidTools import *
+
+class FMMTParser:
+ def __init__(self, name: str, TYPE: str) -> None:
+ self.WholeFvTree = BIOSTREE(name)
+ self.WholeFvTree.type = TYPE
+ self.FinalData = b''
+ self.BinaryInfo = []
+
+ ## Parser the nodes in WholeTree.
+ def ParserFromRoot(self, WholeFvTree=None, whole_data: bytes=b'', Reloffset: int=0) -> None:
+ if WholeFvTree.type == ROOT_TREE or WholeFvTree.type == ROOT_FV_TREE:
+ ParserEntry().DataParser(self.WholeFvTree, whole_data, Reloffset)
+ else:
+ ParserEntry().DataParser(WholeFvTree, whole_data, Reloffset)
+ for Child in WholeFvTree.Child:
+ self.ParserFromRoot(Child, "")
+
+ ## Encapuslation all the data in tree into self.FinalData
+ def Encapsulation(self, rootTree, CompressStatus: bool) -> None:
+ # If current node is Root node, skip it.
+ if rootTree.type == ROOT_TREE or rootTree.type == ROOT_FV_TREE or rootTree.type == ROOT_FFS_TREE or rootTree.type
== ROOT_SECTION_TREE:
+ print('Start at Root !')
+ # If current node do not have Header, just add Data.
+ elif rootTree.type == BINARY_DATA or rootTree.type == FFS_FREE_SPACE:
+ self.FinalData += rootTree.Data.Data
+ rootTree.Child = []
+ # If current node do not have Child and ExtHeader, just add its Header and Data.
+ elif rootTree.type == DATA_FV_TREE or rootTree.type == FFS_PAD:
+ self.FinalData += struct2stream(rootTree.Data.Header) + rootTree.Data.Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ if ParTree.type != 'ROOT':
+ self.FinalData += ParTree.Data.PadData
+ rootTree.Child = []
+ # If current node is not Section node and may have Child and ExtHeader, add its Header,ExtHeader. If do not have
Child, add its Data.
+ elif rootTree.type == FV_TREE or rootTree.type == FFS_TREE or rootTree.type == SEC_FV_TREE:
+ if rootTree.HasChild():
+ self.FinalData += struct2stream(rootTree.Data.Header)
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header) + rootTree.Data.Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ if ParTree.type != 'ROOT':
+ self.FinalData += ParTree.Data.PadData
+ # If current node is Section, need to consider its ExtHeader, Child and Compressed Status.
+ elif rootTree.type == SECTION_TREE:
+ # Not compressed section
+ if rootTree.Data.OriData == b'' or (rootTree.Data.OriData != b'' and CompressStatus):
+ if rootTree.HasChild():
+ if rootTree.Data.ExtHeader:
+ self.FinalData += struct2stream(rootTree.Data.Header) + struct2stream(rootTree.Data.ExtHeader)
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header)
+ else:
+ Data = rootTree.Data.Data
+ if rootTree.Data.ExtHeader:
+ self.FinalData += struct2stream(rootTree.Data.Header) + struct2stream(rootTree.Data.ExtHeader) +
Data + rootTree.Data.PadData
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header) + Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ self.FinalData += ParTree.Data.PadData
+ # If compressed section
+ else:
+ Data = rootTree.Data.OriData
+ rootTree.Child = []
+ if rootTree.Data.ExtHeader:
+ self.FinalData += struct2stream(rootTree.Data.Header) + struct2stream(rootTree.Data.ExtHeader) + Data
+ rootTree.Data.PadData
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header) + Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ self.FinalData += ParTree.Data.PadData
+ for Child in rootTree.Child:
+ self.Encapsulation(Child, CompressStatus)
diff --git a/BaseTools/Source/Python/FMMT/core/FvHandler.py b/BaseTools/Source/Python/FMMT/core/FvHandler.py
new file mode 100644
index 000000000000..7809e1598c1e
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/FvHandler.py
@@ -0,0 +1,521 @@
+## @file
+# This file is used to the implementation of Bios layout handler.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import os
+from core.BiosTree import *
+from core.GuidTools import GUIDTools
+from core.BiosTreeNode import *
+from PI.Common import *
+
+EFI_FVB2_ERASE_POLARITY = 0x00000800
+
+def ChangeSize(TargetTree, size_delta: int=0) -> None:
+ if type(TargetTree.Data.Header) == type(EFI_FFS_FILE_HEADER2()) or type(TargetTree.Data.Header) ==
type(EFI_COMMON_SECTION_HEADER2()):
+ TargetTree.Data.Size -= size_delta
+ TargetTree.Data.Header.ExtendedSize -= size_delta
+ elif TargetTree.type == SECTION_TREE and TargetTree.Data.OriData:
+ OriSize = TargetTree.Data.Header.SECTION_SIZE
+ OriSize -= size_delta
+ TargetTree.Data.Header.Size[0] = OriSize % (16**2)
+ TargetTree.Data.Header.Size[1] = OriSize % (16**4) //(16**2)
+ TargetTree.Data.Header.Size[2] = OriSize // (16**4)
+ else:
+ TargetTree.Data.Size -= size_delta
+ TargetTree.Data.Header.Size[0] = TargetTree.Data.Size % (16**2)
+ TargetTree.Data.Header.Size[1] = TargetTree.Data.Size % (16**4) //(16**2)
+ TargetTree.Data.Header.Size[2] = TargetTree.Data.Size // (16**4)
+
+def ModifyFfsType(TargetFfs) -> None:
+ if type(TargetFfs.Data.Header) == type(EFI_FFS_FILE_HEADER()) and (TargetFfs.Data.HeaderLength + TargetFfs.Data.Size)
0xFFFFFF:
+ ExtendSize = TargetFfs.Data.Header.FFS_FILE_SIZE + 8
+ New_Header = EFI_FFS_FILE_HEADER2()
+ New_Header.Name = TargetFfs.Data.Header.Name
+ New_Header.IntegrityCheck = TargetFfs.Data.Header.IntegrityCheck
+ New_Header.Type = TargetFfs.Data.Header.Type
+ New_Header.Attributes = TargetFfs.Data.Header.Attributes
+ New_Header.Size = 0
+ New_Header.State = TargetFfs.Data.Header.State
+ New_Header.ExtendedSize = ExtendSize
+ TargetFfs.Data.Header = New_Header
+ TargetFfs.Data.Size = TargetFfs.Data.Header.FFS_FILE_SIZE
+ TargetFfs.Data.HeaderLength = TargetFfs.Data.Header.HeaderLength
+ TargetFfs.Data.ModCheckSum()
+ elif type(TargetFfs.Data.Header) == type(EFI_FFS_FILE_HEADER2()) and (TargetFfs.Data.HeaderLength +
TargetFfs.Data.Size) <= 0xFFFFFF:
+ New_Header = EFI_FFS_FILE_HEADER()
+ New_Header.Name = TargetFfs.Data.Header.Name
+ New_Header.IntegrityCheck = TargetFfs.Data.Header.IntegrityCheck
+ New_Header.Type = TargetFfs.Data.Header.Type
+ New_Header.Attributes = TargetFfs.Data.Header.Attributes
+ New_Header.Size = TargetFfs.Data.HeaderLength + TargetFfs.Data.Size
+ New_Header.State = TargetFfs.Data.Header.State
+ TargetFfs.Data.Header = New_Header
+ TargetFfs.Data.Size = TargetFfs.Data.Header.FFS_FILE_SIZE
+ TargetFfs.Data.HeaderLength = TargetFfs.Data.Header.HeaderLength
+ TargetFfs.Data.ModCheckSum()
+ if struct2stream(TargetFfs.Parent.Data.Header.FileSystemGuid) == EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE:
+ NeedChange = True
+ for item in TargetFfs.Parent.Child:
+ if type(item.Data.Header) == type(EFI_FFS_FILE_HEADER2()):
+ NeedChange = False
+ if NeedChange:
+ TargetFfs.Parent.Data.Header.FileSystemGuid = ModifyGuidFormat("8c8ce578-8a3d-4f1c-9935-896185c32dd3")
+
+ if type(TargetFfs.Data.Header) == type(EFI_FFS_FILE_HEADER2()):
+ TarParent = TargetFfs.Parent
+ while TarParent:
+ if TarParent.type == FV_TREE and struct2stream(TarParent.Data.Header.FileSystemGuid) ==
EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE:
+ TarParent.Data.Header.FileSystemGuid = ModifyGuidFormat("5473C07A-3DCB-4dca-BD6F-1E9689E7349A")
+ TarParent = TarParent.Parent
+
+class FvHandler:
+ def __init__(self, NewFfs, TargetFfs) -> None:
+ self.NewFfs = NewFfs
+ self.TargetFfs = TargetFfs
+ self.Status = False
+ self.Remain_New_Free_Space = 0
+
+ ## Use for Compress the Section Data
+ def CompressData(self, TargetTree) -> None:
+ TreePath = TargetTree.GetTreePath()
+ pos = len(TreePath)
+ self.Status = True
+ while pos:
+ if self.Status:
+ if TreePath[pos-1].type == SECTION_TREE and TreePath[pos-1].Data.Type == 0x02:
+ self.CompressSectionData(TreePath[pos-1], None, TreePath[pos-1].Data.ExtHeader.SectionDefinitionGuid)
+ else:
+ if pos == len(TreePath):
+ self.CompressSectionData(TreePath[pos-1], pos)
+ else:
+ self.CompressSectionData(TreePath[pos-1], None)
+ pos -= 1
+
+ def CompressSectionData(self, TargetTree, pos: int, GuidTool=None) -> None:
+ NewData = b''
+ temp_save_child = TargetTree.Child
+ if TargetTree.Data:
+ for item in temp_save_child:
+ if item.type == SECTION_TREE and not item.Data.OriData and item.Data.ExtHeader:
+ NewData += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.Data +
item.Data.PadData
+ elif item.type == SECTION_TREE and item.Data.OriData and not item.Data.ExtHeader:
+ NewData += struct2stream(item.Data.Header) + item.Data.OriData + item.Data.PadData
+ elif item.type == SECTION_TREE and item.Data.OriData and item.Data.ExtHeader:
+ NewData += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.OriData +
item.Data.PadData
+ elif item.type == FFS_FREE_SPACE:
+ NewData += item.Data.Data + item.Data.PadData
+ else:
+ NewData += struct2stream(item.Data.Header) + item.Data.Data + item.Data.PadData
+ if TargetTree.type == FFS_TREE:
+ New_Pad_Size = GetPadSize(len(NewData), 8)
+ Size_delta = len(NewData) - len(TargetTree.Data.Data)
+ ChangeSize(TargetTree, -Size_delta)
+ Delta_Pad_Size = len(TargetTree.Data.PadData) - New_Pad_Size
+ self.Remain_New_Free_Space += Delta_Pad_Size
+ TargetTree.Data.PadData = b'\xff' * New_Pad_Size
+ TargetTree.Data.ModCheckSum()
+ elif TargetTree.type == FV_TREE or TargetTree.type == SEC_FV_TREE and not pos:
+ if self.Remain_New_Free_Space:
+ if TargetTree.Data.Free_Space:
+ TargetTree.Data.Free_Space += self.Remain_New_Free_Space
+ NewData += self.Remain_New_Free_Space * b'\xff'
+ TargetTree.Child[-1].Data.Data += self.Remain_New_Free_Space * b'\xff'
+ else:
+ TargetTree.Data.Data += self.Remain_New_Free_Space * b'\xff'
+ New_Free_Space = BIOSTREE('FREE_SPACE')
+ New_Free_Space.type = FFS_FREE_SPACE
+ New_Free_Space.Data = FreeSpaceNode(b'\xff' * self.Remain_New_Free_Space)
+ TargetTree.insertChild(New_Free_Space)
+ self.Remain_New_Free_Space = 0
+ if TargetTree.type == SEC_FV_TREE:
+ Size_delta = len(NewData) + self.Remain_New_Free_Space - len(TargetTree.Data.Data)
+ TargetTree.Data.Header.FvLength += Size_delta
+ TargetTree.Data.ModFvExt()
+ TargetTree.Data.ModFvSize()
+ TargetTree.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetTree)
+ TargetTree.Data.ModCheckSum()
+ elif TargetTree.type == SECTION_TREE and TargetTree.Data.Type != 0x02:
+ New_Pad_Size = GetPadSize(len(NewData), 4)
+ Size_delta = len(NewData) - len(TargetTree.Data.Data)
+ ChangeSize(TargetTree, -Size_delta)
+ if TargetTree.NextRel:
+ Delta_Pad_Size = len(TargetTree.Data.PadData) - New_Pad_Size
+ self.Remain_New_Free_Space += Delta_Pad_Size
+ TargetTree.Data.PadData = b'\x00' * New_Pad_Size
+ TargetTree.Data.Data = NewData
+ if GuidTool:
+ ParPath = os.path.abspath(os.path.dirname(os.path.abspath(__file__))+os.path.sep+"..")
+ ToolPath = os.path.join(ParPath, r'FMMTConfig.ini')
+ guidtool = GUIDTools(ToolPath).__getitem__(struct2stream(GuidTool))
+ CompressedData = guidtool.pack(TargetTree.Data.Data)
+ if len(CompressedData) < len(TargetTree.Data.OriData):
+ New_Pad_Size = GetPadSize(len(CompressedData), 4)
+ Size_delta = len(CompressedData) - len(TargetTree.Data.OriData)
+ ChangeSize(TargetTree, -Size_delta)
+ if TargetTree.NextRel:
+ TargetTree.Data.PadData = b'\x00' * New_Pad_Size
+ self.Remain_New_Free_Space = len(TargetTree.Data.OriData) + len(TargetTree.Data.PadData) -
len(CompressedData) - New_Pad_Size
+ else:
+ TargetTree.Data.PadData = b''
+ self.Remain_New_Free_Space = len(TargetTree.Data.OriData) - len(CompressedData)
+ TargetTree.Data.OriData = CompressedData
+ elif len(CompressedData) == len(TargetTree.Data.OriData):
+ TargetTree.Data.OriData = CompressedData
+ elif len(CompressedData) > len(TargetTree.Data.OriData):
+ New_Pad_Size = GetPadSize(CompressedData, 4)
+ self.Remain_New_Free_Space = len(CompressedData) + New_Pad_Size - len(TargetTree.Data.OriData) -
len(TargetTree.Data.PadData)
+ Size_delta = len(TargetTree.Data.OriData) - len(CompressedData)
+ ChangeSize(TargetTree, -Size_delta)
+ if TargetTree.NextRel:
+ TargetTree.Data.PadData = b'\x00' * New_Pad_Size
+ TargetTree.Data.OriData = CompressedData
+ self.ModifyTest(TargetTree, self.Remain_New_Free_Space)
+ self.Status = False
+
+ def ModifyFvExtData(self, TreeNode) -> None:
+ FvExtData = b''
+ if TreeNode.Data.Header.ExtHeaderOffset:
+ FvExtHeader = struct2stream(TreeNode.Data.ExtHeader)
+ FvExtData += FvExtHeader
+ if TreeNode.Data.Header.ExtHeaderOffset and TreeNode.Data.ExtEntryExist:
+ FvExtEntry = struct2stream(TreeNode.Data.ExtEntry)
+ FvExtData += FvExtEntry
+ if FvExtData:
+ InfoNode = TreeNode.Child[0]
+ InfoNode.Data.Data = FvExtData + InfoNode.Data.Data[TreeNode.Data.ExtHeader.ExtHeaderSize:]
+ InfoNode.Data.ModCheckSum()
+
+ def ModifyTest(self, ParTree, Needed_Space: int) -> None:
+ if Needed_Space > 0:
+ if ParTree.type == FV_TREE or ParTree.type == SEC_FV_TREE:
+ ParTree.Data.Data = b''
+ Needed_Space = Needed_Space - ParTree.Data.Free_Space
+ if Needed_Space < 0:
+ ParTree.Child[-1].Data.Data = b'\xff' * (-Needed_Space)
+ ParTree.Data.Free_Space = (-Needed_Space)
+ self.Status = True
+ else:
+ if ParTree.type == FV_TREE:
+ self.Status = False
+ else:
+ BlockSize = ParTree.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - Needed_Space%BlockSize
+ if New_Add_Len % BlockSize:
+ ParTree.Child[-1].Data.Data = b'\xff' * New_Add_Len
+ ParTree.Data.Free_Space = New_Add_Len
+ Needed_Space += New_Add_Len
+ else:
+ ParTree.Child.remove(ParTree.Child[-1])
+ ParTree.Data.Free_Space = 0
+ ParTree.Data.Size += Needed_Space
+ ParTree.Data.Header.Fvlength = ParTree.Data.Size
+ for item in ParTree.Child:
+ if item.type == FFS_FREE_SPACE:
+ ParTree.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ ParTree.Data.ModFvExt()
+ ParTree.Data.ModFvSize()
+ ParTree.Data.ModExtHeaderData()
+ self.ModifyFvExtData(ParTree)
+ ParTree.Data.ModCheckSum()
+ elif ParTree.type == FFS_TREE:
+ ParTree.Data.Data = b''
+ for item in ParTree.Child:
+ if item.Data.OriData:
+ if item.Data.ExtHeader:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) +
item.Data.OriData + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header)+ item.Data.OriData + item.Data.PadData
+ else:
+ if item.Data.ExtHeader:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) +
item.Data.Data + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ ChangeSize(ParTree, -Needed_Space)
+ New_Pad_Size = GetPadSize(ParTree.Data.Size, 8)
+ Delta_Pad_Size = New_Pad_Size - len(ParTree.Data.PadData)
+ Needed_Space += Delta_Pad_Size
+ ParTree.Data.PadData = b'\xff' * GetPadSize(ParTree.Data.Size, 8)
+ ParTree.Data.ModCheckSum()
+ elif ParTree.type == SECTION_TREE:
+ OriData = ParTree.Data.Data
+ ParTree.Data.Data = b''
+ for item in ParTree.Child:
+ if item.type == SECTION_TREE and item.Data.ExtHeader and item.Data.Type != 0x02:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) +
item.Data.Data + item.Data.PadData
+ elif item.type == SECTION_TREE and item.Data.ExtHeader and item.Data.Type == 0x02:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) +
item.Data.OriData + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + item.Data.Data + item.Data.PadData
+ if ParTree.Data.Type == 0x02:
+ ParTree.Data.Size += Needed_Space
+ ParPath = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
+ ToolPath = os.path.join(os.path.dirname(ParPath), r'FMMTConfig.ini')
+ guidtool =
GUIDTools(ToolPath).__getitem__(struct2stream(ParTree.Data.ExtHeader.SectionDefinitionGuid))
+ CompressedData = guidtool.pack(ParTree.Data.Data)
+ Needed_Space = len(CompressedData) - len(ParTree.Data.OriData)
+ ParTree.Data.OriData = CompressedData
+ New_Size = ParTree.Data.HeaderLength + len(CompressedData)
+ ParTree.Data.Header.Size[0] = New_Size % (16**2)
+ ParTree.Data.Header.Size[1] = New_Size % (16**4) //(16**2)
+ ParTree.Data.Header.Size[2] = New_Size // (16**4)
+ if ParTree.NextRel:
+ New_Pad_Size = GetPadSize(New_Size, 4)
+ Delta_Pad_Size = New_Pad_Size - len(ParTree.Data.PadData)
+ ParTree.Data.PadData = b'\x00' * New_Pad_Size
+ Needed_Space += Delta_Pad_Size
+ else:
+ ParTree.Data.PadData = b''
+ elif Needed_Space:
+ ChangeSize(ParTree, -Needed_Space)
+ New_Pad_Size = GetPadSize(ParTree.Data.Size, 4)
+ Delta_Pad_Size = New_Pad_Size - len(ParTree.Data.PadData)
+ Needed_Space += Delta_Pad_Size
+ ParTree.Data.PadData = b'\x00' * New_Pad_Size
+ NewParTree = ParTree.Parent
+ ROOT_TYPE = [ROOT_FV_TREE, ROOT_FFS_TREE, ROOT_SECTION_TREE, ROOT_TREE]
+ if NewParTree and NewParTree.type not in ROOT_TYPE:
+ self.ModifyTest(NewParTree, Needed_Space)
+ else:
+ self.Status = True
+
+ def ReplaceFfs(self) -> bool:
+ TargetFv = self.TargetFfs.Parent
+ # If the Fv Header Attributes is EFI_FVB2_ERASE_POLARITY, Child Ffs Header State need be reversed.
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ self.NewFfs.Data.Header.State = c_uint8(
+ ~self.NewFfs.Data.Header.State)
+ # NewFfs parsing will not calculate the PadSize, thus recalculate.
+ self.NewFfs.Data.PadData = b'\xff' * GetPadSize(self.NewFfs.Data.Size, 8)
+ if self.NewFfs.Data.Size > self.TargetFfs.Data.Size:
+ Needed_Space = self.NewFfs.Data.Size + len(self.NewFfs.Data.PadData) - self.TargetFfs.Data.Size -
len(self.TargetFfs.Data.PadData)
+ # If TargetFv have enough free space, just move part of the free space to NewFfs.
+ if TargetFv.Data.Free_Space >= Needed_Space:
+ # Modify TargetFv Child info and BiosTree.
+ TargetFv.Child[-1].Data.Data = b'\xff' * (TargetFv.Data.Free_Space - Needed_Space)
+ TargetFv.Data.Free_Space -= Needed_Space
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ # Modify TargetFv Header and ExtHeader info.
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ # return the Status
+ self.Status = True
+ # If TargetFv do not have enough free space, need move part of the free space of TargetFv's parent Fv to
TargetFv/NewFfs.
+ else:
+ if TargetFv.type == FV_TREE:
+ self.Status = False
+ else:
+ # Recalculate TargetFv needed space to keep it match the BlockSize setting.
+ Needed_Space -= TargetFv.Data.Free_Space
+ BlockSize = TargetFv.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - Needed_Space%BlockSize
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ if New_Add_Len % BlockSize:
+ TargetFv.Child[-1].Data.Data = b'\xff' * New_Add_Len
+ TargetFv.Data.Free_Space = New_Add_Len
+ Needed_Space += New_Add_Len
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ TargetFv.Child.remove(self.TargetFfs)
+ else:
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.Data.Free_Space = 0
+ TargetFv.insertChild(self.NewFfs)
+ # Encapsulate the Fv Data for update.
+ TargetFv.Data.Data = b''
+ for item in TargetFv.Child:
+ if item.type == FFS_FREE_SPACE:
+ TargetFv.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ TargetFv.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ TargetFv.Data.Size += Needed_Space
+ # Modify TargetFv Data Header and ExtHeader info.
+ TargetFv.Data.Header.FvLength = TargetFv.Data.Size
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ # Start free space calculating and moving process.
+ self.ModifyTest(TargetFv.Parent, Needed_Space)
+ else:
+ New_Free_Space = self.TargetFfs.Data.Size - self.NewFfs.Data.Size
+ # If TargetFv already have free space, move the new free space into it.
+ if TargetFv.Data.Free_Space:
+ TargetFv.Child[-1].Data.Data += b'\xff' * New_Free_Space
+ TargetFv.Data.Free_Space += New_Free_Space
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ self.Status = True
+ # If TargetFv do not have free space, create free space for Fv.
+ else:
+ New_Free_Space_Tree = BIOSTREE('FREE_SPACE')
+ New_Free_Space_Tree.type = FFS_FREE_SPACE
+ New_Free_Space_Tree.Data = FfsNode(b'\xff' * New_Free_Space)
+ TargetFv.Data.Free_Space = New_Free_Space
+ TargetFv.insertChild(New_Free_Space)
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ self.Status = True
+ # Modify TargetFv Header and ExtHeader info.
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ return self.Status
+
+ def AddFfs(self) -> bool:
+ # NewFfs parsing will not calculate the PadSize, thus recalculate.
+ self.NewFfs.Data.PadData = b'\xff' * GetPadSize(self.NewFfs.Data.Size, 8)
+ if self.TargetFfs.type == FFS_FREE_SPACE:
+ TargetLen = self.NewFfs.Data.Size + len(self.NewFfs.Data.PadData) - self.TargetFfs.Data.Size -
len(self.TargetFfs.Data.PadData)
+ TargetFv = self.TargetFfs.Parent
+ # If the Fv Header Attributes is EFI_FVB2_ERASE_POLARITY, Child Ffs Header State need be reversed.
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ self.NewFfs.Data.Header.State = c_uint8(
+ ~self.NewFfs.Data.Header.State)
+ # If TargetFv have enough free space, just move part of the free space to NewFfs, split free space to NewFfs
and new free space.
+ if TargetLen < 0:
+ self.Status = True
+ self.TargetFfs.Data.Data = b'\xff' * (-TargetLen)
+ TargetFv.Data.Free_Space = (-TargetLen)
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ TargetFv.insertChild(self.NewFfs, -1)
+ ModifyFfsType(self.NewFfs)
+ elif TargetLen == 0:
+ self.Status = True
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs)
+ ModifyFfsType(self.NewFfs)
+ # If TargetFv do not have enough free space, need move part of the free space of TargetFv's parent Fv to
TargetFv/NewFfs.
+ else:
+ if TargetFv.type == FV_TREE:
+ self.Status = False
+ elif TargetFv.type == SEC_FV_TREE:
+ # Recalculate TargetFv needed space to keep it match the BlockSize setting.
+ BlockSize = TargetFv.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - TargetLen%BlockSize
+ if New_Add_Len % BlockSize:
+ self.TargetFfs.Data.Data = b'\xff' * New_Add_Len
+ self.TargetFfs.Data.Size = New_Add_Len
+ TargetLen += New_Add_Len
+ TargetFv.insertChild(self.NewFfs, -1)
+ TargetFv.Data.Free_Space = New_Add_Len
+ else:
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs)
+ TargetFv.Data.Free_Space = 0
+ ModifyFfsType(self.NewFfs)
+ TargetFv.Data.Data = b''
+ for item in TargetFv.Child:
+ if item.type == FFS_FREE_SPACE:
+ TargetFv.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ TargetFv.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ # Encapsulate the Fv Data for update.
+ TargetFv.Data.Size += TargetLen
+ TargetFv.Data.Header.FvLength = TargetFv.Data.Size
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ # Start free space calculating and moving process.
+ self.ModifyTest(TargetFv.Parent, TargetLen)
+ else:
+ # If TargetFv do not have free space, need directly move part of the free space of TargetFv's parent Fv to
TargetFv/NewFfs.
+ TargetLen = self.NewFfs.Data.Size + len(self.NewFfs.Data.PadData)
+ TargetFv = self.TargetFfs.Parent
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ self.NewFfs.Data.Header.State = c_uint8(
+ ~self.NewFfs.Data.Header.State)
+ if TargetFv.type == FV_TREE:
+ self.Status = False
+ elif TargetFv.type == SEC_FV_TREE:
+ BlockSize = TargetFv.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - TargetLen%BlockSize
+ if New_Add_Len % BlockSize:
+ New_Free_Space = BIOSTREE('FREE_SPACE')
+ New_Free_Space.type = FFS_FREE_SPACE
+ New_Free_Space.Data = FreeSpaceNode(b'\xff' * New_Add_Len)
+ TargetLen += New_Add_Len
+ TargetFv.Data.Free_Space = New_Add_Len
+ TargetFv.insertChild(self.NewFfs)
+ TargetFv.insertChild(New_Free_Space)
+ else:
+ TargetFv.insertChild(self.NewFfs)
+ ModifyFfsType(self.NewFfs)
+ TargetFv.Data.Data = b''
+ for item in TargetFv.Child:
+ if item.type == FFS_FREE_SPACE:
+ TargetFv.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ TargetFv.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ TargetFv.Data.Size += TargetLen
+ TargetFv.Data.Header.FvLength = TargetFv.Data.Size
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ self.ModifyTest(TargetFv.Parent, TargetLen)
+ return self.Status
+
+ def DeleteFfs(self) -> bool:
+ Delete_Ffs = self.TargetFfs
+ Delete_Fv = Delete_Ffs.Parent
+ Add_Free_Space = Delete_Ffs.Data.Size + len(Delete_Ffs.Data.PadData)
+ if Delete_Fv.Data.Free_Space:
+ if Delete_Fv.type == SEC_FV_TREE:
+ Used_Size = Delete_Fv.Data.Size - Delete_Fv.Data.Free_Space - Add_Free_Space
+ BlockSize = Delete_Fv.Data.Header.BlockMap[0].Length
+ New_Free_Space = BlockSize - Used_Size % BlockSize
+ self.Remain_New_Free_Space += Delete_Fv.Data.Free_Space + Add_Free_Space - New_Free_Space
+ Delete_Fv.Child[-1].Data.Data = New_Free_Space * b'\xff'
+ Delete_Fv.Data.Free_Space = New_Free_Space
+ else:
+ Used_Size = Delete_Fv.Data.Size - Delete_Fv.Data.Free_Space - Add_Free_Space
+ Delete_Fv.Child[-1].Data.Data += Add_Free_Space * b'\xff'
+ Delete_Fv.Data.Free_Space += Add_Free_Space
+ New_Free_Space = Delete_Fv.Data.Free_Space + Add_Free_Space
+ else:
+ if Delete_Fv.type == SEC_FV_TREE:
+ Used_Size = Delete_Fv.Data.Size - Add_Free_Space
+ BlockSize = Delete_Fv.Data.Header.BlockMap[0].Length
+ New_Free_Space = BlockSize - Used_Size % BlockSize
+ self.Remain_New_Free_Space += Add_Free_Space - New_Free_Space
+ Add_Free_Space = New_Free_Space
+ else:
+ Used_Size = Delete_Fv.Data.Size - Add_Free_Space
+ New_Free_Space = Add_Free_Space
+ New_Free_Space_Info = FfsNode(Add_Free_Space * b'\xff')
+ New_Free_Space_Info.Data = Add_Free_Space * b'\xff'
+ New_Ffs_Tree = BIOSTREE(New_Free_Space_Info.Name)
+ New_Ffs_Tree.type = FFS_FREE_SPACE
+ New_Ffs_Tree.Data = New_Free_Space_Info
+ Delete_Fv.insertChild(New_Ffs_Tree)
+ Delete_Fv.Data.Free_Space = Add_Free_Space
+ Delete_Fv.Child.remove(Delete_Ffs)
+ Delete_Fv.Data.Header.FvLength = Used_Size + New_Free_Space
+ Delete_Fv.Data.ModFvExt()
+ Delete_Fv.Data.ModFvSize()
+ Delete_Fv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(Delete_Fv)
+ Delete_Fv.Data.ModCheckSum()
+ self.CompressData(Delete_Fv)
+ self.Status = True
+ return self.Status
diff --git a/BaseTools/Source/Python/FMMT/core/GuidTools.py b/BaseTools/Source/Python/FMMT/core/GuidTools.py
new file mode 100644
index 000000000000..287b949cc32f
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/GuidTools.py
@@ -0,0 +1,152 @@
+## @file
+# This file is used to define the FMMT dependent external tool management class.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import glob
+import logging
+import os
+import shutil
+import sys
+import tempfile
+import uuid
+from PI.Common import *
+from utils.FmmtLogger import FmmtLogger as logger
+import subprocess
+
+def ExecuteCommand(cmd: list) -> None:
+ subprocess.run(cmd,stdout=subprocess.DEVNULL)
+
+class GUIDTool:
+ def __init__(self, guid: str, short_name: str, command: str) -> None:
+ self.guid: str = guid
+ self.short_name: str = short_name
+ self.command: str = command
+
+ def pack(self, buffer: bytes) -> bytes:
+ """
+ compress file.
+ """
+ tool = self.command
+ if tool:
+ tmp = tempfile.mkdtemp(dir=os.environ.get('tmp'))
+ ToolInputFile = os.path.join(tmp, "pack_uncompress_sec_file")
+ ToolOuputFile = os.path.join(tmp, "pack_sec_file")
+ try:
+ file = open(ToolInputFile, "wb")
+ file.write(buffer)
+ file.close()
+ command = [tool, '-e', '-o', ToolOuputFile,
+ ToolInputFile]
+ ExecuteCommand(command)
+ buf = open(ToolOuputFile, "rb")
+ res_buffer = buf.read()
+ except Exception as msg:
+ logger.error(msg)
+ return ""
+ else:
+ buf.close()
+ if os.path.exists(tmp):
+ shutil.rmtree(tmp)
+ return res_buffer
+ else:
+ logger.error(
+ "Error parsing section: EFI_SECTION_GUID_DEFINED cannot be parsed at this time.")
+ logger.info("Its GUID is: %s" % self.guid)
+ return ""
+
+
+ def unpack(self, buffer: bytes) -> bytes:
+ """
+ buffer: remove common header
+ uncompress file
+ """
+ tool = self.command
+ if tool:
+ tmp = tempfile.mkdtemp(dir=os.environ.get('tmp'))
+ ToolInputFile = os.path.join(tmp, "unpack_sec_file")
+ ToolOuputFile = os.path.join(tmp, "unpack_uncompress_sec_file")
+ try:
+ file = open(ToolInputFile, "wb")
+ file.write(buffer)
+ file.close()
+ command = [tool, '-d', '-o', ToolOuputFile, ToolInputFile]
+ ExecuteCommand(command)
+ buf = open(ToolOuputFile, "rb")
+ res_buffer = buf.read()
+ except Exception as msg:
+ logger.error(msg)
+ return ""
+ else:
+ buf.close()
+ if os.path.exists(tmp):
+ shutil.rmtree(tmp)
+ return res_buffer
+ else:
+ logger.error("Error parsing section: EFI_SECTION_GUID_DEFINED cannot be parsed at this time.")
+ logger.info("Its GUID is: %s" % self.guid)
+ return ""
+
+class GUIDTools:
+ '''
+ GUIDTools is responsible for reading FMMTConfig.ini, verify the tools and provide interfaces to access those tools.
+ '''
+ default_tools = {
+ struct2stream(ModifyGuidFormat("a31280ad-481e-41b6-95e8-127f4c984779")): GUIDTool("a31280ad-481e-41b6-95e8-
127f4c984779", "TIANO", "TianoCompress"),
+ struct2stream(ModifyGuidFormat("ee4e5898-3914-4259-9d6e-dc7bd79403cf")): GUIDTool("ee4e5898-3914-4259-9d6e-
dc7bd79403cf", "LZMA", "LzmaCompress"),
+ struct2stream(ModifyGuidFormat("fc1bcdb0-7d31-49aa-936a-a4600d9dd083")): GUIDTool("fc1bcdb0-7d31-49aa-936a-
a4600d9dd083", "CRC32", "GenCrc32"),
+ struct2stream(ModifyGuidFormat("d42ae6bd-1352-4bfb-909a-ca72a6eae889")): GUIDTool("d42ae6bd-1352-4bfb-909a-
ca72a6eae889", "LZMAF86", "LzmaF86Compress"),
+ struct2stream(ModifyGuidFormat("3d532050-5cda-4fd0-879e-0f7f630d5afb")): GUIDTool("3d532050-5cda-4fd0-879e-
0f7f630d5afb", "BROTLI", "BrotliCompress"),
+ }
+
+ def __init__(self, tooldef_file: str=None) -> None:
+ self.dir = os.path.dirname(__file__)
+ self.tooldef_file = tooldef_file if tooldef_file else os.path.join(
+ self.dir, "FMMTConfig.ini")
+ self.tooldef = dict()
+ self.load()
+
+ def VerifyTools(self) -> None:
+ """
+ Verify Tools and Update Tools path.
+ """
+ path_env = os.environ.get("PATH")
+ path_env_list = path_env.split(os.pathsep)
+ path_env_list.append(os.path.dirname(__file__))
+ path_env_list = list(set(path_env_list))
+ for tool in self.tooldef.values():
+ cmd = tool.command
+ if os.path.isabs(cmd):
+ if not os.path.exists(cmd):
+ print("Tool Not found %s" % cmd)
+ else:
+ for syspath in path_env_list:
+ if glob.glob(os.path.join(syspath, cmd+"*")):
+ break
+ else:
+ print("Tool Not found %s" % cmd)
+
+ def load(self) -> None:
+ if os.path.exists(self.tooldef_file):
+ with open(self.tooldef_file, "r") as fd:
+ config_data = fd.readlines()
+ for line in config_data:
+ try:
+ guid, short_name, command = line.split()
+ new_format_guid = struct2stream(ModifyGuidFormat(guid.strip()))
+ self.tooldef[new_format_guid] = GUIDTool(
+ guid.strip(), short_name.strip(), command.strip())
+ except:
+ print("GuidTool load error!")
+ continue
+ else:
+ self.tooldef.update(self.default_tools)
+
+ self.VerifyTools()
+
+ def __getitem__(self, guid) -> None:
+ return self.tooldef.get(guid)
+
+
+guidtools = GUIDTools()
diff --git a/BaseTools/Source/Python/FMMT/utils/FmmtLogger.py b/BaseTools/Source/Python/FMMT/utils/FmmtLogger.py
new file mode 100644
index 000000000000..5289fc27049a
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/utils/FmmtLogger.py
@@ -0,0 +1,18 @@
+## @file
+# This file is used to define the Fmmt Logger.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+
+##
+
+import logging
+import sys
+
+FmmtLogger = logging.getLogger('FMMT')
+FmmtLogger.setLevel(logging.INFO)
+
+lh=logging.StreamHandler(sys.stdout)
+lf=logging.Formatter("%(levelname)-8s: %(message)s")
+lh.setFormatter(lf)
+FmmtLogger.addHandler(lh)
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py b/BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py
new file mode 100644
index 000000000000..83a31aeea3fe
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py
@@ -0,0 +1,54 @@
+## @file
+# This file is used to define the printer for Bios layout.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+def GetFormatter(layout_format: str):
+ if layout_format == 'json':
+ return JsonFormatter()
+ elif layout_format == 'yaml':
+ return YamlFormatter()
+ elif layout_format == 'html':
+ return HtmlFormatter()
+ else:
+ return TxtFormatter()
+
+class Formatter(object):
+ def dump(self, layoutdict, layoutlist, outputfile: str=None) -> None:
+ raise NotImplemented
+
+class JsonFormatter(Formatter):
+ def dump(self,layoutdict: dict, layoutlist: list, outputfile: str=None) -> None:
+ try:
+ import json
+ except:
+ TxtFormatter().dump(layoutdict, layoutlist, outputfile)
+ return
+ print(outputfile)
+ if outputfile:
+ with open(outputfile,"w") as fw:
+ json.dump(layoutdict, fw, indent=2)
+ else:
+ print(json.dumps(layoutdict,indent=2))
+
+class TxtFormatter(Formatter):
+ def LogPrint(self,layoutlist: list) -> None:
+ for item in layoutlist:
+ print(item)
+ print('\n')
+
+ def dump(self,layoutdict: dict, layoutlist: list, outputfile: str=None) -> None:
+ print(outputfile)
+ with open(outputfile, "w") as f:
+ for item in layoutlist:
+ f.writelines(item + '\n')
+
+class YamlFormatter(Formatter):
+ def dump(self,layoutdict, layoutlist, outputfile = None):
+ TxtFormatter().dump(layoutdict, layoutlist, outputfile)
+
+class HtmlFormatter(Formatter):
+ def dump(self,layoutdict, layoutlist, outputfile = None):
+ TxtFormatter().dump(layoutdict, layoutlist, outputfile)
\ No newline at end of file
--
2.27.0.windows.1





Re: Progress on getting Uncrustify working for EDK2?

Michael D Kinney
 

Hello,

 

Good information in this thread on code style.

 

Some of the topics apply to uncrustify and some are out of scope for what uncrustify can fix on its own.

 

I would like to focus on a date to convert all source code in edk2 repo using the uncrustify tool and to capture the other code style topics into their own thread and bugzillas.

 

I would like to propose a conversion date for uncrustify immediately after the edk2-stable202111 release on 2021-11-26.

 

I have been working with Michael Kubacki on a build comparison tool that verifies that the build generate the same obj/lib/dll/efi/fv/fd files before and after the uncrustify changes.  We would run and publish the results from this tool before committing the changes.

 

We need TianoCore community approval of the following:

 

  1. Approve format of C source generated by the uncrustify.
  2. Approve uncrustify changes right after edk2-stable-202111 release. 
    1. Extend code freeze until these changes are committed.
  3. Require use of uncrustify tool before submitting patch review emails or PRs.
    1. The required version would be a formally released version  from the fork maintained by Michael Kubacki until the changes can be upstreamed.
    2. https://dev.azure.com/projectmu/Uncrustify
  4. Add EDK II CI check to verify that all PRs submitted exactly match uncrustified version.  Reject PRs that do not match exactly.
  5. Implement a git hook available that would automatically run uncristufy before committing changes to a local branch of an edk2 repo.

 

Thanks,

 

Mike

 

From: Andrew Fish <afish@...>
Sent: Thursday, October 7, 2021 2:09 PM
To: Marvin Häuser <mhaeuser@...>
Cc: edk2-devel-groups-io <devel@edk2.groups.io>; Kinney, Michael D <michael.d.kinney@...>; Leif Lindholm <leif@...>; mikuback@...; rebecca@...; Michael Kubacki <Michael.Kubacki@...>; Bret Barkelew <Bret.Barkelew@...>
Subject: Re: [edk2-devel] Progress on getting Uncrustify working for EDK2?

 

 



On Oct 7, 2021, at 1:43 PM, Marvin Häuser <mhaeuser@...> wrote:

 

Hey Mike,
Hey Andrew,

I'll just reply to both mails at once :)

On 07/10/2021 19:36, Andrew Fish wrote:




On Oct 7, 2021, at 1:19 PM, Michael D Kinney <michael.d.kinney@...> wrote:

Hi Marvin,

Some comments below.

Mike


-----Original Message-----
From:devel@edk2.groups.io<devel@edk2.groups.io> On Behalf Of Marvin Häuser
Sent: Thursday, October 7, 2021 11:31 AM
To: Leif Lindholm <leif@...>;devel@edk2.groups.io;mikuback@...
Cc:rebecca@...; Michael Kubacki <Michael.Kubacki@...>; Bret Barkelew <Bret.Barkelew@...>;
Kinney, Michael D <michael.d.kinney@...>
Subject: Re: [edk2-devel] Progress on getting Uncrustify working for EDK2?

Good day,

+1, but while you're at it, can we have arguments not align to the
function name...

  Status = Test (
             a
             );

... but to the next natural indentation level?

  Status = Test (
    a
    );

Basically no IDE I have seen supports EDK II's style, and I wouldn't be
keen on writing known-broken style to then rely on Uncrustify to fix it.

I also have heard some controversy regarding casts off-list, where some
prefer no spaces after casts to stress the evaluation order, and some
prefer spaces to have clearer visuals (as a cast *ideally* would be
something rare that requires good justification). Just throwing that out
there.


For things unrelated to autoformat (so semi-offtopic) but still relevant
to the coding spec:

1. Allow STATIC functions (if the debugging concerns are still relevant,
there could be another level of indirection, like RELEASE_STATIC)?


Debugging concerns are no longer relevant.  The suggestion in the BZ below
is to remove the STATIC macro and allow EDK II sources to add 'static'
to any functions it makes sense to use on.

https://bugzilla.tianocore.org/show_bug.cgi?id=1766


Thanks! I'd keep STATIC actually just for the sake of not doing no-op changes that do not really do anything and for consistency with CONST, but whatever works really.





2. Allow variable assignments on definition (basically non-static CONST
variables are banned...)?


Are referring to use of pre-initialized CONST variables declared within
a function?  I think Bret brought this topic up when implementing some
unit tests and the suggestion to pass ECCC was to promote them to
pre-initialized CONST global variables.


Yes.



The challenges we have seen in the past with pre-initialized variables within
a function is that they can cause compilers to inject use of memcpy() calls,
especially if the variable being initialized on the stack is a structure.
These cause build breaks today.


This issue is independent of CONST. I’m not sure a coding style tool is smart enough to catch this generically? You need an understanding of C types to know if the local variable assignment is going to trigger a memcpy().

What I’ve seen in the real world is the firmware compiles with -Os or LTO to fit int he ROM for DEBUG and RELEASE, and the optimizer optimizes away the call to memcpy. Then if you try to build NOOPT (or over ride the compiler flags on an individual driver/lib) you fail to link as only the NOOPT build injects the memcpy.


+1



Thus I think the best way to enforce this rule is to compile a project NOOPT. I’m trying to remember are there flags to built to tell it to compile and skip the FD construction? Maybe we should advocate platforms add a NOOPT build target that just compiles the code, but does not create the FD?


I know there were stability concerns with intrinsics in the past, but memcpy() is in the standard, and the rest remained stable to my knowledge. Maybe it's time to fix the issues at the root? Works for us:
https://github.com/acidanthera/OpenCorePkg/tree/master/Library/OcCompilerIntrinsicsLib

 

Marvin,

 

Good point. This would make the rule moot. So maybe just removing the requirement would be the easiest long term fix. 

 

Other embedded projects I know of do this too, and as you point out the compilers keep these APIs standard for folks the provide their own runtimes.

 

Thanks,

 

Andrew Fish



Best regards,
Marvin



Thanks,

Andrew Fish





3. Allow variable declarations at any scope (I had some nasty shadowing
bugs before, probably prohibit shadowing with warnings)?


By shadowing do you mean the declaration of the same variable name in
multiple scoped within the same function?



4. Require that exactly all function declarations and all function
definitions with no prior declaration must be documented (first
direction is enforcing docs, second is prohibiting doc duplication, I've
seen them go out-of-sync plenty of times)?


I agree that this can reduce duplication and sync issues.  The uncrustify
tool being discussed here could not help clean this up or enforce this
type of rule.  It is a good topic, but may need to be split out into its
own thread.



The latter bunch would not require any autoformat rules or reformatation
of existing code, but would be target only new submissions in my
opinion. Thoughts?


Thanks for your efforts!

Best regards,
Marvin


Am 07.10.2021 um 12:48 schrieb Leif Lindholm:

Hi Michael,

Apologies, I've owed you a response (promised off-list) for a while
now.

First, let me say I hugely appreciate this effort. Apart from aligning
the codebase(s), this will reduce manual reviewing effort
substantially, as well as cutting down on number of rework cycles for
developers.

Looking at the changes to (well, the comments in) uncrustify, this
seems to be constrained to:
- Newline after '(' for multi-line function calls.
- Dealing with "(("/"))" for DEBUG macros.
- Function pointer typedefs:
  - typedef\nEFIAPI
  - closing parentheses indentation

I don't think I've made any secret over the years that I am not a
massive fan of the EDK2 coding style in general. So I think for any
of its quirks that are substantial enough that they require not just
custom configuration but actual new function added to existing code
conformance tools, this would be an excellent point to sanitise the
coding style instead.

Taking these in order:

Newline after '('
-----------------
I think we already reached a level of flexibility around this, where
we don't actually enforce this (or single argument per
line). Personally, I'd be happy to update the coding style as
required instead.

DEBUG macro parentheses
-----------------------
How does uncrustify treat DEBUG macros without this modification?
Do we start getting everything turned into multi-level indented
multi-line statements without this change?

Function pointer typedefs:
--------------------------
I don't see that function pointer typedefs need to rigidly follow the
same pattern as the declaration of functions implementing them. Could
we update the coding style (if needed) instead?

Best Regards,

Leif

On Mon, Aug 16, 2021 at 16:00:38 -0400, Michael Kubacki wrote:

The edk2 branch was created here:
https://github.com/makubacki/edk2/tree/uncrustify_poc_2

We have a Project Mu fork with custom changes to the Uncrustify tool to help
comply with EDK II formatting here:
https://dev.azure.com/projectmu/_git/Uncrustify

The latest information about the status and how to experiment with the
configuration file and the tool are in that fork:

https://dev.azure.com/projectmu/Uncrustify/_wiki/wikis/Uncrustify.wiki/1/Project-Mu-(EDK-II)-Fork-Readme


That said, I have also finished a CI plugin to run Uncrustify that should be
ready soon to initially deploy in Project Mu. Before doing so, I am trying
to settle on an initial configuration file that less strictly but more
reliably formats the code than in the examples in those branches. For
example, remove heuristics that when run against the same set of code
multiple times can produce different results. An example would be a rule
that reformats code because it exceeds a specified column width on one run
but on the next run that reformatted code triggers a different rule to
further align the code and so on. At least initially, some rules might be
tweaked in a more conservative approach that can be tightened in the future.
Once this configuration file is ready, we will baseline Project Mu code as
an example and turn on the plugin. The CI plugin runs Uncrustify against
modified files and if there's any changes, indicating a formatting
deviation, the diff chunks are saved in a log so they can be viewed as a
build artifact.

I am making progress on the updated config file and I should be able to post
a "uncrustify_poc_3" branch soon with the results.

Regarding indentation, Marvin is right that Uncrustify cannot support edk2
indentation style out-of-box. Some changes are made in that fork to handle
the formatting. At this point, it can handle the indentation in the cases
I've seen. Uncrustify does potentially give us the ability to massively
deploy changes across the codebase in case a decision were made to change
the style.

Thanks,
Michael

On 8/16/2021 3:39 PM, Marvin Häuser wrote:

Hey Rebecca,

I think even Uncrustify has issues with the EDK II indentation style.
You might want to check the UEFI Talkbox Discord server, I had a brief
chat with Michael about it there. I don't think realistically any tool
supports EDK II's indentation style however, so I'd propose it is
changed. This could be for new submissions only, or actually the entire
codebase could be reformatted at once with a good tool setup. While this
screws with git blame, the (to my understanding) decided on CRLF -> LF
change does that anyway, so at least two evils could be dealt with in
one go really.

Best regards,
Marvin

On 16/08/2021 21:34, Rebecca Cran wrote:


cc devel@ .

On 8/16/21 1:33 PM, Rebecca Cran wrote:


I noticed a message on Twitter about an idea of using Uncrustify
for EDK2 instead of the ECC tool, and came across https://www.mail-

archive.com/search?l@...&q=subject:%22Re%5C%3A+%5C%5Bedk2%5C-
devel%5C%5D+TianoCore+Community+Meeting+Minutes+%5C-+2%5C%2F4%22&o=newest&f=1

.

I was wondering if there's been any progress on it that I could
check out?


Michael Kubacki: in that message, you said:

"I'm planning to put up a branch that we can use as a reference
for a conversation around uncrustify in the next couple of
weeks."


Did you end up creating that branch, and if so could you provide
a link to it please?


--
Rebecca Cran

 
















 


[PATCH 1/1] BaseTools: Add FMMT Tool

Yuwei Chen
 

From: Bob Feng <bob.c.feng@...>

The FMMT python tool is used for firmware files operation, which has
the Fv/FFs-based 'View'&'Add'&'Delete'&'Replace' operation function.
Currently the FMMT C tool is saved in edk2-staging repo, but its
quality and coding style can't meet the Edk2 quality, which is hard to
maintain (Hard/Duplicate Code; Regression bugs; Restrict usage).
The new Python version keeps same functions with origin C version. It
has higher quality and better coding style, and it is much easier to
extend new functions and to maintain.

RFC: https://edk2.groups.io/g/devel/message/82877
Staging Link: https://github.com/tianocore/edk2-staging/tree/PyFMMT

Cc: Bob Feng <bob.c.feng@...>
Cc: Liming Gao <gaoliming@...>
Signed-off-by: Yuwei Chen <yuwei.chen@...>
---
BaseTools/BinWrappers/PosixLike/FMMT | 14 +
BaseTools/BinWrappers/WindowsLike/FMMT.bat | 4 +
BaseTools/Source/Python/FMMT/FMMT.py | 117 ++++
BaseTools/Source/Python/FMMT/FMMTConfig.ini | 5 +
.../Python/FMMT/Img/FirmwareVolumeFormat.png | Bin 0 -> 29515 bytes
.../Source/Python/FMMT/Img/NodeTreeFormat.png | Bin 0 -> 79906 bytes
BaseTools/Source/Python/FMMT/PI/Common.py | 81 +++
.../Source/Python/FMMT/PI/FfsFileHeader.py | 66 +++
BaseTools/Source/Python/FMMT/PI/FvHeader.py | 112 ++++
.../Source/Python/FMMT/PI/SectionHeader.py | 110 ++++
BaseTools/Source/Python/FMMT/PI/__init__.py | 6 +
BaseTools/Source/Python/FMMT/README.md | 180 ++++++
BaseTools/Source/Python/FMMT/__init__.py | 0
.../Python/FMMT/core/BinaryFactoryProduct.py | 371 +++++++++++++
BaseTools/Source/Python/FMMT/core/BiosTree.py | 199 +++++++
.../Source/Python/FMMT/core/BiosTreeNode.py | 191 +++++++
.../Source/Python/FMMT/core/FMMTOperation.py | 140 +++++
.../Source/Python/FMMT/core/FMMTParser.py | 86 +++
.../Source/Python/FMMT/core/FvHandler.py | 521 ++++++++++++++++++
.../Source/Python/FMMT/core/GuidTools.py | 152 +++++
.../Source/Python/FMMT/utils/FmmtLogger.py | 18 +
.../Source/Python/FMMT/utils/FvLayoutPrint.py | 54 ++
22 files changed, 2427 insertions(+)
create mode 100644 BaseTools/BinWrappers/PosixLike/FMMT
create mode 100644 BaseTools/BinWrappers/WindowsLike/FMMT.bat
create mode 100644 BaseTools/Source/Python/FMMT/FMMT.py
create mode 100644 BaseTools/Source/Python/FMMT/FMMTConfig.ini
create mode 100644 BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png
create mode 100644 BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png
create mode 100644 BaseTools/Source/Python/FMMT/PI/Common.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/FvHeader.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/SectionHeader.py
create mode 100644 BaseTools/Source/Python/FMMT/PI/__init__.py
create mode 100644 BaseTools/Source/Python/FMMT/README.md
create mode 100644 BaseTools/Source/Python/FMMT/__init__.py
create mode 100644 BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
create mode 100644 BaseTools/Source/Python/FMMT/core/BiosTree.py
create mode 100644 BaseTools/Source/Python/FMMT/core/BiosTreeNode.py
create mode 100644 BaseTools/Source/Python/FMMT/core/FMMTOperation.py
create mode 100644 BaseTools/Source/Python/FMMT/core/FMMTParser.py
create mode 100644 BaseTools/Source/Python/FMMT/core/FvHandler.py
create mode 100644 BaseTools/Source/Python/FMMT/core/GuidTools.py
create mode 100644 BaseTools/Source/Python/FMMT/utils/FmmtLogger.py
create mode 100644 BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py

diff --git a/BaseTools/BinWrappers/PosixLike/FMMT b/BaseTools/BinWrappers/PosixLike/FMMT
new file mode 100644
index 000000000000..5f5519e1e1b6
--- /dev/null
+++ b/BaseTools/BinWrappers/PosixLike/FMMT
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+#python `dirname $0`/RunToolFromSource.py `basename $0` $*
+
+# If a ${PYTHON_COMMAND} command is available, use it in preference to python
+if command -v ${PYTHON_COMMAND} >/dev/null 2>&1; then
+ python_exe=${PYTHON_COMMAND}
+fi
+
+full_cmd=${BASH_SOURCE:-$0} # see http://mywiki.wooledge.org/BashFAQ/028 for a discussion of why $0 is not a good choice here
+dir=$(dirname "$full_cmd")
+cmd=${full_cmd##*/}
+
+export PYTHONPATH="$dir/../../Source/Python/FMMT:$dir/../../Source/Python${PYTHONPATH:+:"$PYTHONPATH"}"
+exec "${python_exe:-python}" -m $cmd.$cmd "$@"
diff --git a/BaseTools/BinWrappers/WindowsLike/FMMT.bat b/BaseTools/BinWrappers/WindowsLike/FMMT.bat
new file mode 100644
index 000000000000..259d6cd6ddea
--- /dev/null
+++ b/BaseTools/BinWrappers/WindowsLike/FMMT.bat
@@ -0,0 +1,4 @@
+@setlocal
+@set ToolName=%~n0%
+@set PYTHONPATH=%PYTHONPATH%;%BASE_TOOLS_PATH%\Source\Python;%BASE_TOOLS_PATH%\Source\Python\FMMT
+@%PYTHON_COMMAND% -m %ToolName%.%ToolName% %*
diff --git a/BaseTools/Source/Python/FMMT/FMMT.py b/BaseTools/Source/Python/FMMT/FMMT.py
new file mode 100644
index 000000000000..1c41face7308
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/FMMT.py
@@ -0,0 +1,117 @@
+# @file
+# Firmware Module Management Tool.
+#
+# Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+# Import Modules
+#
+import argparse
+import sys
+from core.FMMTOperation import *
+
+parser = argparse.ArgumentParser(description='''
+View the Binary Structure of FD/FV/Ffs/Section, and Delete/Extract/Add/Replace a Ffs from/into a FV.
+''')
+parser.add_argument("--version", action="version", version='%(prog)s Version 1.0',
+ help="Print debug information.")
+parser.add_argument("-v", "--View", dest="View", nargs='+',
+ help="View each FV and the named files within each FV: '-v inputfile outputfile, inputfiletype(.Fd/.Fv/.ffs/.sec)'")
+parser.add_argument("-d", "--Delete", dest="Delete", nargs='+',
+ help="Delete a Ffs from FV: '-d inputfile TargetFfsName outputfile TargetFvName(Optional,\
+ If not given, wil delete all the existed target Ffs)'")
+parser.add_argument("-e", "--Extract", dest="Extract", nargs='+',
+ help="Extract a Ffs Info: '-e inputfile TargetFfsName outputfile'")
+parser.add_argument("-a", "--Add", dest="Add", nargs='+',
+ help="Add a Ffs into a FV:'-a inputfile TargetFvName newffsfile outputfile'")
+parser.add_argument("-r", "--Replace", dest="Replace", nargs='+',
+ help="Replace a Ffs in a FV: '-r inputfile TargetFfsName newffsfile outputfile TargetFvName(Optional,\
+ If not given, wil replace all the existed target Ffs with new Ffs file)'")
+parser.add_argument("-l", "--LogFileType", dest="LogFileType", nargs='+',
+ help="The format of log file which saves Binary layout. Currently supports: json, txt. More formats will be added in the future")
+
+def print_banner():
+ print("")
+
+class FMMT():
+ def __init__(self) -> None:
+ self.firmware_packet = {}
+
+ def CheckFfsName(self, FfsName:str) -> str:
+ try:
+ return uuid.UUID(FfsName)
+ except:
+ return FfsName
+
+ def View(self, inputfile: str, logfiletype: str=None, outputfile: str=None) -> None:
+ # ParserFile(inputfile, ROOT_TYPE, logfile, outputfile)
+ filetype = os.path.splitext(inputfile)[1].lower()
+ if filetype == '.fd':
+ ROOT_TYPE = ROOT_TREE
+ elif filetype == '.fv':
+ ROOT_TYPE = ROOT_FV_TREE
+ elif filetype == '.ffs':
+ ROOT_TYPE = ROOT_FFS_TREE
+ elif filetype == '.sec':
+ ROOT_TYPE = ROOT_SECTION_TREE
+ else:
+ ROOT_TYPE = ROOT_TREE
+ ParserFile(inputfile, ROOT_TYPE, logfiletype, outputfile)
+
+ def Delete(self, inputfile: str, TargetFfs_name: str, outputfile: str, Fv_name: str=None) -> None:
+ if Fv_name:
+ DeleteFfs(inputfile, self.CheckFfsName(TargetFfs_name), outputfile, uuid.UUID(Fv_name))
+ else:
+ DeleteFfs(inputfile, self.CheckFfsName(TargetFfs_name), outputfile)
+
+ def Extract(self, inputfile: str, Ffs_name: str, outputfile: str) -> None:
+ ExtractFfs(inputfile, self.CheckFfsName(Ffs_name), outputfile)
+
+ def Add(self, inputfile: str, Fv_name: str, newffsfile: str, outputfile: str) -> None:
+ AddNewFfs(inputfile, self.CheckFfsName(Fv_name), newffsfile, outputfile)
+
+ def Replace(self,inputfile: str, Ffs_name: str, newffsfile: str, outputfile: str, Fv_name: str=None) -> None:
+ if Fv_name:
+ ReplaceFfs(inputfile, self.CheckFfsName(Ffs_name), newffsfile, outputfile, uuid.UUID(Fv_name))
+ else:
+ ReplaceFfs(inputfile, self.CheckFfsName(Ffs_name), newffsfile, outputfile)
+
+
+def main():
+ args=parser.parse_args()
+ status=0
+
+ try:
+ fmmt=FMMT()
+ if args.View:
+ if args.LogFileType:
+ fmmt.View(args.View[0], args.LogFileType[0])
+ else:
+ fmmt.View(args.View[0])
+ if args.Delete:
+ if len(args.Delete) == 4:
+ fmmt.Delete(args.Delete[0],args.Delete[1],args.Delete[2],args.Delete[3])
+ else:
+ fmmt.Delete(args.Delete[0],args.Delete[1],args.Delete[2])
+ if args.Extract:
+ fmmt.Extract(args.Extract[0],args.Extract[1],args.Extract[2])
+ if args.Add:
+ fmmt.Add(args.Add[0],args.Add[1],args.Add[2],args.Add[3])
+ if args.Replace:
+ if len(args.Replace) == 5:
+ fmmt.Replace(args.Replace[0],args.Replace[1],args.Replace[2],args.Replace[3],args.Replace[4])
+ else:
+ fmmt.Replace(args.Replace[0],args.Replace[1],args.Replace[2],args.Replace[3])
+ # TODO:
+ '''Do the main work'''
+ except Exception as e:
+ print(e)
+
+ return status
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/BaseTools/Source/Python/FMMT/FMMTConfig.ini b/BaseTools/Source/Python/FMMT/FMMTConfig.ini
new file mode 100644
index 000000000000..e4bf03c41f4e
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/FMMTConfig.ini
@@ -0,0 +1,5 @@
+a31280ad-481e-41b6-95e8-127f4c984779 TIANO TianoCompress
+ee4e5898-3914-4259-9d6e-dc7bd79403cf LZMA LzmaCompress
+fc1bcdb0-7d31-49aa-936a-a4600d9dd083 CRC32 GenCrc32
+d42ae6bd-1352-4bfb-909a-ca72a6eae889 LZMAF86 LzmaF86Compress
+3d532050-5cda-4fd0-879e-0f7f630d5afb BROTLI BrotliCompress
diff --git a/BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png b/BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png
new file mode 100644
index 0000000000000000000000000000000000000000..7cc806a0133413a4aa037f1d43d80000546a5246
GIT binary patch
literal 29515
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

literal 0
HcmV?d00001

diff --git a/BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png b/BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png
new file mode 100644
index 0000000000000000000000000000000000000000..6335653ece1605cd0c53bd2cc2bc21d57d213dae
GIT binary patch
literal 79906
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
RBat2OV|2<CTVmi8`9DHIo&o>>

literal 0
HcmV?d00001

diff --git a/BaseTools/Source/Python/FMMT/PI/Common.py b/BaseTools/Source/Python/FMMT/PI/Common.py
new file mode 100644
index 000000000000..683341094429
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/Common.py
@@ -0,0 +1,81 @@
+## @file
+# This file is used to define the common C struct and functions.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from ctypes import *
+import uuid
+
+# ZeroGuid = uuid.UUID('{00000000-0000-0000-0000-000000000000}')
+# EFI_FIRMWARE_FILE_SYSTEM2_GUID = uuid.UUID('{8C8CE578-8A3D-4f1c-9935-896185C32DD3}')
+# EFI_FIRMWARE_FILE_SYSTEM3_GUID = uuid.UUID('{5473C07A-3DCB-4dca-BD6F-1E9689E7349A}')
+# EFI_FFS_VOLUME_TOP_FILE_GUID = uuid.UUID('{1BA0062E-C779-4582-8566-336AE8F78F09}')
+
+EFI_FIRMWARE_FILE_SYSTEM2_GUID = uuid.UUID("8c8ce578-8a3d-4f1c-9935-896185c32dd3")
+EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE = b'x\xe5\x8c\x8c=\x8a\x1cO\x995\x89a\x85\xc3-\xd3'
+# EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE = EFI_FIRMWARE_FILE_SYSTEM2_GUID.bytes
+EFI_FIRMWARE_FILE_SYSTEM3_GUID = uuid.UUID("5473C07A-3DCB-4dca-BD6F-1E9689E7349A")
+# EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE = b'x\xe5\x8c\x8c=\x8a\x1cO\x995\x89a\x85\xc3-\xd3'
+EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE = b'z\xc0sT\xcb=\xcaM\xbdo\x1e\x96\x89\xe74\x9a'
+EFI_SYSTEM_NVDATA_FV_GUID = uuid.UUID("fff12b8d-7696-4c8b-a985-2747075b4f50")
+EFI_SYSTEM_NVDATA_FV_GUID_BYTE = b"\x8d+\xf1\xff\x96v\x8bL\xa9\x85'G\x07[OP"
+EFI_FFS_VOLUME_TOP_FILE_GUID = uuid.UUID("1ba0062e-c779-4582-8566-336ae8f78f09")
+EFI_FFS_VOLUME_TOP_FILE_GUID_BYTE = b'.\x06\xa0\x1by\xc7\x82E\x85f3j\xe8\xf7\x8f\t'
+ZEROVECTOR_BYTE = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+PADVECTOR = uuid.UUID("ffffffff-ffff-ffff-ffff-ffffffffffff")
+FVH_SIGNATURE = b'_FVH'
+
+class GUID(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Guid1', c_uint32),
+ ('Guid2', c_uint16),
+ ('Guid3', c_uint16),
+ ('Guid4', ARRAY(c_uint8, 8)),
+ ]
+
+ def from_list(self, listformat: list) -> None:
+ self.Guid1 = listformat[0]
+ self.Guid2 = listformat[1]
+ self.Guid3 = listformat[2]
+ for i in range(8):
+ self.Guid4[i] = listformat[i+3]
+
+ def __cmp__(self, otherguid) -> bool:
+ if not isinstance(otherguid, GUID):
+ return 'Input is not the GUID instance!'
+ rt = False
+ if self.Guid1 == otherguid.Guid1 and self.Guid2 == otherguid.Guid2 and self.Guid3 == otherguid.Guid3:
+ rt = True
+ for i in range(8):
+ rt = rt & (self.Guid4[i] == otherguid.Guid4[i])
+ return rt
+
+def ModifyGuidFormat(target_guid: str) -> GUID:
+ target_guid = target_guid.replace('-', '')
+ target_list = []
+ start = [0,8,12,16,18,20,22,24,26,28,30]
+ end = [8,12,16,18,20,22,24,26,28,30,32]
+ num = len(start)
+ for pos in range(num):
+ new_value = int(target_guid[start[pos]:end[pos]], 16)
+ target_list.append(new_value)
+ new_format = GUID()
+ new_format.from_list(target_list)
+ return new_format
+
+
+# Get data from ctypes to bytes.
+def struct2stream(s) -> bytes:
+ length = sizeof(s)
+ p = cast(pointer(s), POINTER(c_char * length))
+ return p.contents.raw
+
+
+
+def GetPadSize(Size: int, alignment: int) -> int:
+ if Size % alignment == 0:
+ return 0
+ Pad_Size = alignment - Size % alignment
+ return Pad_Size
diff --git a/BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py b/BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py
new file mode 100644
index 000000000000..fb0100420ecc
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py
@@ -0,0 +1,66 @@
+## @file
+# This file is used to define the Ffs Header C Struct.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from struct import *
+from ctypes import *
+from PI.Common import *
+
+EFI_FFS_FILE_HEADER_LEN = 24
+EFI_FFS_FILE_HEADER2_LEN = 32
+
+class CHECK_SUM(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Header', c_uint8),
+ ('File', c_uint8),
+ ]
+
+class EFI_FFS_INTEGRITY_CHECK(Union):
+ _pack_ = 1
+ _fields_ = [
+ ('Checksum', CHECK_SUM),
+ ('Checksum16', c_uint16),
+ ]
+
+
+class EFI_FFS_FILE_HEADER(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Name', GUID),
+ ('IntegrityCheck', EFI_FFS_INTEGRITY_CHECK),
+ ('Type', c_uint8),
+ ('Attributes', c_uint8),
+ ('Size', ARRAY(c_uint8, 3)),
+ ('State', c_uint8),
+ ]
+
+ @property
+ def FFS_FILE_SIZE(self) -> int:
+ return self.Size[0] | self.Size[1] << 8 | self.Size[2] << 16
+
+ @property
+ def HeaderLength(self) -> int:
+ return 24
+
+class EFI_FFS_FILE_HEADER2(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Name', GUID),
+ ('IntegrityCheck', EFI_FFS_INTEGRITY_CHECK),
+ ('Type', c_uint8),
+ ('Attributes', c_uint8),
+ ('Size', ARRAY(c_uint8, 3)),
+ ('State', c_uint8),
+ ('ExtendedSize', c_uint64),
+ ]
+
+ @property
+ def FFS_FILE_SIZE(self) -> int:
+ return self.ExtendedSize
+
+ @property
+ def HeaderLength(self) -> int:
+ return 32
diff --git a/BaseTools/Source/Python/FMMT/PI/FvHeader.py b/BaseTools/Source/Python/FMMT/PI/FvHeader.py
new file mode 100644
index 000000000000..51cb15ddac28
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/FvHeader.py
@@ -0,0 +1,112 @@
+## @file
+# This file is used to define the FV Header C Struct.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from ast import Str
+from struct import *
+from ctypes import *
+from PI.Common import *
+
+class EFI_FV_BLOCK_MAP_ENTRY(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('NumBlocks', c_uint32),
+ ('Length', c_uint32),
+ ]
+
+
+class EFI_FIRMWARE_VOLUME_HEADER(Structure):
+ _fields_ = [
+ ('ZeroVector', ARRAY(c_uint8, 16)),
+ ('FileSystemGuid', GUID),
+ ('FvLength', c_uint64),
+ ('Signature', c_uint32),
+ ('Attributes', c_uint32),
+ ('HeaderLength', c_uint16),
+ ('Checksum', c_uint16),
+ ('ExtHeaderOffset', c_uint16),
+ ('Reserved', c_uint8),
+ ('Revision', c_uint8),
+ ('BlockMap', ARRAY(EFI_FV_BLOCK_MAP_ENTRY, 1)),
+ ]
+
+def Refine_FV_Header(nums):
+ class EFI_FIRMWARE_VOLUME_HEADER(Structure):
+ _fields_ = [
+ ('ZeroVector', ARRAY(c_uint8, 16)),
+ ('FileSystemGuid', GUID),
+ ('FvLength', c_uint64),
+ ('Signature', c_uint32),
+ ('Attributes', c_uint32),
+ ('HeaderLength', c_uint16),
+ ('Checksum', c_uint16),
+ ('ExtHeaderOffset', c_uint16),
+ ('Reserved', c_uint8),
+ ('Revision', c_uint8),
+ ('BlockMap', ARRAY(EFI_FV_BLOCK_MAP_ENTRY, nums)),
+ ]
+ return EFI_FIRMWARE_VOLUME_HEADER
+
+class EFI_FIRMWARE_VOLUME_EXT_HEADER(Structure):
+ _fields_ = [
+ ('FvName', GUID),
+ ('ExtHeaderSize', c_uint32)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY(Structure):
+ _fields_ = [
+ ('ExtEntrySize', c_uint16),
+ ('ExtEntryType', c_uint16)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE_0(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('TypeMask', c_uint32)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('TypeMask', c_uint32),
+ ('Types', ARRAY(GUID, 1))
+ ]
+
+def Refine_FV_EXT_ENTRY_OEM_TYPE_Header(nums: int) -> EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE:
+ class EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('TypeMask', c_uint32),
+ ('Types', ARRAY(GUID, nums))
+ ]
+ return EFI_FIRMWARE_VOLUME_EXT_ENTRY_OEM_TYPE(Structure)
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE_0(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('FormatType', GUID)
+ ]
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('FormatType', GUID),
+ ('Data', ARRAY(c_uint8, 1))
+ ]
+
+def Refine_FV_EXT_ENTRY_GUID_TYPE_Header(nums: int) -> EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE:
+ class EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('FormatType', GUID),
+ ('Data', ARRAY(c_uint8, nums))
+ ]
+ return EFI_FIRMWARE_VOLUME_EXT_ENTRY_GUID_TYPE(Structure)
+
+class EFI_FIRMWARE_VOLUME_EXT_ENTRY_USED_SIZE_TYPE(Structure):
+ _fields_ = [
+ ('Hdr', EFI_FIRMWARE_VOLUME_EXT_ENTRY),
+ ('UsedSize', c_uint32)
+ ]
diff --git a/BaseTools/Source/Python/FMMT/PI/SectionHeader.py b/BaseTools/Source/Python/FMMT/PI/SectionHeader.py
new file mode 100644
index 000000000000..8c6229819a18
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/SectionHeader.py
@@ -0,0 +1,110 @@
+## @file
+# This file is used to define the Section Header C Struct.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from struct import *
+from ctypes import *
+from PI.Common import *
+
+EFI_COMMON_SECTION_HEADER_LEN = 4
+EFI_COMMON_SECTION_HEADER2_LEN = 8
+
+class EFI_COMMON_SECTION_HEADER(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Size', ARRAY(c_uint8, 3)),
+ ('Type', c_uint8),
+ ]
+
+ @property
+ def SECTION_SIZE(self) -> int:
+ return self.Size[0] | self.Size[1] << 8 | self.Size[2] << 16
+
+ def Common_Header_Size(self) -> int:
+ return 4
+
+class EFI_COMMON_SECTION_HEADER2(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('Size', ARRAY(c_uint8, 3)),
+ ('Type', c_uint8),
+ ('ExtendedSize', c_uint32),
+ ]
+
+ @property
+ def SECTION_SIZE(self) -> int:
+ return self.ExtendedSize
+
+ def Common_Header_Size(self) -> int:
+ return 8
+
+class EFI_COMPRESSION_SECTION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('UncompressedLength', c_uint32),
+ ('CompressionType', c_uint8),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 5
+
+class EFI_FREEFORM_SUBTYPE_GUID_SECTION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('SubTypeGuid', GUID),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 16
+
+class EFI_GUID_DEFINED_SECTION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('SectionDefinitionGuid', GUID),
+ ('DataOffset', c_uint16),
+ ('Attributes', c_uint16),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 20
+
+def Get_USER_INTERFACE_Header(nums: int):
+ class EFI_SECTION_USER_INTERFACE(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('FileNameString', ARRAY(c_uint16, nums)),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 2 * nums
+
+ def GetUiString(self) -> str:
+ UiString = ''
+ for i in range(nums):
+ if self.FileNameString[i]:
+ UiString += chr(self.FileNameString[i])
+ return UiString
+
+ return EFI_SECTION_USER_INTERFACE
+
+def Get_VERSION_Header(nums: int):
+ class EFI_SECTION_VERSION(Structure):
+ _pack_ = 1
+ _fields_ = [
+ ('BuildNumber', c_uint16),
+ ('VersionString', ARRAY(c_uint16, nums)),
+ ]
+
+ def ExtHeaderSize(self) -> int:
+ return 2 * (nums+1)
+
+ def GetVersionString(self) -> str:
+ VersionString = ''
+ for i in range(nums):
+ if self.VersionString[i]:
+ VersionString += chr(self.VersionString[i])
+ return VersionString
+
+ return EFI_SECTION_VERSION
diff --git a/BaseTools/Source/Python/FMMT/PI/__init__.py b/BaseTools/Source/Python/FMMT/PI/__init__.py
new file mode 100644
index 000000000000..21cbd82effc5
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/PI/__init__.py
@@ -0,0 +1,6 @@
+## @file
+# This file is used to define the FMMT dependent external tool management class.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/README.md b/BaseTools/Source/Python/FMMT/README.md
new file mode 100644
index 000000000000..cfbc966a6af6
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/README.md
@@ -0,0 +1,180 @@
+# FMMT
+## Overview
+This FMMT tool is the python implementation of the edk2 FMMT tool which locates at https://github.com/tianocore/edk2-staging/tree/FceFmmt.
+This implementation has the same usage as the edk2 FMMT, but it's more readable and relaiable.
+
+# FMMT User Guide
+
+#### Last updated October 13, 2021
+
+Important Changes and Updates:
+
+- Oct 13, 2021 Initial Draft of FMMT Python Tool
+
+#### Note:
+
+- FMMT Python Tool keeps same function with origin FMMT C Tool. It is much easier to maintain and extend other functions.
+
+
+
+# 1. Introduction
+
+## 1.1 Overview
+
+The Firmware Device is a persistent physical repository that contains firmware code and/or data. The firmware code and/or data stored in Firmware Volumes. Detail layout of Firmware Volumes is described in “Figure 1. The Firmware Volume Format”.
+
+![](Img/FirmwareVolumeFormat.png)
+
+​ Figure 1. The Firmware Volume Format
+
+In firmware development, binary file has its firmware layout following the Platform-Initialization Specification. Thus, operation on FV file / FFS file (Firmware File) is an efficient and convenient way for firmware function testing and developing. FMMT Python tool is used for firmware files operation.
+
+## 1.2 Tool Capabilities
+
+The FMMT tool is capable of:
+
+- Parse a FD (Firmware Device) / FV (Firmware Volume) / FFS (Firmware Files)
+
+- Add a new FFS into a FV file (both included in a FD file or not)
+
+- Replace an FFS in a FV file with a new FFS file
+
+- Delete an FFS in a FV file (both included in a FD file or not)
+
+- Extract the FFS from a FV file (both included in a FD file or not)
+
+## 1.3 References
+
+| Document |
+| ------------------------------------------------ |
+| UEFI Platform Initialization (PI) Specification |
+
+
+
+# 2. FMMT Python Tool Usage
+
+## 2.1 Required Files
+
+### 2.1.1 Independent use
+
+When independent use the FMMT Python Tool, the following files and settings are required:
+
+- GuidTool executable files used for Decompress/Compress Firmware data.
+
+- Environment variables path with GuidTool path setting.
+
+### 2.1.2 Use with Build System
+
+When use the FMMT Python Tool with Build System:
+
+- If only use Edk2 based GuidTool, do not need other preparation.
+
+- If use other customized GuidTool, need prepare the config file with GuidTool info. The syntax for GuidTool definition shown as follow:
+
+ ***ToolsGuid ShortName Command***
+
+ -- Example: ***3d532050-5cda-4fd0-879e-0f7f630d5afb BROTLI BrotliCompress***
+
+## 2.2 Syntax
+
+### 2.2.1 Syntax for Parse file
+
+​ ***-v < Inputfile > < Outputfile > -l < LogFileType >***
+
+- Parse *Inputfile*, show its firmware layout with log file. *Outputfile* is optional, if inputs, the *Inputfile* will be encapsulated into *Outputfile* following the parsed firmware layout. *"-l LogFileType"* is optional, it decides the format of log file which saves Binary layout. Currently supports: json, txt. More formats will be added in the future.
+
+### 2.2.2 Syntax for Add a new FFS
+
+​ ***-a < Inputfile > < TargetFvName/TargetFvGuid > < NewFfsFile > < Outputfile >***
+
+- Add the *NewFfsFile* into *Inputfile*. *TargetFvName/TargetFvGuid* (Name or Guid) is the TargetFv which *NewFfsFile* will be added into.
+
+### 2.2.3 Syntax for Delete an FFS
+
+​ ***-d < Inputfile > < TargetFfsName > < Outputfile > < TargetFvName/TargetFvGuid >***
+
+- Delete the Ffs from *Inputfile*. TargetFfsName (Guid) is the TargetFfs which will be deleted. *TargetFvName/TargetFvGuid* is optional, which is the parent of TargetFfs*.*
+
+### 2.2.4 Syntax for Replace an FFS
+
+​ ***-r < Inputfile > < TargetFfsName > < NewFfsFile > < Outputfile > < TargetFvName/TargetFvGuid >***
+
+- Replace the Ffs with the NewFfsFile. TargetFfsName (Guid) is the TargetFfs which will be replaced. *TargetFvName/TargetFvGuid* is optional, which is the parent of TargetFfs*.*
+
+### 2.2.5 Syntax for Extract an FFS
+
+​ ***-e < Inputfile > < TargetFfsName > < Outputfile >***
+
+- Extract the Ffs from the Inputfile. TargetFfsName (Guid) is the TargetFfs which will be extracted.
+
+
+
+# 3. FMMT Python Tool Design
+
+FMMT Python Tool uses the NodeTree saves whole Firmware layout. Each Node have its Data field, which saves the FirmwareClass(FD/FV/FFS/SECTION/BINARY) Data. All the parse/add/delete/replace/extract operations are based on the NodeTree (adjusting the layout and data).
+
+## 3.1 NodeTree
+
+A whole NodeTree saves all the Firmware info.
+
+- Parent & Child relationship figured out the Firmware layout.
+
+- Each Node have several fields. ‘Data’ field saves an FirmwareClass instance which contains all the data info of the info.
+
+### 3.1.1 NodeTree Format
+
+The NodeTree will be created with parse function. When parse a file, a Root Node will be initialized firstly. The Data split and Tree construction process is described with an FD file shown as ‘Figure 2. The NodeTree format’:
+
+- A Root Node is initialized.
+
+- Use the ‘FV Signature’ as FV key to split Whole FD Data. ‘FV0’, ‘FV1’, ‘FV2’… Node created.
+
+- After FV level Node created, use the ‘Ffs Data Size’ as FFS key to split each FV Data. ‘Ffs0’...Node created.
+
+- After FFS level Node created, use the ‘Section Data Size’ as Section key to split each Ffs Data. ‘Section0’...Node created.
+
+- If some of Section includes other Sections, continue use the ‘Section Data Size’ as Section key to split each Section Data.
+
+- After all Node created, the whole NodeTree saves all the info. (Can be used in other functions or print the whole firmware layout into log file)
+
+![](Img/NodeTreeFormat.png)
+
+​ Figure 2. The NodeTree format
+
+### 3.1.2 Node Factory and Product
+
+As 3.1.1, Each Node is created by data split and recognition. To extend the NodeTree usage, Factory pattern is used in Node created process.
+
+Each Node have its Factory to create Product and use Product ParserData function to deal with the data.
+
+## 3.2 GuidTool
+
+There are two ways to set the GuidTool. One from Config file, another from environment variables.
+
+Current GuidTool first check if has Config file.
+
+- If have, load the config GuidTool Information.
+
+- Else get from environment variables.
+
+### 3.2.1 Get from Config file
+
+- Config file should in same folder with FMMT.py or the path in environment variables.
+
+- Content should follow the format:
+
+ ***ToolsGuid ShortName Command***
+
+### 3.2.2 Get from Environment Variables
+
+- The GuidTool Command used must be set in environment variables.
+
+### 3.2.3 Edk2 Based GuidTool
+
+| ***Guid*** | ***ShortName*** | ***Command*** |
+| ------------------------------------------ | --------------- | --------------------- |
+| ***a31280ad-481e-41b6-95e8-127f4c984779*** | ***TIANO*** | ***TianoCompress*** |
+| ***ee4e5898-3914-4259-9d6e-dc7bd79403cf*** | ***LZMA*** | ***LzmaCompress*** |
+| ***fc1bcdb0-7d31-49aa-936a-a4600d9dd083*** | ***CRC32*** | ***GenCrc32*** |
+| ***d42ae6bd-1352-4bfb-909a-ca72a6eae889*** | ***LZMAF86*** | ***LzmaF86Compress*** |
+| ***3d532050-5cda-4fd0-879e-0f7f630d5afb*** | ***BROTLI*** | ***BrotliCompress*** |
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/__init__.py b/BaseTools/Source/Python/FMMT/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py b/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
new file mode 100644
index 000000000000..afc0a90bd0cd
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
@@ -0,0 +1,371 @@
+## @file
+# This file is used to implement of the various bianry parser.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from re import T
+import copy
+import os
+from PI.Common import *
+from core.BiosTreeNode import *
+from core.BiosTree import *
+from core.GuidTools import *
+
+ROOT_TREE = 'ROOT'
+ROOT_FV_TREE = 'ROOT_FV_TREE'
+ROOT_FFS_TREE = 'ROOT_FFS_TREE'
+ROOT_SECTION_TREE = 'ROOT_SECTION_TREE'
+
+FV_TREE = 'FV'
+DATA_FV_TREE = 'DATA_FV'
+FFS_TREE = 'FFS'
+FFS_PAD = 'FFS_PAD'
+FFS_FREE_SPACE = 'FFS_FREE_SPACE'
+SECTION_TREE = 'SECTION'
+SEC_FV_TREE = 'SEC_FV_IMAGE'
+BINARY_DATA = 'BINARY'
+Fv_count = 0
+
+## Abstract factory
+class BinaryFactory():
+ type:list = []
+
+ def Create_Product():
+ pass
+
+class BinaryProduct():
+ ## Use GuidTool to decompress data.
+ def DeCompressData(self, GuidTool, Section_Data: bytes) -> bytes:
+ ParPath = os.path.abspath(os.path.dirname(os.path.abspath(__file__))+os.path.sep+"..")
+ ToolPath = os.path.join(ParPath, r'FMMTConfig.ini')
+ guidtool = GUIDTools(ToolPath).__getitem__(struct2stream(GuidTool))
+ DecompressedData = guidtool.unpack(Section_Data)
+ return DecompressedData
+
+ def ParserData():
+ pass
+
+class SectionFactory(BinaryFactory):
+ type = [SECTION_TREE]
+
+ def Create_Product():
+ return SectionProduct()
+
+class FfsFactory(BinaryFactory):
+ type = [ROOT_SECTION_TREE, FFS_TREE]
+
+ def Create_Product():
+ return FfsProduct()
+
+class FvFactory(BinaryFactory):
+ type = [ROOT_FFS_TREE, FV_TREE, SEC_FV_TREE]
+
+ def Create_Product():
+ return FvProduct()
+
+class FdFactory(BinaryFactory):
+ type = [ROOT_FV_TREE, ROOT_TREE]
+
+ def Create_Product():
+ return FdProduct()
+
+class SectionProduct(BinaryProduct):
+ ## Decompress the compressed section.
+ def ParserData(self, Section_Tree, whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ if Section_Tree.Data.Type == 0x01:
+ Section_Tree.Data.OriData = Section_Tree.Data.Data
+ self.ParserFfs(Section_Tree, b'')
+ # Guided Define Section
+ elif Section_Tree.Data.Type == 0x02:
+ Section_Tree.Data.OriData = Section_Tree.Data.Data
+ DeCompressGuidTool = Section_Tree.Data.ExtHeader.SectionDefinitionGuid
+ Section_Tree.Data.Data = self.DeCompressData(DeCompressGuidTool, Section_Tree.Data.Data)
+ Section_Tree.Data.Size = len(Section_Tree.Data.Data) + Section_Tree.Data.HeaderLength
+ self.ParserFfs(Section_Tree, b'')
+ elif Section_Tree.Data.Type == 0x03:
+ Section_Tree.Data.OriData = Section_Tree.Data.Data
+ self.ParserFfs(Section_Tree, b'')
+ # SEC_FV Section
+ elif Section_Tree.Data.Type == 0x17:
+ global Fv_count
+ Sec_Fv_Info = FvNode(Fv_count, Section_Tree.Data.Data)
+ Sec_Fv_Tree = BIOSTREE('FV'+ str(Fv_count))
+ Sec_Fv_Tree.type = SEC_FV_TREE
+ Sec_Fv_Tree.Data = Sec_Fv_Info
+ Sec_Fv_Tree.Data.HOffset = Section_Tree.Data.DOffset
+ Sec_Fv_Tree.Data.DOffset = Sec_Fv_Tree.Data.HOffset + Sec_Fv_Tree.Data.Header.HeaderLength
+ Sec_Fv_Tree.Data.Data = Section_Tree.Data.Data[Sec_Fv_Tree.Data.Header.HeaderLength:]
+ Section_Tree.insertChild(Sec_Fv_Tree)
+ Fv_count += 1
+
+ def ParserFfs(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ Rel_Offset = 0
+ Section_Offset = 0
+ # Get the Data from parent tree, if do not have the tree then get it from the whole_data.
+ if ParTree.Data != None:
+ Data_Size = len(ParTree.Data.Data)
+ Section_Offset = ParTree.Data.DOffset
+ Whole_Data = ParTree.Data.Data
+ else:
+ Data_Size = len(Whole_Data)
+ # Parser all the data to collect all the Section recorded in its Parent Section.
+ while Rel_Offset < Data_Size:
+ # Create a SectionNode and set it as the SectionTree's Data
+ Section_Info = SectionNode(Whole_Data[Rel_Offset:])
+ Section_Tree = BIOSTREE(Section_Info.Name)
+ Section_Tree.type = SECTION_TREE
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.HeaderLength: Rel_Offset+Section_Info.Size]
+ Section_Info.DOffset = Section_Offset + Section_Info.HeaderLength + Rel_Whole_Offset
+ Section_Info.HOffset = Section_Offset + Rel_Whole_Offset
+ Section_Info.ROffset = Rel_Offset
+ if Section_Info.Header.Type == 0:
+ break
+ # The final Section in parent Section does not need to add padding, else must be 4-bytes align with parent Section start offset
+ Pad_Size = 0
+ if (Rel_Offset+Section_Info.HeaderLength+len(Section_Info.Data) != Data_Size):
+ Pad_Size = GetPadSize(Section_Info.Size, 4)
+ Section_Info.PadData = Pad_Size * b'\x00'
+ if Section_Info.Header.Type == 0x02:
+ Section_Info.DOffset = Section_Offset + Section_Info.ExtHeader.DataOffset + Rel_Whole_Offset
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.ExtHeader.DataOffset: Rel_Offset+Section_Info.Size]
+ if Section_Info.Header.Type == 0x14:
+ ParTree.Data.Version = Section_Info.ExtHeader.GetVersionString()
+ if Section_Info.Header.Type == 0x15:
+ ParTree.Data.UiName = Section_Info.ExtHeader.GetUiString()
+ Section_Offset += Section_Info.Size + Pad_Size
+ Rel_Offset += Section_Info.Size + Pad_Size
+ Section_Tree.Data = Section_Info
+ ParTree.insertChild(Section_Tree)
+
+class FfsProduct(BinaryProduct):
+ # ParserFFs / GetSection
+ def ParserData(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ Rel_Offset = 0
+ Section_Offset = 0
+ # Get the Data from parent tree, if do not have the tree then get it from the whole_data.
+ if ParTree.Data != None:
+ Data_Size = len(ParTree.Data.Data)
+ Section_Offset = ParTree.Data.DOffset
+ Whole_Data = ParTree.Data.Data
+ else:
+ Data_Size = len(Whole_Data)
+ # Parser all the data to collect all the Section recorded in Ffs.
+ while Rel_Offset < Data_Size:
+ # Create a SectionNode and set it as the SectionTree's Data
+ Section_Info = SectionNode(Whole_Data[Rel_Offset:])
+ Section_Tree = BIOSTREE(Section_Info.Name)
+ Section_Tree.type = SECTION_TREE
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.HeaderLength: Rel_Offset+Section_Info.Size]
+ Section_Info.DOffset = Section_Offset + Section_Info.HeaderLength + Rel_Whole_Offset
+ Section_Info.HOffset = Section_Offset + Rel_Whole_Offset
+ Section_Info.ROffset = Rel_Offset
+ if Section_Info.Header.Type == 0:
+ break
+ # The final Section in Ffs does not need to add padding, else must be 4-bytes align with Ffs start offset
+ Pad_Size = 0
+ if (Rel_Offset+Section_Info.HeaderLength+len(Section_Info.Data) != Data_Size):
+ Pad_Size = GetPadSize(Section_Info.Size, 4)
+ Section_Info.PadData = Pad_Size * b'\x00'
+ if Section_Info.Header.Type == 0x02:
+ Section_Info.DOffset = Section_Offset + Section_Info.ExtHeader.DataOffset + Rel_Whole_Offset
+ Section_Info.Data = Whole_Data[Rel_Offset+Section_Info.ExtHeader.DataOffset: Rel_Offset+Section_Info.Size]
+ # If Section is Version or UI type, it saves the version and UI info of its parent Ffs.
+ if Section_Info.Header.Type == 0x14:
+ ParTree.Data.Version = Section_Info.ExtHeader.GetVersionString()
+ if Section_Info.Header.Type == 0x15:
+ ParTree.Data.UiName = Section_Info.ExtHeader.GetUiString()
+ Section_Offset += Section_Info.Size + Pad_Size
+ Rel_Offset += Section_Info.Size + Pad_Size
+ Section_Tree.Data = Section_Info
+ ParTree.insertChild(Section_Tree)
+
+class FvProduct(BinaryProduct):
+ ## ParserFv / GetFfs
+ def ParserData(self, ParTree, Whole_Data: bytes, Rel_Whole_Offset: int=0) -> None:
+ Ffs_Offset = 0
+ Rel_Offset = 0
+ # Get the Data from parent tree, if do not have the tree then get it from the whole_data.
+ if ParTree.Data != None:
+ Data_Size = len(ParTree.Data.Data)
+ Ffs_Offset = ParTree.Data.DOffset
+ Whole_Data = ParTree.Data.Data
+ else:
+ Data_Size = len(Whole_Data)
+ # Parser all the data to collect all the Ffs recorded in Fv.
+ while Rel_Offset < Data_Size:
+ # Create a FfsNode and set it as the FFsTree's Data
+ if Data_Size - Rel_Offset < 24:
+ Ffs_Tree = BIOSTREE('Free_Space')
+ Ffs_Tree.type = FFS_FREE_SPACE
+ Ffs_Tree.Data = FreeSpaceNode(Whole_Data[Rel_Offset:])
+ Ffs_Tree.Data.HOffset = Ffs_Offset + Rel_Whole_Offset
+ Ffs_Tree.Data.DOffset = Ffs_Tree.Data.HOffset
+ ParTree.Data.Free_Space = Data_Size - Rel_Offset
+ ParTree.insertChild(Ffs_Tree)
+ Rel_Offset = Data_Size
+ else:
+ Ffs_Info = FfsNode(Whole_Data[Rel_Offset:])
+ Ffs_Tree = BIOSTREE(Ffs_Info.Name)
+ Ffs_Info.HOffset = Ffs_Offset + Rel_Whole_Offset
+ Ffs_Info.DOffset = Ffs_Offset + Ffs_Info.Header.HeaderLength + Rel_Whole_Offset
+ Ffs_Info.ROffset = Rel_Offset
+ if Ffs_Info.Name == PADVECTOR:
+ Ffs_Tree.type = FFS_PAD
+ Ffs_Info.Data = Whole_Data[Rel_Offset+Ffs_Info.Header.HeaderLength: Rel_Offset+Ffs_Info.Size]
+ Ffs_Info.Size = len(Ffs_Info.Data) + Ffs_Info.Header.HeaderLength
+ # if current Ffs is the final ffs of Fv and full of b'\xff', define it with Free_Space
+ if struct2stream(Ffs_Info.Header).replace(b'\xff', b'') == b'':
+ Ffs_Tree.type = FFS_FREE_SPACE
+ Ffs_Info.Data = Whole_Data[Rel_Offset:]
+ Ffs_Info.Size = len(Ffs_Info.Data)
+ ParTree.Data.Free_Space = Ffs_Info.Size
+ else:
+ Ffs_Tree.type = FFS_TREE
+ Ffs_Info.Data = Whole_Data[Rel_Offset+Ffs_Info.Header.HeaderLength: Rel_Offset+Ffs_Info.Size]
+ # The final Ffs in Fv does not need to add padding, else must be 8-bytes align with Fv start offset
+ Pad_Size = 0
+ if Ffs_Tree.type != FFS_FREE_SPACE and (Rel_Offset+Ffs_Info.Header.HeaderLength+len(Ffs_Info.Data) != Data_Size):
+ Pad_Size = GetPadSize(Ffs_Info.Size, 8)
+ Ffs_Info.PadData = Pad_Size * b'\xff'
+ Ffs_Offset += Ffs_Info.Size + Pad_Size
+ Rel_Offset += Ffs_Info.Size + Pad_Size
+ Ffs_Tree.Data = Ffs_Info
+ ParTree.insertChild(Ffs_Tree)
+
+class FdProduct(BinaryProduct):
+ type = [ROOT_FV_TREE, ROOT_TREE]
+
+ ## Create DataTree with first level /fv Info, then parser each Fv.
+ def ParserData(self, WholeFvTree, whole_data: bytes=b'', offset: int=0) -> None:
+ # Get all Fv image in Fd with offset and length
+ Fd_Struct = self.GetFvFromFd(whole_data)
+ data_size = len(whole_data)
+ Binary_count = 0
+ global Fv_count
+ # If the first Fv image is the Binary Fv, add it into the tree.
+ if Fd_Struct[0][1] != 0:
+ Binary_node = BIOSTREE('BINARY'+ str(Binary_count))
+ Binary_node.type = BINARY_DATA
+ Binary_node.Data = BinaryNode(str(Binary_count))
+ Binary_node.Data.Data = whole_data[:Fd_Struct[0][1]]
+ Binary_node.Data.Size = len(Binary_node.Data.Data)
+ Binary_node.Data.HOffset = 0 + offset
+ WholeFvTree.insertChild(Binary_node)
+ Binary_count += 1
+ # Add the first collected Fv image into the tree.
+ Cur_node = BIOSTREE(Fd_Struct[0][0]+ str(Fv_count))
+ Cur_node.type = Fd_Struct[0][0]
+ Cur_node.Data = FvNode(Fv_count, whole_data[Fd_Struct[0][1]:Fd_Struct[0][1]+Fd_Struct[0][2][0]])
+ Cur_node.Data.HOffset = Fd_Struct[0][1] + offset
+ Cur_node.Data.DOffset = Cur_node.Data.HOffset+Cur_node.Data.Header.HeaderLength
+ Cur_node.Data.Data = whole_data[Fd_Struct[0][1]+Cur_node.Data.Header.HeaderLength:Fd_Struct[0][1]+Cur_node.Data.Size]
+ WholeFvTree.insertChild(Cur_node)
+ Fv_count += 1
+ Fv_num = len(Fd_Struct)
+ # Add all the collected Fv image and the Binary Fv image between them into the tree.
+ for i in range(Fv_num-1):
+ if Fd_Struct[i][1]+Fd_Struct[i][2][0] != Fd_Struct[i+1][1]:
+ Binary_node = BIOSTREE('BINARY'+ str(Binary_count))
+ Binary_node.type = BINARY_DATA
+ Binary_node.Data = BinaryNode(str(Binary_count))
+ Binary_node.Data.Data = whole_data[Fd_Struct[i][1]+Fd_Struct[i][2][0]:Fd_Struct[i+1][1]]
+ Binary_node.Data.Size = len(Binary_node.Data.Data)
+ Binary_node.Data.HOffset = Fd_Struct[i][1]+Fd_Struct[i][2][0] + offset
+ WholeFvTree.insertChild(Binary_node)
+ Binary_count += 1
+ Cur_node = BIOSTREE(Fd_Struct[i+1][0]+ str(Fv_count))
+ Cur_node.type = Fd_Struct[i+1][0]
+ Cur_node.Data = FvNode(Fv_count, whole_data[Fd_Struct[i+1][1]:Fd_Struct[i+1][1]+Fd_Struct[i+1][2][0]])
+ Cur_node.Data.HOffset = Fd_Struct[i+1][1] + offset
+ Cur_node.Data.DOffset = Cur_node.Data.HOffset+Cur_node.Data.Header.HeaderLength
+ Cur_node.Data.Data = whole_data[Fd_Struct[i+1][1]+Cur_node.Data.Header.HeaderLength:Fd_Struct[i+1][1]+Cur_node.Data.Size]
+ WholeFvTree.insertChild(Cur_node)
+ Fv_count += 1
+ # If the final Fv image is the Binary Fv, add it into the tree
+ if Fd_Struct[-1][1] + Fd_Struct[-1][2][0] != data_size:
+ Binary_node = BIOSTREE('BINARY'+ str(Binary_count))
+ Binary_node.type = BINARY_DATA
+ Binary_node.Data = BinaryNode(str(Binary_count))
+ Binary_node.Data.Data = whole_data[Fd_Struct[-1][1]+Fd_Struct[-1][2][0]:]
+ Binary_node.Data.Size = len(Binary_node.Data.Data)
+ Binary_node.Data.HOffset = Fd_Struct[-1][1]+Fd_Struct[-1][2][0] + offset
+ WholeFvTree.insertChild(Binary_node)
+ Binary_count += 1
+
+ ## Get the first level Fv from Fd file.
+ def GetFvFromFd(self, whole_data: bytes=b'') -> list:
+ Fd_Struct = []
+ data_size = len(whole_data)
+ cur_index = 0
+ # Get all the EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE FV image offset and length.
+ while cur_index < data_size:
+ if EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE in whole_data[cur_index:]:
+ target_index = whole_data[cur_index:].index(EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE) + cur_index
+ if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE and whole_data[target_index-16:target_index] == ZEROVECTOR_BYTE:
+ Fd_Struct.append([FV_TREE, target_index - 16, unpack("Q", whole_data[target_index+16:target_index+24])])
+ cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]
+ else:
+ cur_index = target_index + 16
+ else:
+ cur_index = data_size
+ cur_index = 0
+ # Get all the EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE FV image offset and length.
+ while cur_index < data_size:
+ if EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE in whole_data[cur_index:]:
+ target_index = whole_data[cur_index:].index(EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE) + cur_index
+ if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE and whole_data[target_index-16:target_index] == ZEROVECTOR_BYTE:
+ Fd_Struct.append([FV_TREE, target_index - 16, unpack("Q", whole_data[target_index+16:target_index+24])])
+ cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]
+ else:
+ cur_index = target_index + 16
+ else:
+ cur_index = data_size
+ cur_index = 0
+ # Get all the EFI_SYSTEM_NVDATA_FV_GUID_BYTE FV image offset and length.
+ while cur_index < data_size:
+ if EFI_SYSTEM_NVDATA_FV_GUID_BYTE in whole_data[cur_index:]:
+ target_index = whole_data[cur_index:].index(EFI_SYSTEM_NVDATA_FV_GUID_BYTE) + cur_index
+ if whole_data[target_index+24:target_index+28] == FVH_SIGNATURE and whole_data[target_index-16:target_index] == ZEROVECTOR_BYTE:
+ Fd_Struct.append([DATA_FV_TREE, target_index - 16, unpack("Q", whole_data[target_index+16:target_index+24])])
+ cur_index = Fd_Struct[-1][1] + Fd_Struct[-1][2][0]
+ else:
+ cur_index = target_index + 16
+ else:
+ cur_index = data_size
+ # Sort all the collect Fv image with offset.
+ Fd_Struct.sort(key=lambda x:x[1])
+ tmp_struct = copy.deepcopy(Fd_Struct)
+ tmp_index = 0
+ Fv_num = len(Fd_Struct)
+ # Remove the Fv image included in another Fv image.
+ for i in range(1,Fv_num):
+ if tmp_struct[i][1]+tmp_struct[i][2][0] < tmp_struct[i-1][1]+tmp_struct[i-1][2][0]:
+ Fd_Struct.remove(Fd_Struct[i-tmp_index])
+ tmp_index += 1
+ return Fd_Struct
+
+class ParserEntry():
+ FactoryTable:dict = {
+ SECTION_TREE: SectionFactory,
+ ROOT_SECTION_TREE: FfsFactory,
+ FFS_TREE: FfsFactory,
+ ROOT_FFS_TREE: FvFactory,
+ FV_TREE: FvFactory,
+ SEC_FV_TREE: FvFactory,
+ ROOT_FV_TREE: FdFactory,
+ ROOT_TREE: FdFactory,
+ }
+
+ def GetTargetFactory(self, Tree_type: str) -> BinaryFactory:
+ if Tree_type in self.FactoryTable:
+ return self.FactoryTable[Tree_type]
+
+ def Generate_Product(self, TargetFactory: BinaryFactory, Tree, Data: bytes, Offset: int) -> None:
+ New_Product = TargetFactory.Create_Product()
+ New_Product.ParserData(Tree, Data, Offset)
+
+ def DataParser(self, Tree, Data: bytes, Offset: int) -> None:
+ TargetFactory = self.GetTargetFactory(Tree.type)
+ if TargetFactory:
+ self.Generate_Product(TargetFactory, Tree, Data, Offset)
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/BiosTree.py b/BaseTools/Source/Python/FMMT/core/BiosTree.py
new file mode 100644
index 000000000000..b2cdb49f9f3f
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BiosTree.py
@@ -0,0 +1,199 @@
+## @file
+# This file is used to define the Bios layout tree structure and related operations.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import collections
+from typing import OrderedDict
+from PI.Common import *
+
+ROOT_TREE = 'ROOT'
+ROOT_FV_TREE = 'ROOT_FV_TREE'
+ROOT_FFS_TREE = 'ROOT_FFS_TREE'
+ROOT_SECTION_TREE = 'ROOT_SECTION_TREE'
+
+FV_TREE = 'FV'
+DATA_FV_TREE = 'DATA_FV'
+FFS_TREE = 'FFS'
+FFS_PAD = 'FFS_PAD'
+FFS_FREE_SPACE = 'FFS_FREE_SPACE'
+SECTION_TREE = 'SECTION'
+SEC_FV_TREE = 'SEC_FV_IMAGE'
+BINARY_DATA = 'BINARY'
+
+RootType = [ROOT_TREE, ROOT_FV_TREE, ROOT_FFS_TREE, ROOT_SECTION_TREE]
+FvType = [FV_TREE, SEC_FV_TREE]
+FfsType = FFS_TREE
+SecType = SECTION_TREE
+
+class BIOSTREE:
+ def __init__(self, NodeName: str) -> None:
+ self.key = NodeName
+ self.type = None
+ self.Data = None
+ self.Child = []
+ self.Findlist = []
+ self.Parent = None
+ self.NextRel = None
+ self.LastRel = None
+
+ def HasChild(self) -> bool:
+ if self.Child == []:
+ return False
+ else:
+ return True
+
+ def isFinalChild(self) -> bool:
+ ParTree = self.Parent
+ if ParTree:
+ if ParTree.Child[-1] == self:
+ return True
+ return False
+
+ # FvTree.insertChild()
+ def insertChild(self, newNode, pos: int=None) -> None:
+ if len(self.Child) == 0:
+ self.Child.append(newNode)
+ else:
+ if not pos:
+ LastTree = self.Child[-1]
+ self.Child.append(newNode)
+ LastTree.NextRel = newNode
+ newNode.LastRel = LastTree
+ else:
+ newNode.NextRel = self.Child[pos-1].NextRel
+ newNode.LastRel = self.Child[pos].LastRel
+ self.Child[pos-1].NextRel = newNode
+ self.Child[pos].LastRel = newNode
+ self.Child.insert(pos, newNode)
+ newNode.Parent = self
+
+ # lastNode.insertRel(newNode)
+ def insertRel(self, newNode) -> None:
+ if self.Parent:
+ parentTree = self.Parent
+ new_index = parentTree.Child.index(self) + 1
+ parentTree.Child.insert(new_index, newNode)
+ self.NextRel = newNode
+ newNode.LastRel = self
+
+ def deleteNode(self, deletekey: str) -> None:
+ FindStatus, DeleteTree = self.FindNode(deletekey)
+ if FindStatus:
+ parentTree = DeleteTree.Parent
+ lastTree = DeleteTree.LastRel
+ nextTree = DeleteTree.NextRel
+ if parentTree:
+ index = parentTree.Child.index(DeleteTree)
+ del parentTree.Child[index]
+ if lastTree and nextTree:
+ lastTree.NextRel = nextTree
+ nextTree.LastRel = lastTree
+ elif lastTree:
+ lastTree.NextRel = None
+ elif nextTree:
+ nextTree.LastRel = None
+ return DeleteTree
+ else:
+ print('Could not find the target tree')
+ return None
+
+ def FindNode(self, key: str, Findlist: list) -> None:
+ if self.key == key or (self.Data and self.Data.Name == key) or (self.type == FFS_TREE and self.Data.UiName == key):
+ Findlist.append(self)
+ else:
+ for item in self.Child:
+ item.FindNode(key, Findlist)
+
+ def GetTreePath(self):
+ BiosTreePath = [self]
+ while self.Parent:
+ BiosTreePath.insert(0, self.Parent)
+ self = self.Parent
+ return BiosTreePath
+
+ def parserTree(self, TargetDict: dict=None, Info: list=None, space: int=0) -> None:
+ Key = list(TargetDict.keys())[0]
+ if TargetDict[Key]["Type"] in RootType:
+ Info.append("Image File: {}".format(Key))
+ Info.append("FilesNum: {}".format(TargetDict.get(Key).get('FilesNum')))
+ Info.append("\n")
+ elif TargetDict[Key]["Type"] in FvType:
+ space += 2
+ if TargetDict[Key]["Type"] == SEC_FV_TREE:
+ Info.append("{}Child FV named {} of {}".format(space*" ", Key, self.FvId))
+ space += 2
+ else:
+ Info.append("FvId: {}".format(Key))
+ self.FvId = Key
+ Info.append("{}FvNameGuid: {}".format(space*" ", TargetDict.get(Key).get('FvNameGuid')))
+ Info.append("{}Attributes: {}".format(space*" ", TargetDict.get(Key).get('Attributes')))
+ Info.append("{}Total Volume Size: {}".format(space*" ", TargetDict.get(Key).get('Size')))
+ Info.append("{}Free Volume Size: {}".format(space*" ", TargetDict.get(Key).get('FreeSize')))
+ Info.append("{}Volume Offset: {}".format(space*" ", TargetDict.get(Key).get('Offset')))
+ Info.append("{}FilesNum: {}".format(space*" ", TargetDict.get(Key).get('FilesNum')))
+ elif TargetDict[Key]["Type"] in FfsType:
+ space += 2
+ if TargetDict.get(Key).get('UiName') != "b''":
+ Info.append("{}File: {} / {}".format(space*" ", Key, TargetDict.get(Key).get('UiName')))
+ else:
+ Info.append("{}File: {}".format(space*" ", Key))
+ if "Files" in list(TargetDict[Key].keys()):
+ for item in TargetDict[Key]["Files"]:
+ self.parserTree(item, Info, space)
+
+ def ExportTree(self,TreeInfo: dict=None) -> dict:
+ if TreeInfo is None:
+ TreeInfo =collections.OrderedDict()
+
+ if self.type == ROOT_TREE or self.type == ROOT_FV_TREE or self.type == ROOT_FFS_TREE or self.type == ROOT_SECTION_TREE:
+ key = str(self.key)
+ TreeInfo[self.key] = collections.OrderedDict()
+ TreeInfo[self.key]["Name"] = key
+ TreeInfo[self.key]["Type"] = self.type
+ TreeInfo[self.key]["FilesNum"] = len(self.Child)
+ elif self.type == FV_TREE or self.type == SEC_FV_TREE:
+ key = str(self.Data.FvId)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ if self.Data.FvId != self.Data.Name:
+ TreeInfo[key]["FvNameGuid"] = str(self.Data.Name)
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Attributes"] = hex(self.Data.Header.Attributes)
+ TreeInfo[key]["Size"] = hex(self.Data.Header.FvLength)
+ TreeInfo[key]["FreeSize"] = hex(self.Data.Free_Space)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+ elif self.type == FFS_TREE:
+ key = str(self.Data.Name)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ TreeInfo[key]["UiName"] = '{}'.format(self.Data.UiName)
+ TreeInfo[key]["Version"] = '{}'.format(self.Data.Version)
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Size"] = hex(self.Data.Size)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+ elif self.type == SECTION_TREE and self.Data.Type == 0x02:
+ key = str(self.Data.Name)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Size"] = hex(len(self.Data.OriData) + self.Data.HeaderLength)
+ TreeInfo[key]["DecompressedSize"] = hex(self.Data.Size)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+ elif self is not None:
+ key = str(self.Data.Name)
+ TreeInfo[key] = collections.OrderedDict()
+ TreeInfo[key]["Name"] = key
+ TreeInfo[key]["Type"] = self.type
+ TreeInfo[key]["Size"] = hex(self.Data.Size)
+ TreeInfo[key]["Offset"] = hex(self.Data.HOffset)
+ TreeInfo[key]["FilesNum"] = len(self.Child)
+
+ for item in self.Child:
+ TreeInfo[key].setdefault('Files',[]).append( item.ExportTree())
+
+ return TreeInfo
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/BiosTreeNode.py b/BaseTools/Source/Python/FMMT/core/BiosTreeNode.py
new file mode 100644
index 000000000000..b77d9100e7b3
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BiosTreeNode.py
@@ -0,0 +1,191 @@
+## @file
+# This file is used to define the BIOS Tree Node.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from PI.FvHeader import *
+from PI.FfsFileHeader import *
+from PI.SectionHeader import *
+from PI.Common import *
+import uuid
+
+SectionHeaderType = {
+ 0x01:'EFI_COMPRESSION_SECTION',
+ 0x02:'EFI_GUID_DEFINED_SECTION',
+ 0x03:'EFI_SECTION_DISPOSABLE',
+ 0x10:'EFI_SECTION_PE32',
+ 0x11:'EFI_SECTION_PIC',
+ 0x12:'EFI_SECTION_TE',
+ 0x13:'EFI_SECTION_DXE_DEPEX',
+ 0x14:'EFI_SECTION_VERSION',
+ 0x15:'EFI_SECTION_USER_INTERFACE',
+ 0x16:'EFI_SECTION_COMPATIBILITY16',
+ 0x17:'EFI_SECTION_FIRMWARE_VOLUME_IMAGE',
+ 0x18:'EFI_FREEFORM_SUBTYPE_GUID_SECTION',
+ 0x19:'EFI_SECTION_RAW',
+ 0x1B:'EFI_SECTION_PEI_DEPEX',
+ 0x1C:'EFI_SECTION_MM_DEPEX'
+}
+HeaderType = [0x01, 0x02, 0x14, 0x15, 0x18]
+
+class BinaryNode:
+ def __init__(self, name: str) -> None:
+ self.Size = 0
+ self.Name = "BINARY" + str(name)
+ self.HOffset = 0
+ self.Data = b''
+
+class FvNode:
+ def __init__(self, name, buffer: bytes) -> None:
+ self.Header = EFI_FIRMWARE_VOLUME_HEADER.from_buffer_copy(buffer)
+ Map_num = (self.Header.HeaderLength - 56)//8
+ self.Header = Refine_FV_Header(Map_num).from_buffer_copy(buffer)
+ self.FvId = "FV" + str(name)
+ self.Name = "FV" + str(name)
+ if self.Header.ExtHeaderOffset:
+ self.ExtHeader = EFI_FIRMWARE_VOLUME_EXT_HEADER.from_buffer_copy(buffer[self.Header.ExtHeaderOffset:])
+ self.Name = uuid.UUID(bytes_le=struct2stream(self.ExtHeader.FvName))
+ self.ExtEntryOffset = self.Header.ExtHeaderOffset + 20
+ if self.ExtHeader.ExtHeaderSize != 20:
+ self.ExtEntryExist = 1
+ self.ExtEntry = EFI_FIRMWARE_VOLUME_EXT_ENTRY.from_buffer_copy(buffer[self.ExtEntryOffset:])
+ self.ExtTypeExist = 1
+ if self.ExtEntry.ExtEntryType == 0x01:
+ nums = (self.ExtEntry.ExtEntrySize - 8) // 16
+ self.ExtEntry = Refine_FV_EXT_ENTRY_OEM_TYPE_Header(nums).from_buffer_copy(buffer[self.ExtEntryOffset:])
+ elif self.ExtEntry.ExtEntryType == 0x02:
+ nums = self.ExtEntry.ExtEntrySize - 20
+ self.ExtEntry = Refine_FV_EXT_ENTRY_GUID_TYPE_Header(nums).from_buffer_copy(buffer[self.ExtEntryOffset:])
+ elif self.ExtEntry.ExtEntryType == 0x03:
+ self.ExtEntry = EFI_FIRMWARE_VOLUME_EXT_ENTRY_USED_SIZE_TYPE.from_buffer_copy(buffer[self.ExtEntryOffset:])
+ else:
+ self.ExtTypeExist = 0
+ else:
+ self.ExtEntryExist = 0
+ self.Size = self.Header.FvLength
+ self.HeaderLength = self.Header.HeaderLength
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.Data = b''
+ if self.Header.Signature != 1213613663:
+ print('Invalid! Fv Header Signature {} is not "_FVH".'.format(self.Header.Signature))
+ with open(str(self.Name)+'.fd', "wb") as f:
+ f.write(struct2stream(self.Header))
+ assert False
+ self.PadData = b''
+ self.Free_Space = 0
+ self.ModCheckSum()
+
+ def ModCheckSum(self) -> None:
+ # Fv Header Sums to 0.
+ Header = struct2stream(self.Header)[::-1]
+ Size = self.HeaderLength // 2
+ Sum = 0
+ for i in range(Size):
+ Sum += int(Header[i*2: i*2 + 2].hex(), 16)
+ if Sum & 0xffff:
+ self.Header.Checksum = int(hex(0x10000 - int(hex(Sum - self.Header.Checksum)[-4:], 16)), 16)
+
+ def ModFvExt(self) -> None:
+ # If used space changes and self.ExtEntry.UsedSize exists, self.ExtEntry.UsedSize need to be changed.
+ if self.Header.ExtHeaderOffset and self.ExtEntryExist and self.ExtTypeExist and self.ExtEntry.Hdr.ExtEntryType == 0x03:
+ self.ExtEntry.UsedSize = self.Header.FvLength - self.Free_Space
+
+ def ModFvSize(self) -> None:
+ # If Fv Size changed, self.Header.FvLength and self.Header.BlockMap[i].NumBlocks need to be changed.
+ BlockMapNum = len(self.Header.BlockMap)
+ for i in range(BlockMapNum):
+ if self.Header.BlockMap[i].Length:
+ self.Header.BlockMap[i].NumBlocks = self.Header.FvLength // self.Header.BlockMap[i].Length
+
+ def ModExtHeaderData(self) -> None:
+ if self.Header.ExtHeaderOffset:
+ ExtHeaderData = struct2stream(self.ExtHeader)
+ ExtHeaderDataOffset = self.Header.ExtHeaderOffset - self.HeaderLength
+ self.Data = self.Data[:ExtHeaderDataOffset] + ExtHeaderData + self.Data[ExtHeaderDataOffset+20:]
+ if self.Header.ExtHeaderOffset and self.ExtEntryExist:
+ ExtHeaderEntryData = struct2stream(self.ExtEntry)
+ ExtHeaderEntryDataOffset = self.Header.ExtHeaderOffset + 20 - self.HeaderLength
+ self.Data = self.Data[:ExtHeaderEntryDataOffset] + ExtHeaderEntryData + self.Data[ExtHeaderEntryDataOffset+len(ExtHeaderEntryData):]
+
+class FfsNode:
+ def __init__(self, buffer: bytes) -> None:
+ self.Header = EFI_FFS_FILE_HEADER.from_buffer_copy(buffer)
+ # self.Attributes = unpack("<B", buffer[21:22])[0]
+ if self.Header.Size != 0 and self.Header.Attributes == 0x01:
+ print('Error Ffs Header! Ffs Header Size and Attributes is not matched!')
+ if self.Header.Size == 0 and self.Header.Attributes == 0x01:
+ self.Header = EFI_FFS_FILE_HEADER2.from_buffer_copy(buffer)
+ self.Name = uuid.UUID(bytes_le=struct2stream(self.Header.Name))
+ self.UiName = b''
+ self.Version = b''
+ self.Size = self.Header.FFS_FILE_SIZE
+ self.HeaderLength = self.Header.HeaderLength
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.Data = b''
+ self.PadData = b''
+
+ def ModCheckSum(self) -> None:
+ HeaderData = struct2stream(self.Header)
+ HeaderSum = 0
+ for item in HeaderData:
+ HeaderSum += item
+ HeaderSum -= self.Header.State
+ HeaderSum -= self.Header.IntegrityCheck.Checksum.File
+ if HeaderSum & 0xff:
+ Header = self.Header.IntegrityCheck.Checksum.Header + 0x100 - int(hex(HeaderSum)[-2:], 16)
+ self.Header.IntegrityCheck.Checksum.Header = int(hex(Header)[-2:], 16)
+
+class SectionNode:
+ def __init__(self, buffer: bytes) -> None:
+ if buffer[0:3] != b'\xff\xff\xff':
+ self.Header = EFI_COMMON_SECTION_HEADER.from_buffer_copy(buffer)
+ else:
+ self.Header = EFI_COMMON_SECTION_HEADER2.from_buffer_copy(buffer)
+ if self.Header.Type in SectionHeaderType:
+ self.Name = SectionHeaderType[self.Header.Type]
+ elif self.Header.Type == 0:
+ self.Name = "EFI_SECTION_RAW"
+ else:
+ self.Name = "SECTION"
+ if self.Header.Type in HeaderType:
+ self.ExtHeader = self.GetExtHeader(self.Header.Type, buffer[self.Header.Common_Header_Size():], (self.Header.SECTION_SIZE-self.Header.Common_Header_Size()))
+ self.HeaderLength = self.Header.Common_Header_Size() + self.ExtHeader.ExtHeaderSize()
+ else:
+ self.ExtHeader = None
+ self.HeaderLength = self.Header.Common_Header_Size()
+ self.Size = self.Header.SECTION_SIZE
+ self.Type = self.Header.Type
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.Data = b''
+ self.OriData = b''
+ self.OriHeader = b''
+ self.PadData = b''
+
+ def GetExtHeader(self, Type: int, buffer: bytes, nums: int=0) -> None:
+ if Type == 0x01:
+ return EFI_COMPRESSION_SECTION.from_buffer_copy(buffer)
+ elif Type == 0x02:
+ return EFI_GUID_DEFINED_SECTION.from_buffer_copy(buffer)
+ elif Type == 0x14:
+ return Get_VERSION_Header((nums - 2)//2).from_buffer_copy(buffer)
+ elif Type == 0x15:
+ return Get_USER_INTERFACE_Header(nums//2).from_buffer_copy(buffer)
+ elif Type == 0x18:
+ return EFI_FREEFORM_SUBTYPE_GUID_SECTION.from_buffer_copy(buffer)
+
+class FreeSpaceNode:
+ def __init__(self, buffer: bytes) -> None:
+ self.Name = 'Free_Space'
+ self.Data = buffer
+ self.Size = len(buffer)
+ self.HOffset = 0
+ self.DOffset = 0
+ self.ROffset = 0
+ self.PadData = b''
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/FMMTOperation.py b/BaseTools/Source/Python/FMMT/core/FMMTOperation.py
new file mode 100644
index 000000000000..e42565965909
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/FMMTOperation.py
@@ -0,0 +1,140 @@
+## @file
+# This file is used to define the functions to operate bios binary file.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from core.FMMTParser import *
+from core.FvHandler import *
+from utils.FvLayoutPrint import *
+
+global Fv_count
+Fv_count = 0
+
+# The ROOT_TYPE can be 'ROOT_TREE', 'ROOT_FV_TREE', 'ROOT_FFS_TREE', 'ROOT_SECTION_TREE'
+def ParserFile(inputfile: str, ROOT_TYPE: str, logfile: str=None, outputfile: str=None) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TYPE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ # 3. Log Output
+ InfoDict = FmmtParser.WholeFvTree.ExportTree()
+ FmmtParser.WholeFvTree.parserTree(InfoDict, FmmtParser.BinaryInfo)
+ GetFormatter("").LogPrint(FmmtParser.BinaryInfo)
+ if logfile:
+ GetFormatter(logfile.lower()).dump(InfoDict, FmmtParser.BinaryInfo,"Parser_Log_{}{}".format(os.path.basename(inputfile),".{}".format(logfile.lower())))
+ # 4. Data Encapsultion
+ if outputfile:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def DeleteFfs(inputfile: str, TargetFfs_name: str, outputfile: str, Fv_name: str=None) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ # 3. Data Modify
+ FmmtParser.WholeFvTree.FindNode(TargetFfs_name, FmmtParser.WholeFvTree.Findlist)
+ # Choose the Specfic DeleteFfs with Fv info
+ if Fv_name:
+ for item in FmmtParser.WholeFvTree.Findlist:
+ if item.Parent.key != Fv_name and item.Parent.Data.Name != Fv_name:
+ FmmtParser.WholeFvTree.Findlist.remove(item)
+ if FmmtParser.WholeFvTree.Findlist != []:
+ for Delete_Ffs in FmmtParser.WholeFvTree.Findlist:
+ FfsMod = FvHandler(None, Delete_Ffs)
+ Status = FfsMod.DeleteFfs()
+ else:
+ print('Target Ffs not found!!!')
+ # 4. Data Encapsultion
+ if Status:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def AddNewFfs(inputfile: str, Fv_name: str, newffsfile: str, outputfile: str) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ # Get Target Fv and Target Ffs_Pad
+ FmmtParser.WholeFvTree.FindNode(Fv_name, FmmtParser.WholeFvTree.Findlist)
+ # Create new ffs Tree
+ with open(newffsfile, "rb") as f:
+ new_ffs_data = f.read()
+ NewFmmtParser = FMMTParser(newffsfile, ROOT_FFS_TREE)
+ Status = False
+ # 3. Data Modify
+ if FmmtParser.WholeFvTree.Findlist:
+ for TargetFv in FmmtParser.WholeFvTree.Findlist:
+ TargetFfsPad = TargetFv.Child[-1]
+ if TargetFfsPad.type == FFS_FREE_SPACE:
+ NewFmmtParser.ParserFromRoot(NewFmmtParser.WholeFvTree, new_ffs_data, TargetFfsPad.Data.HOffset)
+ else:
+ NewFmmtParser.ParserFromRoot(NewFmmtParser.WholeFvTree, new_ffs_data, TargetFfsPad.Data.HOffset+TargetFfsPad.Data.Size)
+ FfsMod = FvHandler(NewFmmtParser.WholeFvTree.Child[0], TargetFfsPad)
+ Status = FfsMod.AddFfs()
+ else:
+ print('Target Fv not found!!!')
+ # 4. Data Encapsultion
+ if Status:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def ReplaceFfs(inputfile: str, Ffs_name: str, newffsfile: str, outputfile: str, Fv_name: str=None) -> None:
+ # 1. Data Prepare
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ # 2. DataTree Create
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ with open(newffsfile, "rb") as f:
+ new_ffs_data = f.read()
+ newFmmtParser = FMMTParser(newffsfile, FV_TREE)
+ newFmmtParser.ParserFromRoot(newFmmtParser.WholeFvTree, new_ffs_data)
+ Status = False
+ # 3. Data Modify
+ new_ffs = newFmmtParser.WholeFvTree.Child[0]
+ new_ffs.Data.PadData = GetPadSize(new_ffs.Data.Size, 8) * b'\xff'
+ FmmtParser.WholeFvTree.FindNode(Ffs_name, FmmtParser.WholeFvTree.Findlist)
+ if Fv_name:
+ for item in FmmtParser.WholeFvTree.Findlist:
+ if item.Parent.key != Fv_name and item.Parent.Data.Name != Fv_name:
+ FmmtParser.WholeFvTree.Findlist.remove(item)
+ if FmmtParser.WholeFvTree.Findlist != []:
+ for TargetFfs in FmmtParser.WholeFvTree.Findlist:
+ FfsMod = FvHandler(newFmmtParser.WholeFvTree.Child[0], TargetFfs)
+ Status = FfsMod.ReplaceFfs()
+ else:
+ print('Target Ffs not found!!!')
+ # 4. Data Encapsultion
+ if Status:
+ FmmtParser.Encapsulation(FmmtParser.WholeFvTree, False)
+ with open(outputfile, "wb") as f:
+ f.write(FmmtParser.FinalData)
+
+def ExtractFfs(inputfile: str, Ffs_name: str, outputfile: str) -> None:
+ with open(inputfile, "rb") as f:
+ whole_data = f.read()
+ FmmtParser = FMMTParser(inputfile, ROOT_TREE)
+ FmmtParser.ParserFromRoot(FmmtParser.WholeFvTree, whole_data)
+ FmmtParser.WholeFvTree.FindNode(Ffs_name, FmmtParser.WholeFvTree.Findlist)
+ if FmmtParser.WholeFvTree.Findlist != []:
+ TargetNode = FmmtParser.WholeFvTree.Findlist[0]
+ TargetFv = TargetNode.Parent
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ TargetNode.Data.Header.State = c_uint8(
+ ~TargetNode.Data.Header.State)
+ FinalData = struct2stream(TargetNode.Data.Header) + TargetNode.Data.Data
+ with open(outputfile, "wb") as f:
+ f.write(FinalData)
+ else:
+ print('Target Ffs not found!!!')
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/core/FMMTParser.py b/BaseTools/Source/Python/FMMT/core/FMMTParser.py
new file mode 100644
index 000000000000..6f35d4dd76cf
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/FMMTParser.py
@@ -0,0 +1,86 @@
+## @file
+# This file is used to define the interface of Bios Parser.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+from PI.Common import *
+from core.BinaryFactoryProduct import ParserEntry
+from core.BiosTreeNode import *
+from core.BiosTree import *
+from core.GuidTools import *
+
+class FMMTParser:
+ def __init__(self, name: str, TYPE: str) -> None:
+ self.WholeFvTree = BIOSTREE(name)
+ self.WholeFvTree.type = TYPE
+ self.FinalData = b''
+ self.BinaryInfo = []
+
+ ## Parser the nodes in WholeTree.
+ def ParserFromRoot(self, WholeFvTree=None, whole_data: bytes=b'', Reloffset: int=0) -> None:
+ if WholeFvTree.type == ROOT_TREE or WholeFvTree.type == ROOT_FV_TREE:
+ ParserEntry().DataParser(self.WholeFvTree, whole_data, Reloffset)
+ else:
+ ParserEntry().DataParser(WholeFvTree, whole_data, Reloffset)
+ for Child in WholeFvTree.Child:
+ self.ParserFromRoot(Child, "")
+
+ ## Encapuslation all the data in tree into self.FinalData
+ def Encapsulation(self, rootTree, CompressStatus: bool) -> None:
+ # If current node is Root node, skip it.
+ if rootTree.type == ROOT_TREE or rootTree.type == ROOT_FV_TREE or rootTree.type == ROOT_FFS_TREE or rootTree.type == ROOT_SECTION_TREE:
+ print('Start at Root !')
+ # If current node do not have Header, just add Data.
+ elif rootTree.type == BINARY_DATA or rootTree.type == FFS_FREE_SPACE:
+ self.FinalData += rootTree.Data.Data
+ rootTree.Child = []
+ # If current node do not have Child and ExtHeader, just add its Header and Data.
+ elif rootTree.type == DATA_FV_TREE or rootTree.type == FFS_PAD:
+ self.FinalData += struct2stream(rootTree.Data.Header) + rootTree.Data.Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ if ParTree.type != 'ROOT':
+ self.FinalData += ParTree.Data.PadData
+ rootTree.Child = []
+ # If current node is not Section node and may have Child and ExtHeader, add its Header,ExtHeader. If do not have Child, add its Data.
+ elif rootTree.type == FV_TREE or rootTree.type == FFS_TREE or rootTree.type == SEC_FV_TREE:
+ if rootTree.HasChild():
+ self.FinalData += struct2stream(rootTree.Data.Header)
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header) + rootTree.Data.Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ if ParTree.type != 'ROOT':
+ self.FinalData += ParTree.Data.PadData
+ # If current node is Section, need to consider its ExtHeader, Child and Compressed Status.
+ elif rootTree.type == SECTION_TREE:
+ # Not compressed section
+ if rootTree.Data.OriData == b'' or (rootTree.Data.OriData != b'' and CompressStatus):
+ if rootTree.HasChild():
+ if rootTree.Data.ExtHeader:
+ self.FinalData += struct2stream(rootTree.Data.Header) + struct2stream(rootTree.Data.ExtHeader)
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header)
+ else:
+ Data = rootTree.Data.Data
+ if rootTree.Data.ExtHeader:
+ self.FinalData += struct2stream(rootTree.Data.Header) + struct2stream(rootTree.Data.ExtHeader) + Data + rootTree.Data.PadData
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header) + Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ self.FinalData += ParTree.Data.PadData
+ # If compressed section
+ else:
+ Data = rootTree.Data.OriData
+ rootTree.Child = []
+ if rootTree.Data.ExtHeader:
+ self.FinalData += struct2stream(rootTree.Data.Header) + struct2stream(rootTree.Data.ExtHeader) + Data + rootTree.Data.PadData
+ else:
+ self.FinalData += struct2stream(rootTree.Data.Header) + Data + rootTree.Data.PadData
+ if rootTree.isFinalChild():
+ ParTree = rootTree.Parent
+ self.FinalData += ParTree.Data.PadData
+ for Child in rootTree.Child:
+ self.Encapsulation(Child, CompressStatus)
diff --git a/BaseTools/Source/Python/FMMT/core/FvHandler.py b/BaseTools/Source/Python/FMMT/core/FvHandler.py
new file mode 100644
index 000000000000..7809e1598c1e
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/FvHandler.py
@@ -0,0 +1,521 @@
+## @file
+# This file is used to the implementation of Bios layout handler.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import os
+from core.BiosTree import *
+from core.GuidTools import GUIDTools
+from core.BiosTreeNode import *
+from PI.Common import *
+
+EFI_FVB2_ERASE_POLARITY = 0x00000800
+
+def ChangeSize(TargetTree, size_delta: int=0) -> None:
+ if type(TargetTree.Data.Header) == type(EFI_FFS_FILE_HEADER2()) or type(TargetTree.Data.Header) == type(EFI_COMMON_SECTION_HEADER2()):
+ TargetTree.Data.Size -= size_delta
+ TargetTree.Data.Header.ExtendedSize -= size_delta
+ elif TargetTree.type == SECTION_TREE and TargetTree.Data.OriData:
+ OriSize = TargetTree.Data.Header.SECTION_SIZE
+ OriSize -= size_delta
+ TargetTree.Data.Header.Size[0] = OriSize % (16**2)
+ TargetTree.Data.Header.Size[1] = OriSize % (16**4) //(16**2)
+ TargetTree.Data.Header.Size[2] = OriSize // (16**4)
+ else:
+ TargetTree.Data.Size -= size_delta
+ TargetTree.Data.Header.Size[0] = TargetTree.Data.Size % (16**2)
+ TargetTree.Data.Header.Size[1] = TargetTree.Data.Size % (16**4) //(16**2)
+ TargetTree.Data.Header.Size[2] = TargetTree.Data.Size // (16**4)
+
+def ModifyFfsType(TargetFfs) -> None:
+ if type(TargetFfs.Data.Header) == type(EFI_FFS_FILE_HEADER()) and (TargetFfs.Data.HeaderLength + TargetFfs.Data.Size) > 0xFFFFFF:
+ ExtendSize = TargetFfs.Data.Header.FFS_FILE_SIZE + 8
+ New_Header = EFI_FFS_FILE_HEADER2()
+ New_Header.Name = TargetFfs.Data.Header.Name
+ New_Header.IntegrityCheck = TargetFfs.Data.Header.IntegrityCheck
+ New_Header.Type = TargetFfs.Data.Header.Type
+ New_Header.Attributes = TargetFfs.Data.Header.Attributes
+ New_Header.Size = 0
+ New_Header.State = TargetFfs.Data.Header.State
+ New_Header.ExtendedSize = ExtendSize
+ TargetFfs.Data.Header = New_Header
+ TargetFfs.Data.Size = TargetFfs.Data.Header.FFS_FILE_SIZE
+ TargetFfs.Data.HeaderLength = TargetFfs.Data.Header.HeaderLength
+ TargetFfs.Data.ModCheckSum()
+ elif type(TargetFfs.Data.Header) == type(EFI_FFS_FILE_HEADER2()) and (TargetFfs.Data.HeaderLength + TargetFfs.Data.Size) <= 0xFFFFFF:
+ New_Header = EFI_FFS_FILE_HEADER()
+ New_Header.Name = TargetFfs.Data.Header.Name
+ New_Header.IntegrityCheck = TargetFfs.Data.Header.IntegrityCheck
+ New_Header.Type = TargetFfs.Data.Header.Type
+ New_Header.Attributes = TargetFfs.Data.Header.Attributes
+ New_Header.Size = TargetFfs.Data.HeaderLength + TargetFfs.Data.Size
+ New_Header.State = TargetFfs.Data.Header.State
+ TargetFfs.Data.Header = New_Header
+ TargetFfs.Data.Size = TargetFfs.Data.Header.FFS_FILE_SIZE
+ TargetFfs.Data.HeaderLength = TargetFfs.Data.Header.HeaderLength
+ TargetFfs.Data.ModCheckSum()
+ if struct2stream(TargetFfs.Parent.Data.Header.FileSystemGuid) == EFI_FIRMWARE_FILE_SYSTEM3_GUID_BYTE:
+ NeedChange = True
+ for item in TargetFfs.Parent.Child:
+ if type(item.Data.Header) == type(EFI_FFS_FILE_HEADER2()):
+ NeedChange = False
+ if NeedChange:
+ TargetFfs.Parent.Data.Header.FileSystemGuid = ModifyGuidFormat("8c8ce578-8a3d-4f1c-9935-896185c32dd3")
+
+ if type(TargetFfs.Data.Header) == type(EFI_FFS_FILE_HEADER2()):
+ TarParent = TargetFfs.Parent
+ while TarParent:
+ if TarParent.type == FV_TREE and struct2stream(TarParent.Data.Header.FileSystemGuid) == EFI_FIRMWARE_FILE_SYSTEM2_GUID_BYTE:
+ TarParent.Data.Header.FileSystemGuid = ModifyGuidFormat("5473C07A-3DCB-4dca-BD6F-1E9689E7349A")
+ TarParent = TarParent.Parent
+
+class FvHandler:
+ def __init__(self, NewFfs, TargetFfs) -> None:
+ self.NewFfs = NewFfs
+ self.TargetFfs = TargetFfs
+ self.Status = False
+ self.Remain_New_Free_Space = 0
+
+ ## Use for Compress the Section Data
+ def CompressData(self, TargetTree) -> None:
+ TreePath = TargetTree.GetTreePath()
+ pos = len(TreePath)
+ self.Status = True
+ while pos:
+ if self.Status:
+ if TreePath[pos-1].type == SECTION_TREE and TreePath[pos-1].Data.Type == 0x02:
+ self.CompressSectionData(TreePath[pos-1], None, TreePath[pos-1].Data.ExtHeader.SectionDefinitionGuid)
+ else:
+ if pos == len(TreePath):
+ self.CompressSectionData(TreePath[pos-1], pos)
+ else:
+ self.CompressSectionData(TreePath[pos-1], None)
+ pos -= 1
+
+ def CompressSectionData(self, TargetTree, pos: int, GuidTool=None) -> None:
+ NewData = b''
+ temp_save_child = TargetTree.Child
+ if TargetTree.Data:
+ for item in temp_save_child:
+ if item.type == SECTION_TREE and not item.Data.OriData and item.Data.ExtHeader:
+ NewData += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.Data + item.Data.PadData
+ elif item.type == SECTION_TREE and item.Data.OriData and not item.Data.ExtHeader:
+ NewData += struct2stream(item.Data.Header) + item.Data.OriData + item.Data.PadData
+ elif item.type == SECTION_TREE and item.Data.OriData and item.Data.ExtHeader:
+ NewData += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.OriData + item.Data.PadData
+ elif item.type == FFS_FREE_SPACE:
+ NewData += item.Data.Data + item.Data.PadData
+ else:
+ NewData += struct2stream(item.Data.Header) + item.Data.Data + item.Data.PadData
+ if TargetTree.type == FFS_TREE:
+ New_Pad_Size = GetPadSize(len(NewData), 8)
+ Size_delta = len(NewData) - len(TargetTree.Data.Data)
+ ChangeSize(TargetTree, -Size_delta)
+ Delta_Pad_Size = len(TargetTree.Data.PadData) - New_Pad_Size
+ self.Remain_New_Free_Space += Delta_Pad_Size
+ TargetTree.Data.PadData = b'\xff' * New_Pad_Size
+ TargetTree.Data.ModCheckSum()
+ elif TargetTree.type == FV_TREE or TargetTree.type == SEC_FV_TREE and not pos:
+ if self.Remain_New_Free_Space:
+ if TargetTree.Data.Free_Space:
+ TargetTree.Data.Free_Space += self.Remain_New_Free_Space
+ NewData += self.Remain_New_Free_Space * b'\xff'
+ TargetTree.Child[-1].Data.Data += self.Remain_New_Free_Space * b'\xff'
+ else:
+ TargetTree.Data.Data += self.Remain_New_Free_Space * b'\xff'
+ New_Free_Space = BIOSTREE('FREE_SPACE')
+ New_Free_Space.type = FFS_FREE_SPACE
+ New_Free_Space.Data = FreeSpaceNode(b'\xff' * self.Remain_New_Free_Space)
+ TargetTree.insertChild(New_Free_Space)
+ self.Remain_New_Free_Space = 0
+ if TargetTree.type == SEC_FV_TREE:
+ Size_delta = len(NewData) + self.Remain_New_Free_Space - len(TargetTree.Data.Data)
+ TargetTree.Data.Header.FvLength += Size_delta
+ TargetTree.Data.ModFvExt()
+ TargetTree.Data.ModFvSize()
+ TargetTree.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetTree)
+ TargetTree.Data.ModCheckSum()
+ elif TargetTree.type == SECTION_TREE and TargetTree.Data.Type != 0x02:
+ New_Pad_Size = GetPadSize(len(NewData), 4)
+ Size_delta = len(NewData) - len(TargetTree.Data.Data)
+ ChangeSize(TargetTree, -Size_delta)
+ if TargetTree.NextRel:
+ Delta_Pad_Size = len(TargetTree.Data.PadData) - New_Pad_Size
+ self.Remain_New_Free_Space += Delta_Pad_Size
+ TargetTree.Data.PadData = b'\x00' * New_Pad_Size
+ TargetTree.Data.Data = NewData
+ if GuidTool:
+ ParPath = os.path.abspath(os.path.dirname(os.path.abspath(__file__))+os.path.sep+"..")
+ ToolPath = os.path.join(ParPath, r'FMMTConfig.ini')
+ guidtool = GUIDTools(ToolPath).__getitem__(struct2stream(GuidTool))
+ CompressedData = guidtool.pack(TargetTree.Data.Data)
+ if len(CompressedData) < len(TargetTree.Data.OriData):
+ New_Pad_Size = GetPadSize(len(CompressedData), 4)
+ Size_delta = len(CompressedData) - len(TargetTree.Data.OriData)
+ ChangeSize(TargetTree, -Size_delta)
+ if TargetTree.NextRel:
+ TargetTree.Data.PadData = b'\x00' * New_Pad_Size
+ self.Remain_New_Free_Space = len(TargetTree.Data.OriData) + len(TargetTree.Data.PadData) - len(CompressedData) - New_Pad_Size
+ else:
+ TargetTree.Data.PadData = b''
+ self.Remain_New_Free_Space = len(TargetTree.Data.OriData) - len(CompressedData)
+ TargetTree.Data.OriData = CompressedData
+ elif len(CompressedData) == len(TargetTree.Data.OriData):
+ TargetTree.Data.OriData = CompressedData
+ elif len(CompressedData) > len(TargetTree.Data.OriData):
+ New_Pad_Size = GetPadSize(CompressedData, 4)
+ self.Remain_New_Free_Space = len(CompressedData) + New_Pad_Size - len(TargetTree.Data.OriData) - len(TargetTree.Data.PadData)
+ Size_delta = len(TargetTree.Data.OriData) - len(CompressedData)
+ ChangeSize(TargetTree, -Size_delta)
+ if TargetTree.NextRel:
+ TargetTree.Data.PadData = b'\x00' * New_Pad_Size
+ TargetTree.Data.OriData = CompressedData
+ self.ModifyTest(TargetTree, self.Remain_New_Free_Space)
+ self.Status = False
+
+ def ModifyFvExtData(self, TreeNode) -> None:
+ FvExtData = b''
+ if TreeNode.Data.Header.ExtHeaderOffset:
+ FvExtHeader = struct2stream(TreeNode.Data.ExtHeader)
+ FvExtData += FvExtHeader
+ if TreeNode.Data.Header.ExtHeaderOffset and TreeNode.Data.ExtEntryExist:
+ FvExtEntry = struct2stream(TreeNode.Data.ExtEntry)
+ FvExtData += FvExtEntry
+ if FvExtData:
+ InfoNode = TreeNode.Child[0]
+ InfoNode.Data.Data = FvExtData + InfoNode.Data.Data[TreeNode.Data.ExtHeader.ExtHeaderSize:]
+ InfoNode.Data.ModCheckSum()
+
+ def ModifyTest(self, ParTree, Needed_Space: int) -> None:
+ if Needed_Space > 0:
+ if ParTree.type == FV_TREE or ParTree.type == SEC_FV_TREE:
+ ParTree.Data.Data = b''
+ Needed_Space = Needed_Space - ParTree.Data.Free_Space
+ if Needed_Space < 0:
+ ParTree.Child[-1].Data.Data = b'\xff' * (-Needed_Space)
+ ParTree.Data.Free_Space = (-Needed_Space)
+ self.Status = True
+ else:
+ if ParTree.type == FV_TREE:
+ self.Status = False
+ else:
+ BlockSize = ParTree.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - Needed_Space%BlockSize
+ if New_Add_Len % BlockSize:
+ ParTree.Child[-1].Data.Data = b'\xff' * New_Add_Len
+ ParTree.Data.Free_Space = New_Add_Len
+ Needed_Space += New_Add_Len
+ else:
+ ParTree.Child.remove(ParTree.Child[-1])
+ ParTree.Data.Free_Space = 0
+ ParTree.Data.Size += Needed_Space
+ ParTree.Data.Header.Fvlength = ParTree.Data.Size
+ for item in ParTree.Child:
+ if item.type == FFS_FREE_SPACE:
+ ParTree.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ ParTree.Data.ModFvExt()
+ ParTree.Data.ModFvSize()
+ ParTree.Data.ModExtHeaderData()
+ self.ModifyFvExtData(ParTree)
+ ParTree.Data.ModCheckSum()
+ elif ParTree.type == FFS_TREE:
+ ParTree.Data.Data = b''
+ for item in ParTree.Child:
+ if item.Data.OriData:
+ if item.Data.ExtHeader:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.OriData + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header)+ item.Data.OriData + item.Data.PadData
+ else:
+ if item.Data.ExtHeader:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.Data + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ ChangeSize(ParTree, -Needed_Space)
+ New_Pad_Size = GetPadSize(ParTree.Data.Size, 8)
+ Delta_Pad_Size = New_Pad_Size - len(ParTree.Data.PadData)
+ Needed_Space += Delta_Pad_Size
+ ParTree.Data.PadData = b'\xff' * GetPadSize(ParTree.Data.Size, 8)
+ ParTree.Data.ModCheckSum()
+ elif ParTree.type == SECTION_TREE:
+ OriData = ParTree.Data.Data
+ ParTree.Data.Data = b''
+ for item in ParTree.Child:
+ if item.type == SECTION_TREE and item.Data.ExtHeader and item.Data.Type != 0x02:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.Data + item.Data.PadData
+ elif item.type == SECTION_TREE and item.Data.ExtHeader and item.Data.Type == 0x02:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + struct2stream(item.Data.ExtHeader) + item.Data.OriData + item.Data.PadData
+ else:
+ ParTree.Data.Data += struct2stream(item.Data.Header) + item.Data.Data + item.Data.PadData
+ if ParTree.Data.Type == 0x02:
+ ParTree.Data.Size += Needed_Space
+ ParPath = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
+ ToolPath = os.path.join(os.path.dirname(ParPath), r'FMMTConfig.ini')
+ guidtool = GUIDTools(ToolPath).__getitem__(struct2stream(ParTree.Data.ExtHeader.SectionDefinitionGuid))
+ CompressedData = guidtool.pack(ParTree.Data.Data)
+ Needed_Space = len(CompressedData) - len(ParTree.Data.OriData)
+ ParTree.Data.OriData = CompressedData
+ New_Size = ParTree.Data.HeaderLength + len(CompressedData)
+ ParTree.Data.Header.Size[0] = New_Size % (16**2)
+ ParTree.Data.Header.Size[1] = New_Size % (16**4) //(16**2)
+ ParTree.Data.Header.Size[2] = New_Size // (16**4)
+ if ParTree.NextRel:
+ New_Pad_Size = GetPadSize(New_Size, 4)
+ Delta_Pad_Size = New_Pad_Size - len(ParTree.Data.PadData)
+ ParTree.Data.PadData = b'\x00' * New_Pad_Size
+ Needed_Space += Delta_Pad_Size
+ else:
+ ParTree.Data.PadData = b''
+ elif Needed_Space:
+ ChangeSize(ParTree, -Needed_Space)
+ New_Pad_Size = GetPadSize(ParTree.Data.Size, 4)
+ Delta_Pad_Size = New_Pad_Size - len(ParTree.Data.PadData)
+ Needed_Space += Delta_Pad_Size
+ ParTree.Data.PadData = b'\x00' * New_Pad_Size
+ NewParTree = ParTree.Parent
+ ROOT_TYPE = [ROOT_FV_TREE, ROOT_FFS_TREE, ROOT_SECTION_TREE, ROOT_TREE]
+ if NewParTree and NewParTree.type not in ROOT_TYPE:
+ self.ModifyTest(NewParTree, Needed_Space)
+ else:
+ self.Status = True
+
+ def ReplaceFfs(self) -> bool:
+ TargetFv = self.TargetFfs.Parent
+ # If the Fv Header Attributes is EFI_FVB2_ERASE_POLARITY, Child Ffs Header State need be reversed.
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ self.NewFfs.Data.Header.State = c_uint8(
+ ~self.NewFfs.Data.Header.State)
+ # NewFfs parsing will not calculate the PadSize, thus recalculate.
+ self.NewFfs.Data.PadData = b'\xff' * GetPadSize(self.NewFfs.Data.Size, 8)
+ if self.NewFfs.Data.Size > self.TargetFfs.Data.Size:
+ Needed_Space = self.NewFfs.Data.Size + len(self.NewFfs.Data.PadData) - self.TargetFfs.Data.Size - len(self.TargetFfs.Data.PadData)
+ # If TargetFv have enough free space, just move part of the free space to NewFfs.
+ if TargetFv.Data.Free_Space >= Needed_Space:
+ # Modify TargetFv Child info and BiosTree.
+ TargetFv.Child[-1].Data.Data = b'\xff' * (TargetFv.Data.Free_Space - Needed_Space)
+ TargetFv.Data.Free_Space -= Needed_Space
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ # Modify TargetFv Header and ExtHeader info.
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ # return the Status
+ self.Status = True
+ # If TargetFv do not have enough free space, need move part of the free space of TargetFv's parent Fv to TargetFv/NewFfs.
+ else:
+ if TargetFv.type == FV_TREE:
+ self.Status = False
+ else:
+ # Recalculate TargetFv needed space to keep it match the BlockSize setting.
+ Needed_Space -= TargetFv.Data.Free_Space
+ BlockSize = TargetFv.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - Needed_Space%BlockSize
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ if New_Add_Len % BlockSize:
+ TargetFv.Child[-1].Data.Data = b'\xff' * New_Add_Len
+ TargetFv.Data.Free_Space = New_Add_Len
+ Needed_Space += New_Add_Len
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ TargetFv.Child.remove(self.TargetFfs)
+ else:
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.Data.Free_Space = 0
+ TargetFv.insertChild(self.NewFfs)
+ # Encapsulate the Fv Data for update.
+ TargetFv.Data.Data = b''
+ for item in TargetFv.Child:
+ if item.type == FFS_FREE_SPACE:
+ TargetFv.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ TargetFv.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ TargetFv.Data.Size += Needed_Space
+ # Modify TargetFv Data Header and ExtHeader info.
+ TargetFv.Data.Header.FvLength = TargetFv.Data.Size
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ # Start free space calculating and moving process.
+ self.ModifyTest(TargetFv.Parent, Needed_Space)
+ else:
+ New_Free_Space = self.TargetFfs.Data.Size - self.NewFfs.Data.Size
+ # If TargetFv already have free space, move the new free space into it.
+ if TargetFv.Data.Free_Space:
+ TargetFv.Child[-1].Data.Data += b'\xff' * New_Free_Space
+ TargetFv.Data.Free_Space += New_Free_Space
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ self.Status = True
+ # If TargetFv do not have free space, create free space for Fv.
+ else:
+ New_Free_Space_Tree = BIOSTREE('FREE_SPACE')
+ New_Free_Space_Tree.type = FFS_FREE_SPACE
+ New_Free_Space_Tree.Data = FfsNode(b'\xff' * New_Free_Space)
+ TargetFv.Data.Free_Space = New_Free_Space
+ TargetFv.insertChild(New_Free_Space)
+ Target_index = TargetFv.Child.index(self.TargetFfs)
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs, Target_index)
+ self.Status = True
+ # Modify TargetFv Header and ExtHeader info.
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ return self.Status
+
+ def AddFfs(self) -> bool:
+ # NewFfs parsing will not calculate the PadSize, thus recalculate.
+ self.NewFfs.Data.PadData = b'\xff' * GetPadSize(self.NewFfs.Data.Size, 8)
+ if self.TargetFfs.type == FFS_FREE_SPACE:
+ TargetLen = self.NewFfs.Data.Size + len(self.NewFfs.Data.PadData) - self.TargetFfs.Data.Size - len(self.TargetFfs.Data.PadData)
+ TargetFv = self.TargetFfs.Parent
+ # If the Fv Header Attributes is EFI_FVB2_ERASE_POLARITY, Child Ffs Header State need be reversed.
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ self.NewFfs.Data.Header.State = c_uint8(
+ ~self.NewFfs.Data.Header.State)
+ # If TargetFv have enough free space, just move part of the free space to NewFfs, split free space to NewFfs and new free space.
+ if TargetLen < 0:
+ self.Status = True
+ self.TargetFfs.Data.Data = b'\xff' * (-TargetLen)
+ TargetFv.Data.Free_Space = (-TargetLen)
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ TargetFv.insertChild(self.NewFfs, -1)
+ ModifyFfsType(self.NewFfs)
+ elif TargetLen == 0:
+ self.Status = True
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs)
+ ModifyFfsType(self.NewFfs)
+ # If TargetFv do not have enough free space, need move part of the free space of TargetFv's parent Fv to TargetFv/NewFfs.
+ else:
+ if TargetFv.type == FV_TREE:
+ self.Status = False
+ elif TargetFv.type == SEC_FV_TREE:
+ # Recalculate TargetFv needed space to keep it match the BlockSize setting.
+ BlockSize = TargetFv.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - TargetLen%BlockSize
+ if New_Add_Len % BlockSize:
+ self.TargetFfs.Data.Data = b'\xff' * New_Add_Len
+ self.TargetFfs.Data.Size = New_Add_Len
+ TargetLen += New_Add_Len
+ TargetFv.insertChild(self.NewFfs, -1)
+ TargetFv.Data.Free_Space = New_Add_Len
+ else:
+ TargetFv.Child.remove(self.TargetFfs)
+ TargetFv.insertChild(self.NewFfs)
+ TargetFv.Data.Free_Space = 0
+ ModifyFfsType(self.NewFfs)
+ TargetFv.Data.Data = b''
+ for item in TargetFv.Child:
+ if item.type == FFS_FREE_SPACE:
+ TargetFv.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ TargetFv.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ # Encapsulate the Fv Data for update.
+ TargetFv.Data.Size += TargetLen
+ TargetFv.Data.Header.FvLength = TargetFv.Data.Size
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ # Start free space calculating and moving process.
+ self.ModifyTest(TargetFv.Parent, TargetLen)
+ else:
+ # If TargetFv do not have free space, need directly move part of the free space of TargetFv's parent Fv to TargetFv/NewFfs.
+ TargetLen = self.NewFfs.Data.Size + len(self.NewFfs.Data.PadData)
+ TargetFv = self.TargetFfs.Parent
+ if TargetFv.Data.Header.Attributes & EFI_FVB2_ERASE_POLARITY:
+ self.NewFfs.Data.Header.State = c_uint8(
+ ~self.NewFfs.Data.Header.State)
+ if TargetFv.type == FV_TREE:
+ self.Status = False
+ elif TargetFv.type == SEC_FV_TREE:
+ BlockSize = TargetFv.Data.Header.BlockMap[0].Length
+ New_Add_Len = BlockSize - TargetLen%BlockSize
+ if New_Add_Len % BlockSize:
+ New_Free_Space = BIOSTREE('FREE_SPACE')
+ New_Free_Space.type = FFS_FREE_SPACE
+ New_Free_Space.Data = FreeSpaceNode(b'\xff' * New_Add_Len)
+ TargetLen += New_Add_Len
+ TargetFv.Data.Free_Space = New_Add_Len
+ TargetFv.insertChild(self.NewFfs)
+ TargetFv.insertChild(New_Free_Space)
+ else:
+ TargetFv.insertChild(self.NewFfs)
+ ModifyFfsType(self.NewFfs)
+ TargetFv.Data.Data = b''
+ for item in TargetFv.Child:
+ if item.type == FFS_FREE_SPACE:
+ TargetFv.Data.Data += item.Data.Data + item.Data.PadData
+ else:
+ TargetFv.Data.Data += struct2stream(item.Data.Header)+ item.Data.Data + item.Data.PadData
+ TargetFv.Data.Size += TargetLen
+ TargetFv.Data.Header.FvLength = TargetFv.Data.Size
+ TargetFv.Data.ModFvExt()
+ TargetFv.Data.ModFvSize()
+ TargetFv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(TargetFv)
+ TargetFv.Data.ModCheckSum()
+ self.ModifyTest(TargetFv.Parent, TargetLen)
+ return self.Status
+
+ def DeleteFfs(self) -> bool:
+ Delete_Ffs = self.TargetFfs
+ Delete_Fv = Delete_Ffs.Parent
+ Add_Free_Space = Delete_Ffs.Data.Size + len(Delete_Ffs.Data.PadData)
+ if Delete_Fv.Data.Free_Space:
+ if Delete_Fv.type == SEC_FV_TREE:
+ Used_Size = Delete_Fv.Data.Size - Delete_Fv.Data.Free_Space - Add_Free_Space
+ BlockSize = Delete_Fv.Data.Header.BlockMap[0].Length
+ New_Free_Space = BlockSize - Used_Size % BlockSize
+ self.Remain_New_Free_Space += Delete_Fv.Data.Free_Space + Add_Free_Space - New_Free_Space
+ Delete_Fv.Child[-1].Data.Data = New_Free_Space * b'\xff'
+ Delete_Fv.Data.Free_Space = New_Free_Space
+ else:
+ Used_Size = Delete_Fv.Data.Size - Delete_Fv.Data.Free_Space - Add_Free_Space
+ Delete_Fv.Child[-1].Data.Data += Add_Free_Space * b'\xff'
+ Delete_Fv.Data.Free_Space += Add_Free_Space
+ New_Free_Space = Delete_Fv.Data.Free_Space + Add_Free_Space
+ else:
+ if Delete_Fv.type == SEC_FV_TREE:
+ Used_Size = Delete_Fv.Data.Size - Add_Free_Space
+ BlockSize = Delete_Fv.Data.Header.BlockMap[0].Length
+ New_Free_Space = BlockSize - Used_Size % BlockSize
+ self.Remain_New_Free_Space += Add_Free_Space - New_Free_Space
+ Add_Free_Space = New_Free_Space
+ else:
+ Used_Size = Delete_Fv.Data.Size - Add_Free_Space
+ New_Free_Space = Add_Free_Space
+ New_Free_Space_Info = FfsNode(Add_Free_Space * b'\xff')
+ New_Free_Space_Info.Data = Add_Free_Space * b'\xff'
+ New_Ffs_Tree = BIOSTREE(New_Free_Space_Info.Name)
+ New_Ffs_Tree.type = FFS_FREE_SPACE
+ New_Ffs_Tree.Data = New_Free_Space_Info
+ Delete_Fv.insertChild(New_Ffs_Tree)
+ Delete_Fv.Data.Free_Space = Add_Free_Space
+ Delete_Fv.Child.remove(Delete_Ffs)
+ Delete_Fv.Data.Header.FvLength = Used_Size + New_Free_Space
+ Delete_Fv.Data.ModFvExt()
+ Delete_Fv.Data.ModFvSize()
+ Delete_Fv.Data.ModExtHeaderData()
+ self.ModifyFvExtData(Delete_Fv)
+ Delete_Fv.Data.ModCheckSum()
+ self.CompressData(Delete_Fv)
+ self.Status = True
+ return self.Status
diff --git a/BaseTools/Source/Python/FMMT/core/GuidTools.py b/BaseTools/Source/Python/FMMT/core/GuidTools.py
new file mode 100644
index 000000000000..287b949cc32f
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/GuidTools.py
@@ -0,0 +1,152 @@
+## @file
+# This file is used to define the FMMT dependent external tool management class.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+import glob
+import logging
+import os
+import shutil
+import sys
+import tempfile
+import uuid
+from PI.Common import *
+from utils.FmmtLogger import FmmtLogger as logger
+import subprocess
+
+def ExecuteCommand(cmd: list) -> None:
+ subprocess.run(cmd,stdout=subprocess.DEVNULL)
+
+class GUIDTool:
+ def __init__(self, guid: str, short_name: str, command: str) -> None:
+ self.guid: str = guid
+ self.short_name: str = short_name
+ self.command: str = command
+
+ def pack(self, buffer: bytes) -> bytes:
+ """
+ compress file.
+ """
+ tool = self.command
+ if tool:
+ tmp = tempfile.mkdtemp(dir=os.environ.get('tmp'))
+ ToolInputFile = os.path.join(tmp, "pack_uncompress_sec_file")
+ ToolOuputFile = os.path.join(tmp, "pack_sec_file")
+ try:
+ file = open(ToolInputFile, "wb")
+ file.write(buffer)
+ file.close()
+ command = [tool, '-e', '-o', ToolOuputFile,
+ ToolInputFile]
+ ExecuteCommand(command)
+ buf = open(ToolOuputFile, "rb")
+ res_buffer = buf.read()
+ except Exception as msg:
+ logger.error(msg)
+ return ""
+ else:
+ buf.close()
+ if os.path.exists(tmp):
+ shutil.rmtree(tmp)
+ return res_buffer
+ else:
+ logger.error(
+ "Error parsing section: EFI_SECTION_GUID_DEFINED cannot be parsed at this time.")
+ logger.info("Its GUID is: %s" % self.guid)
+ return ""
+
+
+ def unpack(self, buffer: bytes) -> bytes:
+ """
+ buffer: remove common header
+ uncompress file
+ """
+ tool = self.command
+ if tool:
+ tmp = tempfile.mkdtemp(dir=os.environ.get('tmp'))
+ ToolInputFile = os.path.join(tmp, "unpack_sec_file")
+ ToolOuputFile = os.path.join(tmp, "unpack_uncompress_sec_file")
+ try:
+ file = open(ToolInputFile, "wb")
+ file.write(buffer)
+ file.close()
+ command = [tool, '-d', '-o', ToolOuputFile, ToolInputFile]
+ ExecuteCommand(command)
+ buf = open(ToolOuputFile, "rb")
+ res_buffer = buf.read()
+ except Exception as msg:
+ logger.error(msg)
+ return ""
+ else:
+ buf.close()
+ if os.path.exists(tmp):
+ shutil.rmtree(tmp)
+ return res_buffer
+ else:
+ logger.error("Error parsing section: EFI_SECTION_GUID_DEFINED cannot be parsed at this time.")
+ logger.info("Its GUID is: %s" % self.guid)
+ return ""
+
+class GUIDTools:
+ '''
+ GUIDTools is responsible for reading FMMTConfig.ini, verify the tools and provide interfaces to access those tools.
+ '''
+ default_tools = {
+ struct2stream(ModifyGuidFormat("a31280ad-481e-41b6-95e8-127f4c984779")): GUIDTool("a31280ad-481e-41b6-95e8-127f4c984779", "TIANO", "TianoCompress"),
+ struct2stream(ModifyGuidFormat("ee4e5898-3914-4259-9d6e-dc7bd79403cf")): GUIDTool("ee4e5898-3914-4259-9d6e-dc7bd79403cf", "LZMA", "LzmaCompress"),
+ struct2stream(ModifyGuidFormat("fc1bcdb0-7d31-49aa-936a-a4600d9dd083")): GUIDTool("fc1bcdb0-7d31-49aa-936a-a4600d9dd083", "CRC32", "GenCrc32"),
+ struct2stream(ModifyGuidFormat("d42ae6bd-1352-4bfb-909a-ca72a6eae889")): GUIDTool("d42ae6bd-1352-4bfb-909a-ca72a6eae889", "LZMAF86", "LzmaF86Compress"),
+ struct2stream(ModifyGuidFormat("3d532050-5cda-4fd0-879e-0f7f630d5afb")): GUIDTool("3d532050-5cda-4fd0-879e-0f7f630d5afb", "BROTLI", "BrotliCompress"),
+ }
+
+ def __init__(self, tooldef_file: str=None) -> None:
+ self.dir = os.path.dirname(__file__)
+ self.tooldef_file = tooldef_file if tooldef_file else os.path.join(
+ self.dir, "FMMTConfig.ini")
+ self.tooldef = dict()
+ self.load()
+
+ def VerifyTools(self) -> None:
+ """
+ Verify Tools and Update Tools path.
+ """
+ path_env = os.environ.get("PATH")
+ path_env_list = path_env.split(os.pathsep)
+ path_env_list.append(os.path.dirname(__file__))
+ path_env_list = list(set(path_env_list))
+ for tool in self.tooldef.values():
+ cmd = tool.command
+ if os.path.isabs(cmd):
+ if not os.path.exists(cmd):
+ print("Tool Not found %s" % cmd)
+ else:
+ for syspath in path_env_list:
+ if glob.glob(os.path.join(syspath, cmd+"*")):
+ break
+ else:
+ print("Tool Not found %s" % cmd)
+
+ def load(self) -> None:
+ if os.path.exists(self.tooldef_file):
+ with open(self.tooldef_file, "r") as fd:
+ config_data = fd.readlines()
+ for line in config_data:
+ try:
+ guid, short_name, command = line.split()
+ new_format_guid = struct2stream(ModifyGuidFormat(guid.strip()))
+ self.tooldef[new_format_guid] = GUIDTool(
+ guid.strip(), short_name.strip(), command.strip())
+ except:
+ print("GuidTool load error!")
+ continue
+ else:
+ self.tooldef.update(self.default_tools)
+
+ self.VerifyTools()
+
+ def __getitem__(self, guid) -> None:
+ return self.tooldef.get(guid)
+
+
+guidtools = GUIDTools()
diff --git a/BaseTools/Source/Python/FMMT/utils/FmmtLogger.py b/BaseTools/Source/Python/FMMT/utils/FmmtLogger.py
new file mode 100644
index 000000000000..5289fc27049a
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/utils/FmmtLogger.py
@@ -0,0 +1,18 @@
+## @file
+# This file is used to define the Fmmt Logger.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+
+##
+
+import logging
+import sys
+
+FmmtLogger = logging.getLogger('FMMT')
+FmmtLogger.setLevel(logging.INFO)
+
+lh=logging.StreamHandler(sys.stdout)
+lf=logging.Formatter("%(levelname)-8s: %(message)s")
+lh.setFormatter(lf)
+FmmtLogger.addHandler(lh)
\ No newline at end of file
diff --git a/BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py b/BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py
new file mode 100644
index 000000000000..83a31aeea3fe
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py
@@ -0,0 +1,54 @@
+## @file
+# This file is used to define the printer for Bios layout.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+def GetFormatter(layout_format: str):
+ if layout_format == 'json':
+ return JsonFormatter()
+ elif layout_format == 'yaml':
+ return YamlFormatter()
+ elif layout_format == 'html':
+ return HtmlFormatter()
+ else:
+ return TxtFormatter()
+
+class Formatter(object):
+ def dump(self, layoutdict, layoutlist, outputfile: str=None) -> None:
+ raise NotImplemented
+
+class JsonFormatter(Formatter):
+ def dump(self,layoutdict: dict, layoutlist: list, outputfile: str=None) -> None:
+ try:
+ import json
+ except:
+ TxtFormatter().dump(layoutdict, layoutlist, outputfile)
+ return
+ print(outputfile)
+ if outputfile:
+ with open(outputfile,"w") as fw:
+ json.dump(layoutdict, fw, indent=2)
+ else:
+ print(json.dumps(layoutdict,indent=2))
+
+class TxtFormatter(Formatter):
+ def LogPrint(self,layoutlist: list) -> None:
+ for item in layoutlist:
+ print(item)
+ print('\n')
+
+ def dump(self,layoutdict: dict, layoutlist: list, outputfile: str=None) -> None:
+ print(outputfile)
+ with open(outputfile, "w") as f:
+ for item in layoutlist:
+ f.writelines(item + '\n')
+
+class YamlFormatter(Formatter):
+ def dump(self,layoutdict, layoutlist, outputfile = None):
+ TxtFormatter().dump(layoutdict, layoutlist, outputfile)
+
+class HtmlFormatter(Formatter):
+ def dump(self,layoutdict, layoutlist, outputfile = None):
+ TxtFormatter().dump(layoutdict, layoutlist, outputfile)
\ No newline at end of file
--
2.27.0.windows.1


Re: [PATCH 1/2] Reconfigure OpensslLib to add elliptic curve chipher algorithms

Vineel Kovvuri
 

Hi Folks,

 

We are able to resolve the __ModuleEntryPoint error and was able to run below build configurations locally.

  • Windows_VS2019 - Passed
    • EmulatorPkg_Win_VS2019 - Passed
    • OvmfPkg_Win_VS2019 - Passed
  • Ubuntu_GCC5 - Passed
    • ArmVirtPkg_Ubuntu_GCC5 - Passed
    • EmulatorPkg_Ubuntu_GCC5 - Passed
    • OvmfPkg_Ubuntu_GCC5 – Failed
      • INFO - GenFv: ERROR 3000: Invalid
      • INFO -   the required fv image size 0xcb2ac0 exceeds the set fv image size 0xc00000

Is it okay to increase the fv image size if so need some guidance with respected to that as it may effect other projects like QEMU etc. Any inputs here are much appreciated

 

For Reference: https://github.com/vineelkovvuri/edk2/pull/2

 


Re: [PATCH v2 3/4] OvmfPkg: Enable physical presence interface for TPM 1.2

Stefan Berger
 

On 11/8/21 09:43, Stefan Berger wrote:
On 11/8/21 07:13, Yao, Jiewen wrote:

The PPFlag variable MUST to be locked to prevent malicious modification.
Otherwise, anyone can change the PP configuration without confirmation from end user.
That change by an attacker could presumably only  be done via UEFI shell/command line? How do I display the variables? I tried with 'dmpstore PhysicalPresenceFlags' (TPM 1.2) or 'dumpstore Tcg2PhysicalPresenceFlags' but I don't see them. I don't see them with 'dmpstore -b', either, but I see them both on Linux in /sys/firmware/efi/efivars.

Under Linux I can remove the (Tcg2)PhysicalPresenceFlags after 'chattr -i' on the file and then an 'rm'. Is it a concern for this particular variable if root can do this?

Since this is old/outdated, what is a new API for it?

   Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **)&VariableLockProtocol);
   if (!EFI_ERROR (Status)) {
     Status = VariableLockProtocol->RequestToLock (

Thanks.
It seems that the following makes this a read-only variable for root on Linux as well. It doesn't make it appear with 'dmpstrore -b'. Is this now the correct solution?


diff --git a/SecurityPkg/Library/AuthVariableLib/AuthServiceInternal.h b/SecurityPkg/Library/AuthVariableLib/AuthServiceInternal.h
index 2bec637f75..fc2abdb96c 100644
--- a/SecurityPkg/Library/AuthVariableLib/AuthServiceInternal.h
+++ b/SecurityPkg/Library/AuthVariableLib/AuthServiceInternal.h
@@ -30,6 +30,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent

 #include <Guid/AuthenticatedVariableFormat.h>
 #include <Guid/ImageAuthentication.h>
+#include <Guid/PhysicalPresenceData.h>

 #define TWO_BYTE_ENCODE       0x82

diff --git a/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.c b/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.c
index 122b3b0bf4..888977efa7 100644
--- a/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.c
+++ b/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.c
@@ -89,6 +89,17 @@ VARIABLE_ENTRY_PROPERTY mAuthVarEntry[] = {
       MAX_UINTN
     }
   },
+  {
+    &gEfiPhysicalPresenceGuid,
+    PHYSICAL_PRESENCE_FLAGS_VARIABLE,
+    {
+      VAR_CHECK_VARIABLE_PROPERTY_REVISION,
+      VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY,
+      VARIABLE_ATTRIBUTE_NV_BS,
+      sizeof (EFI_PHYSICAL_PRESENCE_FLAGS),
+      MAX_UINTN
+    }
+  }
 };

 VOID **mAuthVarAddressPointer[9];
diff --git a/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.inf b/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.inf
index 8eadeebceb..d0ced0792c 100644
--- a/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.inf
+++ b/SecurityPkg/Library/AuthVariableLib/AuthVariableLib.inf
@@ -75,6 +75,10 @@
   ## PRODUCES            ## Variable:L"certdbv"
   gEfiCertDbGuid

+  ## CONSUMES            ## Variable:L"PhysicalPresenceFlags"
+  ## PRODUCES            ## Variable:L"PhysicalPresenceFlags"
+  gEfiPhysicalPresenceGuid
+
   ## CONSUMES            ## Variable:L"VendorKeysNv"
   ## PRODUCES            ## Variable:L"VendorKeysNv"
   gEfiVendorKeysNvGuid


   Stefan


Re: [EXTERNAL] Re: [edk2-devel] [PATCH v2 16/16] ArmPlatformPkg: Resolve build errors resulting from package moves

Bret Barkelew <bret.barkelew@...>
 

Marvin,

 

Thanks for the question! You’re right, I need to squash this patch into the applicable patches in the series so that the build fixes are present when the initial changes are added (to maintain bisectability). Surprised no one had mentioned it yet. 😉

 

I would actually be happy to keep the Arm entry point in StandaloneMmPkg if someone else wanted to tackle the work needed to remove the ArmPkg interface dependencies from the implementation. The necessary abstractions are beyond my ability to bite off right now, so the most direct solution was to relocate the implementation (leveraging the abstraction at the EntryPoint level) to the ArmPkg so that the necessary platforms could consume it. I could also see an argument that it should be platform code, but I think it’s more common than that and would prefer to keep it in EDK2.

 

- Bret

 

From: Marvin Häuser via groups.io
Sent: Saturday, November 6, 2021 2:50 AM
To: devel@edk2.groups.io; bret@...
Cc: devel@edk2.groups.io; Lindholm, Leif; Ard Biesheuvel; Sean Brogan
Subject: [EXTERNAL] Re: [edk2-devel] [PATCH v2 16/16] ArmPlatformPkg: Resolve build errors resulting from package moves

 

Hey Bret,

If I understood this correctly, this fixes build issues introduced with
the move patch of the same series? In that case, is there no edk2 rule
that every commit must compile for the whole tree? We have such a rule
downstream that overrides any colliding rules (e.g. "mod only one
package at a time") to not break bisectioning. We actually have
per-commit builds readily available in a database for some projects to
ease it further. No big deal for us as we don't do that with edk2 (yet),
but maybe worth considering for the future? :)

I'll just ask about another patch here because it doesn't matter for
review, but why move the ARM entry point to ArmPkg? I guess because
abstracting the ARM-specific things would more or less just make the
StandaloneMm library a trivial wrapper? My "issue" with this is that ARM
kind of has its own ecosystem in edk2 and without keeping up, it's hard
to tell whether to look for ARM implementations of modules and libraries
in the "generic" or the ARM packages.

Thanks a lot for the series!

Best regards,
Marvin

02.11.2021 21:21:59 Bret Barkelew <bret@...>:

> From: Bret Barkelew <brbarkel@...>
>
> REF: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fbugzilla.tianocore.org%2Fshow_bug.cgi%3Fid%3D3652&amp;data=04%7C01%7CBret.Barkelew%40microsoft.com%7C2d061d1ff7a845914be808d9a10addee%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637717890363754993%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=r5yRvqRrCGPQLNCXfXv4KdmldhxFTpCwdsjMoPcvl4M%3D&amp;reserved=0
>
> Cc: Leif Lindholm <leif@...>
> Cc: Ard Biesheuvel <ardb+tianocore@...>
> Cc: Sean Brogan <sean.brogan@...>
> Signed-off-by: Bret Barkelew <bret.barkelew@...>
> ---
> ArmPlatformPkg/ArmPlatformPkg.dsc | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/ArmPlatformPkg/ArmPlatformPkg.dsc b/ArmPlatformPkg/ArmPlatformPkg.dsc
> index 661a4cea220d..3ed0bae87c41 100644
> --- a/ArmPlatformPkg/ArmPlatformPkg.dsc
> +++ b/ArmPlatformPkg/ArmPlatformPkg.dsc
> @@ -79,6 +79,8 @@ [LibraryClasses.common]
>    NULL|ArmPkg/Library/CompilerIntrinsicsLib/CompilerIntrinsicsLib.inf
>    NULL|MdePkg/Library/BaseStackCheckLib/BaseStackCheckLib.inf
>
> +  ArmSvcLib|ArmPkg/Library/ArmSvcLib/ArmSvcLib.inf
> +
> [LibraryClasses.common.PEIM]
>    HobLib|MdePkg/Library/PeiHobLib/PeiHobLib.inf
>    MemoryAllocationLib|MdePkg/Library/PeiMemoryAllocationLib/PeiMemoryAllocationLib.inf
> @@ -92,7 +94,7 @@ [LibraryClasses.common.SEC]
>    MemoryAllocationLib|EmbeddedPkg/Library/PrePiMemoryAllocationLib/PrePiMemoryAllocationLib.inf
>    PrePiHobListPointerLib|ArmPlatformPkg/Library/PrePiHobListPointerLib/PrePiHobListPointerLib.inf
>
> -[LibraryClasses.AARCH64.MM_STANDALONE]
> +[LibraryClasses.common.MM_STANDALONE]
>    HobLib|StandaloneMmPkg/Library/StandaloneMmHobLib/StandaloneMmHobLib.inf
>    MemoryAllocationLib|StandaloneMmPkg/Library/StandaloneMmMemoryAllocationLib/StandaloneMmMemoryAllocationLib.inf
>    MmServicesTableLib|MdePkg/Library/StandaloneMmServicesTableLib/StandaloneMmServicesTableLib.inf
> --
> 2.31.1.windows.1
>
>
>
>




 


Re: [Patch V2 5/7] NetworkPkg: Reproduce builds across source format changes

Maciej Rabeda
 

Hey Mike,

Reviewed-by: Maciej Rabeda <maciej.rabeda@...>

Thanks,
Maciej

On 01-Nov-21 22:37, Michael D Kinney wrote:
REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3688

Use DEBUG_LINE_NUMBER instead of __LINE__.

Cc: Maciej Rabeda <maciej.rabeda@...>
Cc: Jiaxin Wu <jiaxin.wu@...>
Cc: Siyuan Fu <siyuan.fu@...>
Cc: Michael Kubacki <michael.kubacki@...>
Signed-off-by: Michael D Kinney <michael.d.kinney@...>
---
NetworkPkg/Include/Library/NetLib.h | 8 ++++----
NetworkPkg/Library/DxeNetLib/DxeNetLib.c | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/NetworkPkg/Include/Library/NetLib.h b/NetworkPkg/Include/Library/NetLib.h
index 858d0b6ba07c..6c0924863147 100644
--- a/NetworkPkg/Include/Library/NetLib.h
+++ b/NetworkPkg/Include/Library/NetLib.h
@@ -277,7 +277,7 @@ typedef struct {
NETDEBUG_LEVEL_TRACE, \
Module, \
__FILE__, \
- __LINE__, \
+ DEBUG_LINE_NUMBER, \
NetDebugASPrint PrintArg \
)
@@ -286,7 +286,7 @@ typedef struct {
NETDEBUG_LEVEL_WARNING, \
Module, \
__FILE__, \
- __LINE__, \
+ DEBUG_LINE_NUMBER, \
NetDebugASPrint PrintArg \
)
@@ -295,7 +295,7 @@ typedef struct {
NETDEBUG_LEVEL_ERROR, \
Module, \
__FILE__, \
- __LINE__, \
+ DEBUG_LINE_NUMBER, \
NetDebugASPrint PrintArg \
)
@@ -311,7 +311,7 @@ typedef struct {
NETDEBUG_LEVEL_TRACE,
"Tcp",
__FILE__,
- __LINE__,
+ DEBUG_LINE_NUMBER,
NetDebugASPrint ("State transit to %a\n", Name)
)
diff --git a/NetworkPkg/Library/DxeNetLib/DxeNetLib.c b/NetworkPkg/Library/DxeNetLib/DxeNetLib.c
index 2a555a7b90fa..0f95ce4b710d 100644
--- a/NetworkPkg/Library/DxeNetLib/DxeNetLib.c
+++ b/NetworkPkg/Library/DxeNetLib/DxeNetLib.c
@@ -434,7 +434,7 @@ SyslogBuildPacket (
NETDEBUG_LEVEL_TRACE,
"Tcp",
__FILE__,
- __LINE__,
+ DEBUG_LINE_NUMBER,
NetDebugASPrint ("State transit to %a\n", Name)
)


Re: [PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

Jiang, Xiaolu <xiaolu.jiang@...>
 

Hi Liming,

It's a good suggestion, I will try it first, Thanks !

-----Original Message-----
From: gaoliming <gaoliming@...>
Sent: Monday, November 8, 2021 11:18 AM
To: Jiang, Xiaolu <xiaolu.jiang@...>; devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Chen, Christine <yuwei.chen@...>; Fu, Siyuan <siyuan.fu@...>
Subject: 回复: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

Xiaolu:
Can you calculate the required memory, then allocate it instead of use hard code memory length?

Thanks
Liming
-----邮件原件-----
发件人: Jiang, Xiaolu <xiaolu.jiang@...>
发送时间: 2021年11月8日 10:26
收件人: gaoliming <gaoliming@...>; devel@edk2.groups.io
抄送: Feng, Bob C <bob.c.feng@...>; Chen, Christine
<yuwei.chen@...>; Fu, Siyuan <siyuan.fu@...>
主题: RE: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length
for support more PCD value.

Hi Liming,

I have checked with Team member , Totally We need support 50+ Driver
GUID in DevicePath PCD value ,So the length need to increase again, I
will change the Patch and re-send again.

Thanks!

-----Original Message-----
From: Jiang, Xiaolu
Sent: Monday, November 8, 2021 9:24 AM
To: gaoliming <gaoliming@...>; devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Chen, Christine
<Yuwei.Chen@...>
Subject: RE: [edk2-devel][PATCH] BaseTools: Increase the DevicePath
length for support more PCD value.

1. Currently the PCD value length is More than 1024, less than 2048,
2. Now we have 14 members, Also need to add 4~6 part to the PCD.
So we Increase the length to 4096.

-----Original Message-----
From: gaoliming <gaoliming@...>
Sent: Monday, November 8, 2021 9:15 AM
To: Jiang, Xiaolu <xiaolu.jiang@...>; devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Chen, Christine
<yuwei.chen@...>
Subject: 回复: [edk2-devel][PATCH] BaseTools: Increase the DevicePath
length for support more PCD value.

Xiaolu:
Why choose 4096? Is it enough?

Thanks
Liming
-----邮件原件-----
发件人: Xiaolu.Jiang <xiaolu.jiang@...>
发送时间: 2021年11月6日 11:51
收件人: devel@edk2.groups.io
抄送: Xiaolu.Jiang <xiaolu.jiang@...>; Bob Feng
<bob.c.feng@...>; Liming Gao <gaoliming@...>; Yuwei
Chen <yuwei.chen@...>
主题: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length
for support more PCD value.

Currently the PCD Value only support 13 Guid,When use more 13 pcd
will cause the build tool fail, Need increase the DevicePath length
to support more value.

REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3718

Cc: Bob Feng <bob.c.feng@...>
Cc: Liming Gao <gaoliming@...>
Cc: Yuwei Chen <yuwei.chen@...>

Signed-off-by: Xiaolu Jiang <xiaolu.jiang@...>
---
BaseTools/Source/C/DevicePath/DevicePath.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/BaseTools/Source/C/DevicePath/DevicePath.c
b/BaseTools/Source/C/DevicePath/DevicePath.c
index c4d224ed61..ef493f5506 100644
--- a/BaseTools/Source/C/DevicePath/DevicePath.c
+++ b/BaseTools/Source/C/DevicePath/DevicePath.c
@@ -170,7 +170,7 @@ int main(int argc, CHAR8 *argv[])
fprintf(stderr, "Invalid option value, Device Path can't be
NULL");

return STATUS_ERROR;

}

- Str16 = (CHAR16 *)malloc(1024);

+ Str16 = (CHAR16 *)malloc(4096);

if (Str16 == NULL) {

fprintf(stderr, "Resource, memory cannot be allocated");

return STATUS_ERROR;

--
2.30.2.windows.1


Re: [PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

Jiang, Xiaolu <xiaolu.jiang@...>
 

Hi Liming,

I have checked with Team member , Totally We need support 50+ Driver GUID in DevicePath PCD value ,So the length need to increase again, I will change the Patch and re-send again.

Thanks!

-----Original Message-----
From: Jiang, Xiaolu
Sent: Monday, November 8, 2021 9:24 AM
To: gaoliming <gaoliming@...>; devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Chen, Christine <Yuwei.Chen@...>
Subject: RE: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

1. Currently the PCD value length is More than 1024, less than 2048, 2. Now we have 14 members, Also need to add 4~6 part to the PCD.
So we Increase the length to 4096.

-----Original Message-----
From: gaoliming <gaoliming@...>
Sent: Monday, November 8, 2021 9:15 AM
To: Jiang, Xiaolu <xiaolu.jiang@...>; devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Chen, Christine <yuwei.chen@...>
Subject: 回复: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

Xiaolu:
Why choose 4096? Is it enough?

Thanks
Liming
-----邮件原件-----
发件人: Xiaolu.Jiang <xiaolu.jiang@...>
发送时间: 2021年11月6日 11:51
收件人: devel@edk2.groups.io
抄送: Xiaolu.Jiang <xiaolu.jiang@...>; Bob Feng
<bob.c.feng@...>; Liming Gao <gaoliming@...>; Yuwei
Chen <yuwei.chen@...>
主题: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length for
support more PCD value.

Currently the PCD Value only support 13 Guid,When use more 13 pcd will
cause the build tool fail, Need increase the DevicePath length to
support more value.

REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3718

Cc: Bob Feng <bob.c.feng@...>
Cc: Liming Gao <gaoliming@...>
Cc: Yuwei Chen <yuwei.chen@...>

Signed-off-by: Xiaolu Jiang <xiaolu.jiang@...>
---
BaseTools/Source/C/DevicePath/DevicePath.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/BaseTools/Source/C/DevicePath/DevicePath.c
b/BaseTools/Source/C/DevicePath/DevicePath.c
index c4d224ed61..ef493f5506 100644
--- a/BaseTools/Source/C/DevicePath/DevicePath.c
+++ b/BaseTools/Source/C/DevicePath/DevicePath.c
@@ -170,7 +170,7 @@ int main(int argc, CHAR8 *argv[])
fprintf(stderr, "Invalid option value, Device Path can't be
NULL");

return STATUS_ERROR;

}

- Str16 = (CHAR16 *)malloc(1024);

+ Str16 = (CHAR16 *)malloc(4096);

if (Str16 == NULL) {

fprintf(stderr, "Resource, memory cannot be allocated");

return STATUS_ERROR;

--
2.30.2.windows.1


Re: [PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

Jiang, Xiaolu <xiaolu.jiang@...>
 

1. Currently the PCD value length is More than 1024, less than 2048,
2. Now we have 14 members, Also need to add 4~6 part to the PCD.
So we Increase the length to 4096.

-----Original Message-----
From: gaoliming <gaoliming@...>
Sent: Monday, November 8, 2021 9:15 AM
To: Jiang, Xiaolu <xiaolu.jiang@...>; devel@edk2.groups.io
Cc: Feng, Bob C <bob.c.feng@...>; Chen, Christine <yuwei.chen@...>
Subject: 回复: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length for support more PCD value.

Xiaolu:
Why choose 4096? Is it enough?

Thanks
Liming
-----邮件原件-----
发件人: Xiaolu.Jiang <xiaolu.jiang@...>
发送时间: 2021年11月6日 11:51
收件人: devel@edk2.groups.io
抄送: Xiaolu.Jiang <xiaolu.jiang@...>; Bob Feng
<bob.c.feng@...>; Liming Gao <gaoliming@...>; Yuwei
Chen <yuwei.chen@...>
主题: [edk2-devel][PATCH] BaseTools: Increase the DevicePath length for
support more PCD value.

Currently the PCD Value only support 13 Guid,When use more 13 pcd will
cause the build tool fail, Need increase the DevicePath length to
support more value.

REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3718

Cc: Bob Feng <bob.c.feng@...>
Cc: Liming Gao <gaoliming@...>
Cc: Yuwei Chen <yuwei.chen@...>

Signed-off-by: Xiaolu Jiang <xiaolu.jiang@...>
---
BaseTools/Source/C/DevicePath/DevicePath.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/BaseTools/Source/C/DevicePath/DevicePath.c
b/BaseTools/Source/C/DevicePath/DevicePath.c
index c4d224ed61..ef493f5506 100644
--- a/BaseTools/Source/C/DevicePath/DevicePath.c
+++ b/BaseTools/Source/C/DevicePath/DevicePath.c
@@ -170,7 +170,7 @@ int main(int argc, CHAR8 *argv[])
fprintf(stderr, "Invalid option value, Device Path can't be
NULL");

return STATUS_ERROR;

}

- Str16 = (CHAR16 *)malloc(1024);

+ Str16 = (CHAR16 *)malloc(4096);

if (Str16 == NULL) {

fprintf(stderr, "Resource, memory cannot be allocated");

return STATUS_ERROR;

--
2.30.2.windows.1


[Patch 1/1] OvmfPkg/Xen: Fix VS2019 build issues

Michael D Kinney
 

REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3722

Fix VS2019 NOOPT build issues related to converting
a larger integer value to a smaller integer value.

Cc: Anthony Perard <anthony.perard@...>
Cc: Julien Grall <julien@...>
Cc: Ard Biesheuvel <ardb+tianocore@...>
Cc: Jiewen Yao <jiewen.yao@...>
Cc: Jordan Justen <jordan.l.justen@...>
Cc: Gerd Hoffmann <kraxel@...>
Signed-off-by: Michael D Kinney <michael.d.kinney@...>
---
.../Library/XenRealTimeClockLib/XenRealTimeClockLib.c | 10 +++++-----
OvmfPkg/XenPlatformPei/MemDetect.c | 2 +-
OvmfPkg/XenTimerDxe/XenTimerDxe.c | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/OvmfPkg/Library/XenRealTimeClockLib/XenRealTimeClockLib.c b/OvmfPkg/Library/XenRealTimeClockLib/XenRealTimeClockLib.c
index e113bc89bd75..72e0aaa8798c 100644
--- a/OvmfPkg/Library/XenRealTimeClockLib/XenRealTimeClockLib.c
+++ b/OvmfPkg/Library/XenRealTimeClockLib/XenRealTimeClockLib.c
@@ -53,9 +53,9 @@ EpochToEfiTime (
m = (((da * 5) + 308) / 153) - 2;
d = da - (((m + 4) * 153) / 5) + 122;

- Time->Year = y - 4800 + ((m + 2) / 12);
+ Time->Year = (UINT16)(y - 4800 + ((m + 2) / 12));
Time->Month = ((m + 2) % 12) + 1;
- Time->Day = d + 1;
+ Time->Day = (UINT8)(d + 1);

ss = EpochSeconds % 60;
a = (EpochSeconds - ss) / 60;
@@ -63,9 +63,9 @@ EpochToEfiTime (
b = (a - mm) / 60;
hh = b % 24;

- Time->Hour = hh;
- Time->Minute = mm;
- Time->Second = ss;
+ Time->Hour = (UINT8)hh;
+ Time->Minute = (UINT8)mm;
+ Time->Second = (UINT8)ss;
Time->Nanosecond = 0;

}
diff --git a/OvmfPkg/XenPlatformPei/MemDetect.c b/OvmfPkg/XenPlatformPei/MemDetect.c
index 1970b631c94d..fa1be888d6ba 100644
--- a/OvmfPkg/XenPlatformPei/MemDetect.c
+++ b/OvmfPkg/XenPlatformPei/MemDetect.c
@@ -154,7 +154,7 @@ GetSystemMemorySizeBelow4gb (
HighestAddress = GetHighestSystemMemoryAddress (TRUE);
ASSERT (HighestAddress > 0 && HighestAddress <= BASE_4GB);

- return HighestAddress;
+ return (UINT32)HighestAddress;
}

//
diff --git a/OvmfPkg/XenTimerDxe/XenTimerDxe.c b/OvmfPkg/XenTimerDxe/XenTimerDxe.c
index 0bec59382b0a..19fa17a29fb4 100644
--- a/OvmfPkg/XenTimerDxe/XenTimerDxe.c
+++ b/OvmfPkg/XenTimerDxe/XenTimerDxe.c
@@ -165,7 +165,7 @@ TimerDriverSetTimerPeriod (
{
UINT64 TimerCount;
UINT32 TimerFrequency;
- UINTN DivideValue = 1;
+ UINT32 DivideValue = 1;

if (TimerPeriod == 0) {
//
@@ -193,7 +193,7 @@ TimerDriverSetTimerPeriod (
//
// Program the timer with the new count value
//
- InitializeApicTimer(DivideValue, TimerCount, TRUE, LOCAL_APIC_TIMER_VECTOR);
+ InitializeApicTimer(DivideValue, (UINT32)TimerCount, TRUE, LOCAL_APIC_TIMER_VECTOR);

//
// Enable timer interrupt
--
2.32.0.windows.1


Re: [PATCH v2 3/4] OvmfPkg: Enable physical presence interface for TPM 1.2

Stefan Berger
 

On 11/8/21 07:13, Yao, Jiewen wrote:

The PPFlag variable MUST to be locked to prevent malicious modification.
Otherwise, anyone can change the PP configuration without confirmation from end user.
That change by an attacker could presumably only  be done via UEFI shell/command line? How do I display the variables? I tried with 'dmpstore PhysicalPresenceFlags' (TPM 1.2) or 'dumpstore Tcg2PhysicalPresenceFlags' but I don't see them. I don't see them with 'dmpstore -b', either, but I see them both on Linux in /sys/firmware/efi/efivars.

Under Linux I can remove the (Tcg2)PhysicalPresenceFlags after 'chattr -i' on the file and then an 'rm'. Is it a concern for this particular variable if root can do this?

Since this is old/outdated, what is a new API for it?

   Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **)&VariableLockProtocol);
   if (!EFI_ERROR (Status)) {
     Status = VariableLockProtocol->RequestToLock (

Thanks.


   Stefan


Thank you
Yao Jiewen


-----Original Message-----
From: Gerd Hoffmann <kraxel@...>
Sent: Monday, November 8, 2021 7:58 PM
To: Stefan Berger <stefanb@...>
Cc: devel@edk2.groups.io; marcandre.lureau@...; Yao, Jiewen
<jiewen.yao@...>; Wang, Jian J <jian.j.wang@...>; Ard Biesheuvel
<ardb+tianocore@...>; Justen, Jordan L <jordan.l.justen@...>
Subject: Re: [edk2-devel] [PATCH v2 3/4] OvmfPkg: Enable physical presence
interface for TPM 1.2

On Sat, Nov 06, 2021 at 09:19:33PM -0400, Stefan Berger wrote:
On 11/5/21 08:17, Gerd Hoffmann wrote:
On Tue, Nov 02, 2021 at 11:49:09AM -0400, Stefan Berger wrote:
Enable the physical presence interface for TPM 1.2. It is required for the
TPM 1.2 menu to work.

The changes to DxeTcgPhysicalPresenceLib.c are due to the device we are
using
in QEMU for presenting the supported PPI commands and results to the OS
via
ACPI as well as to store the PPI opcode to execute.
Fails to build for microvm.

+
TcgPhysicalPresenceLib|OvmfPkg/Library/TcgPhysicalPresenceLibNull/DxeTcgPh
ysicalPresenceLib.inf
I guess this line is needed just next to Tcg2PhysicalPresenceLibNull
line?
(same problem on OvmfXen.dsc)
Fixed in v3 for microvm and Xen and Bhyve also.

You happen to know about the variable lock issue? Why does the variable need
to be locked?
No clue, sorry. That's a topic I have to learn about myself. Noticed
the variable locking deprecation warning in the ovmf boot log too, but
havn't found the time yet to look into that.

take care,
Gerd



Re: [PATCH V5 3/3] SecurityPkg: Support CcMeasurementProtocol in DxeTpmMeasurementLib

Sami Mujawar
 

Hi Min,

Thank you for the updated patch.

I have a minor suggestion marked inline as [SAMI]. Otherwise, these changes look good to me.

Reviewed-by: Sami Mujawar <sami.mujawar@...>

Regards,

Sami Mujawar


On 07/11/2021 12:35 PM, Min Xu wrote:
BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3625

DxeTpmMeasurementLib supports TPM based measurement in DXE phase.
After CcMeasurementProtocol is introduced, CC based measurement needs
to be supported in DxeTpmMeasurementLib as well.

A platform should have only one RTS/RTR. Only one of (virtual)TPM1.2,
(virtual)TPM2.0 and CC MR exists. Then only one TCG_SERVICE_PROTOCOL,
TCG2_PROTOCOL, CC_MEASUREMENT_PROTOCOL is exposed.

In this library when do measurement only one of above 3 protocols will
be called.

Cc: Michael D Kinney <michael.d.kinney@...>
Cc: Liming Gao <gaoliming@...>
Cc: Zhiguang Liu <zhiguang.liu@...>
Cc: Jiewen Yao <jiewen.yao@...>
Cc: Jian J Wang <jian.j.wang@...>
Cc: Sami Mujawar <sami.mujawar@...>
Cc: Gerd Hoffmann <kraxel@...>
Signed-off-by: Min Xu <min.m.xu@...>
---
.../DxeTpmMeasurementLib.c | 120 +++++++++++++++---
.../DxeTpmMeasurementLib.inf | 9 +-
2 files changed, 107 insertions(+), 22 deletions(-)

diff --git a/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.c b/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.c
index 061136ee7860..a626d0f6ec38 100644
--- a/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.c
+++ b/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.c
@@ -1,5 +1,6 @@
/** @file
- This library is used by other modules to measure data to TPM.
+ This library is used by other modules to measure data to TPM and Confidential
+ Computing (CC) measure registers.
Copyright (c) 2012 - 2018, Intel Corporation. All rights reserved. <BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
@@ -19,8 +20,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
#include <Guid/Acpi.h>
#include <IndustryStandard/Acpi.h>
-
-
+#include <Protocol/CcMeasurement.h>
/**
Tpm12 measure and log data, and extend the measurement result into a specific PCR.
@@ -149,6 +149,72 @@ Tpm20MeasureAndLogData (
return Status;
}
+/**
+ Cc measure and log data, and extend the measurement result into a
+ specific CC MR.
+
+ @param[in] PcrIndex PCR Index.
+ @param[in] EventType Event type.
+ @param[in] EventLog Measurement event log.
+ @param[in] LogLen Event log length in bytes.
+ @param[in] HashData The start of the data buffer to be hashed, extended.
+ @param[in] HashDataLen The length, in bytes, of the buffer referenced by HashData
+
+ @retval EFI_SUCCESS Operation completed successfully.
+ @retval EFI_UNSUPPORTED CC guest not available.
+ @retval EFI_OUT_OF_RESOURCES Out of memory.
+ @retval EFI_DEVICE_ERROR The operation was unsuccessful.
+**/
+EFI_STATUS
+CcMeasureAndLogData (
+ IN UINT32 PcrIndex,
+ IN UINT32 EventType,
+ IN VOID *EventLog,
+ IN UINT32 LogLen,
+ IN VOID *HashData,
+ IN UINT64 HashDataLen
+ )
[SAMI] I think this function can be made static and the CcProtocol pointer could be passed as the first argument.
Similarly, the other functions Tpm20MeasureAndLogData() and Tpm12MeasureAndLogDat() could also be made static.
[/SAMI]
+{
+ EFI_STATUS Status;
+ EFI_CC_MEASUREMENT_PROTOCOL *CcProtocol;
+ EFI_CC_EVENT *EfiCcEvent;
+ EFI_CC_MR_INDEX MrIndex;
+
+ Status = gBS->LocateProtocol (&gEfiCcMeasurementProtocolGuid, NULL, (VOID **) &CcProtocol);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = CcProtocol->MapPcrToMrIndex (CcProtocol, PcrIndex, &MrIndex);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ EfiCcEvent = (EFI_CC_EVENT *) AllocateZeroPool (LogLen + sizeof (EFI_CC_EVENT));
+ if(EfiCcEvent == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ EfiCcEvent->Size = (UINT32) LogLen + sizeof (EFI_CC_EVENT) - sizeof (EfiCcEvent->Event);
+ EfiCcEvent->Header.HeaderSize = sizeof (EFI_CC_EVENT_HEADER);
+ EfiCcEvent->Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION;
+ EfiCcEvent->Header.MrIndex = MrIndex;
+ EfiCcEvent->Header.EventType = EventType;
+ CopyMem (&EfiCcEvent->Event[0], EventLog, LogLen);
+
+ Status = CcProtocol->HashLogExtendEvent (
+ CcProtocol,
+ 0,
+ (EFI_PHYSICAL_ADDRESS) (UINTN) HashData,
+ HashDataLen,
+ EfiCcEvent
+ );
+ FreePool (EfiCcEvent);
+
+ return Status;
+}
+
+
/**
Tpm measure and log data, and extend the measurement result into a specific PCR.
@@ -175,25 +241,15 @@ TpmMeasureAndLogData (
IN UINT64 HashDataLen
)
{
- EFI_STATUS Status;
+ EFI_STATUS Status;
+ EFI_CC_MEASUREMENT_PROTOCOL *CcProtocol;
- //
- // Try to measure using Tpm20 protocol
- //
- Status = Tpm20MeasureAndLogData(
- PcrIndex,
- EventType,
- EventLog,
- LogLen,
- HashData,
- HashDataLen
- );
-
- if (EFI_ERROR (Status)) {
+ Status = gBS->LocateProtocol (&gEfiCcMeasurementProtocolGuid, NULL, (VOID **) &CcProtocol);
+ if (!EFI_ERROR (Status)) {
//
- // Try to measure using Tpm1.2 protocol
+ // Try to measure using Cc measurement protocol
//
- Status = Tpm12MeasureAndLogData(
+ Status = CcMeasureAndLogData (
PcrIndex,
EventType,
EventLog,
@@ -201,6 +257,32 @@ TpmMeasureAndLogData (
HashData,
HashDataLen
);
+ } else {
+ //
+ // Try to measure using Tpm20 protocol
+ //
+ Status = Tpm20MeasureAndLogData (
+ PcrIndex,
+ EventType,
+ EventLog,
+ LogLen,
+ HashData,
+ HashDataLen
+ );
+
+ if (EFI_ERROR (Status)) {
+ //
+ // Try to measure using Tpm1.2 protocol
+ //
+ Status = Tpm12MeasureAndLogData(
+ PcrIndex,
+ EventType,
+ EventLog,
+ LogLen,
+ HashData,
+ HashDataLen
+ );
+ }
}
return Status;
diff --git a/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.inf b/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.inf
index 7d41bc41f95d..3af3d4e33b25 100644
--- a/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.inf
+++ b/SecurityPkg/Library/DxeTpmMeasurementLib/DxeTpmMeasurementLib.inf
@@ -1,5 +1,7 @@
## @file
-# Provides TPM measurement functions for TPM1.2 and TPM 2.0
+# Provides below measurement functions:
+# 1. TPM measurement functions for TPM1.2 and TPM 2.0
+# 2. Confidential Computing (CC) measurement functions
#
# This library provides TpmMeasureAndLogData() to measure and log data, and
# extend the measurement result into a specific PCR.
@@ -40,5 +42,6 @@
UefiBootServicesTableLib
[Protocols]
- gEfiTcgProtocolGuid ## SOMETIMES_CONSUMES
- gEfiTcg2ProtocolGuid ## SOMETIMES_CONSUMES
+ gEfiTcgProtocolGuid ## SOMETIMES_CONSUMES
+ gEfiTcg2ProtocolGuid ## SOMETIMES_CONSUMES
+ gEfiCcMeasurementProtocolGuid ## SOMETIMES_CONSUMES


Re: [PATCH V5 2/3] SecurityPkg: Support CcMeasurementProtocol in DxeTpm2MeasureBootLib

Sami Mujawar
 

Hi Min,

Thank you for the updated patch.

I have a minor suggestion marked inline as [SAMI]. Otherwise, this patch looks good to me.

Reviewed-by: Sami Mujawar <sami.mujawar@...>

Regards,

Sami Mujawar


On 07/11/2021 12:35 PM, Min Xu wrote:
BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3625

DxeTpm2MeasureBootLib supports TPM2 based measure boot. After
CcMeasurementProtocol is introduced, CC based measure boot needs to
be supported in DxeTpm2MeasureBootLib as well.

There are 2 major changes in this commit.

1. A platform should have only one RTS/RTR. Only one of (virtual)TPM1.2,
(virtual)TPM2.0 and CC MR exists. Then only one TCG_SERVICE_PROTOCOL,
TCG2_PROTOCOL, CC_MEASUREMENT_PROTOCOL is exposed. In this library when
do measure boot only one of TCG2_PROTOCOL / CC_MEASUREMENT_PROTOCOL
will be called. MEASURE_BOOT_PROTOCOLS is defined to store the instances
of TCG2 protocol and CC Measurement protocol.

2. CcEvent is similar to Tcg2Event except the MrIndex and PcrIndex.
So in the code Tcg2Event will be first created and intialized. If
CcMeasurementProtocol is called to do the measure boot, then CcEvent
points to Tcg2Event and the MrIndex is adjusted.

Cc: Michael D Kinney <michael.d.kinney@...>
Cc: Liming Gao <gaoliming@...>
Cc: Zhiguang Liu <zhiguang.liu@...>
Cc: Jiewen Yao <jiewen.yao@...>
Cc: Jian J Wang <jian.j.wang@...>
Cc: Sami Mujawar <sami.mujawar@...>
Cc: Gerd Hoffmann <kraxel@...>
Signed-off-by: Min Xu <min.m.xu@...>
---
 .../DxeTpm2MeasureBootLib.c                   | 304 ++++++++++++++----
 .../DxeTpm2MeasureBootLib.inf                 |   3 +-
 2 files changed, 242 insertions(+), 65 deletions(-)

diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c
index 92eac715800f..609a1d80cac3 100644
--- a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c
+++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c
@@ -1,5 +1,6 @@
 /** @file
-  The library instance provides security service of TPM2 measure boot.
+  The library instance provides security service of TPM2 measure boot and
+  Confidential Computing (CC) measure boot.
 
   Caution: This file requires additional review when modified.
   This library will have external input - PE/COFF image and GPT partition.
@@ -41,6 +42,12 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
 #include <Library/PeCoffLib.h>
 #include <Library/SecurityManagementLib.h>
 #include <Library/HobLib.h>
+#include <Protocol/CcMeasurement.h>
+
+typedef struct {
+  EFI_TCG2_PROTOCOL             *Tcg2Protocol;
+  EFI_CC_MEASUREMENT_PROTOCOL   *CcProtocol;
+} MEASURE_BOOT_PROTOCOLS;
 
 //
 // Flag to check GPT partition. It only need be measured once.
@@ -109,7 +116,7 @@ DxeTpm2MeasureBootLibImageRead (
   Caution: This function may receive untrusted input.
   The GPT partition table is external input, so this function should parse partition data carefully.
 
-  @param Tcg2Protocol            Pointer to the located TCG2 protocol instance.
+  @param MeasureBootProtocols    Pointer to the located MeasureBoot protocol instances (i.e. TCG2/CC protocol).
   @param GptHandle               Handle that GPT partition was installed.
 
   @retval EFI_SUCCESS            Successfully measure GPT table.
@@ -121,8 +128,8 @@ DxeTpm2MeasureBootLibImageRead (
 EFI_STATUS
 EFIAPI
 Tcg2MeasureGptTable (
-  IN  EFI_TCG2_PROTOCOL  *Tcg2Protocol,
-  IN  EFI_HANDLE         GptHandle
+  IN  MEASURE_BOOT_PROTOCOLS  *MeasureBootProtocols,
+  IN  EFI_HANDLE              GptHandle
   )
 {
   EFI_STATUS                        Status;
@@ -133,14 +140,31 @@ Tcg2MeasureGptTable (
   UINT8                             *EntryPtr;
   UINTN                             NumberOfPartition;
   UINT32                            Index;
+  UINT8                             *EventPtr;
   EFI_TCG2_EVENT                    *Tcg2Event;
+  EFI_CC_EVENT                      *CcEvent;
   EFI_GPT_DATA                      *GptData;
   UINT32                            EventSize;
+  EFI_TCG2_PROTOCOL                 *Tcg2Protocol;
+  EFI_CC_MEASUREMENT_PROTOCOL       *CcProtocol;
+  EFI_CC_MR_INDEX                   MrIndex;
 
   if (mTcg2MeasureGptCount > 0) {
     return EFI_SUCCESS;
   }
 
+  PrimaryHeader = NULL;
+  EntryPtr      = NULL;
+  EventPtr      = NULL;
+
+  Tcg2Protocol  = MeasureBootProtocols->Tcg2Protocol;
+  CcProtocol    = MeasureBootProtocols->CcProtocol;
+
+  if (Tcg2Protocol == NULL && CcProtocol == NULL) {
+    ASSERT (FALSE);
+    return EFI_UNSUPPORTED;
+  }
+
   Status = gBS->HandleProtocol (GptHandle, &gEfiBlockIoProtocolGuid, (VOID**)&BlockIo);
   if (EFI_ERROR (Status)) {
     return EFI_UNSUPPORTED;
@@ -149,6 +173,7 @@ Tcg2MeasureGptTable (
   if (EFI_ERROR (Status)) {
     return EFI_UNSUPPORTED;
   }
+
   //
   // Read the EFI Partition Table Header
   //
@@ -156,6 +181,7 @@ Tcg2MeasureGptTable (
   if (PrimaryHeader == NULL) {
     return EFI_OUT_OF_RESOURCES;
   }
+
   Status = DiskIo->ReadDisk (
                      DiskIo,
                      BlockIo->Media->MediaId,
@@ -164,10 +190,20 @@ Tcg2MeasureGptTable (
                      (UINT8 *)PrimaryHeader
                      );
   if (EFI_ERROR (Status)) {
-    DEBUG ((EFI_D_ERROR, "Failed to Read Partition Table Header!\n"));
+    DEBUG ((DEBUG_ERROR, "Failed to Read Partition Table Header!\n"));
     FreePool (PrimaryHeader);
     return EFI_DEVICE_ERROR;
   }
+
+  //
+  // PrimaryHeader->SizeOfPartitionEntry should not be zero
+  //
+  if (PrimaryHeader->SizeOfPartitionEntry == 0) {
+    DEBUG ((DEBUG_ERROR, "SizeOfPartitionEntry should not be zero!\n"));
+    FreePool (PrimaryHeader);
+    return EFI_BAD_BUFFER_SIZE;
+  }
+
   //
   // Read the partition entry.
   //
@@ -202,17 +238,17 @@ Tcg2MeasureGptTable (
   }
 
   //
-  // Prepare Data for Measurement
+  // Prepare Data for Measurement (CcProtocol and Tcg2Protocol)
   //
   EventSize = (UINT32)(sizeof (EFI_GPT_DATA) - sizeof (GptData->Partitions)
                         + NumberOfPartition * PrimaryHeader->SizeOfPartitionEntry);
-  Tcg2Event = (EFI_TCG2_EVENT *) AllocateZeroPool (EventSize + sizeof (EFI_TCG2_EVENT) - sizeof(Tcg2Event->Event));
-  if (Tcg2Event == NULL) {
-    FreePool (PrimaryHeader);
-    FreePool (EntryPtr);
-    return EFI_OUT_OF_RESOURCES;
+  EventPtr = (UINT8 *)AllocateZeroPool (EventSize + sizeof (EFI_TCG2_EVENT) - sizeof(Tcg2Event->Event));
+  if (EventPtr == NULL) {
+    Status = EFI_OUT_OF_RESOURCES;
+    goto Exit;
   }
 
+  Tcg2Event       = (EFI_TCG2_EVENT *)EventPtr;
   Tcg2Event->Size = EventSize + sizeof (EFI_TCG2_EVENT) - sizeof(Tcg2Event->Event);
   Tcg2Event->Header.HeaderSize    = sizeof(EFI_TCG2_EVENT_HEADER);
   Tcg2Event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION;
@@ -243,22 +279,63 @@ Tcg2MeasureGptTable (
   }
 
   //
-  // Measure the GPT data
+  // Only one of TCG2_PROTOCOL or CC_MEASUREMENT_PROTOCOL is exposed.
+  // So Measure the GPT data with one of the protocol.
   //
-  Status = Tcg2Protocol->HashLogExtendEvent (
-             Tcg2Protocol,
-             0,
-             (EFI_PHYSICAL_ADDRESS) (UINTN) (VOID *) GptData,
-             (UINT64) EventSize,
-             Tcg2Event
-             );
-  if (!EFI_ERROR (Status)) {
-    mTcg2MeasureGptCount++;
+  if (CcProtocol != NULL) {
+    //
+    // EFI_CC_EVENT share the same data structure with EFI_TCG2_EVENT
+    // except the MrIndex and PCRIndex in Header.
[SAMI] Since we are now sharing the same data structures between TCG2 and CC, would it be better to typedef the CC data structures?

This would also avoid potential issues should any one of the data structures were to be changed in the future.
I think EFI_CC_EVENT, EFI_CC_EVENT_HEADER and EFI_CC_MR_INDEX could be type defined. Similarly, EFI_CC_EVENT_HEADER_VERSION could
also be defined to the TCG2 equivalent.

If not then we should at least add an ASSERT () to check if the size of EFI_CC_EVENT and EFI_TCG2_EVENT is not different.

[/SAMI]
+    // Tcg2Event has been created and initialized before. So only the MrIndex need
+    // be adjusted.
+    //
+    Status = CcProtocol->MapPcrToMrIndex (CcProtocol, Tcg2Event->Header.PCRIndex, &MrIndex);
+    if (EFI_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "Cannot map PcrIndex(%d) to MrIndex\n", Tcg2Event->Header.PCRIndex));
+      goto Exit;
+    }
+
+    CcEvent = (EFI_CC_EVENT *)EventPtr;
+    CcEvent->Header.MrIndex = MrIndex;
+    Status = CcProtocol->HashLogExtendEvent (
+               CcProtocol,
+               0,
+               (EFI_PHYSICAL_ADDRESS) (UINTN) (VOID *) GptData,
+               (UINT64) EventSize,
+               CcEvent
+               );
+    if (!EFI_ERROR (Status)) {
+      mTcg2MeasureGptCount++;
+    }
+    DEBUG ((DEBUG_INFO, "DxeTpm2MeasureBootHandler - Cc MeasureGptTable - %r\n", Status));
+
+  } else if (Tcg2Protocol != NULL) {
+    //
+    // If Tcg2Protocol is installed, then Measure GPT data with this protocol.
+    //
+    Status = Tcg2Protocol->HashLogExtendEvent (
+               Tcg2Protocol,
+               0,
+               (EFI_PHYSICAL_ADDRESS) (UINTN) (VOID *) GptData,
+               (UINT64) EventSize,
+               Tcg2Event
+               );
+    if (!EFI_ERROR (Status)) {
+      mTcg2MeasureGptCount++;
+    }
+    DEBUG ((DEBUG_INFO, "DxeTpm2MeasureBootHandler - Tcg2 MeasureGptTable - %r\n", Status));
   }
 
-  FreePool (PrimaryHeader);
-  FreePool (EntryPtr);
-  FreePool (Tcg2Event);
+Exit:
+  if (PrimaryHeader != NULL) {
+    FreePool (PrimaryHeader);
+  }
+  if (EntryPtr != NULL) {
+    FreePool (EntryPtr);
+  }
+  if (EventPtr != NULL) {
+    FreePool (EventPtr);
+  }
 
   return Status;
 }
@@ -271,12 +348,12 @@ Tcg2MeasureGptTable (
   PE/COFF image is external input, so this function will validate its data structure
   within this image buffer before use.
 
-  @param[in] Tcg2Protocol   Pointer to the located TCG2 protocol instance.
-  @param[in] ImageAddress   Start address of image buffer.
-  @param[in] ImageSize      Image size
-  @param[in] LinkTimeBase   Address that the image is loaded into memory.
-  @param[in] ImageType      Image subsystem type.
-  @param[in] FilePath       File path is corresponding to the input image.
+  @param[in] MeasureBootProtocols   Pointer to the located MeasureBoot protocol instances.
+  @param[in] ImageAddress           Start address of image buffer.
+  @param[in] ImageSize              Image size
+  @param[in] LinkTimeBase           Address that the image is loaded into memory.
+  @param[in] ImageType              Image subsystem type.
+  @param[in] FilePath               File path is corresponding to the input image.
 
   @retval EFI_SUCCESS            Successfully measure image.
   @retval EFI_OUT_OF_RESOURCES   No enough resource to measure image.
@@ -287,7 +364,7 @@ Tcg2MeasureGptTable (
 EFI_STATUS
 EFIAPI
 Tcg2MeasurePeImage (
-  IN  EFI_TCG2_PROTOCOL         *Tcg2Protocol,
+  IN  MEASURE_BOOT_PROTOCOLS    *MeasureBootProtocols,
   IN  EFI_PHYSICAL_ADDRESS      ImageAddress,
   IN  UINTN                     ImageSize,
   IN  UINTN                     LinkTimeBase,
@@ -300,20 +377,36 @@ Tcg2MeasurePeImage (
   EFI_IMAGE_LOAD_EVENT              *ImageLoad;
   UINT32                            FilePathSize;
   UINT32                            EventSize;
+  EFI_CC_EVENT                      *CcEvent;
+  EFI_CC_MEASUREMENT_PROTOCOL       *CcProtocol;
+  EFI_TCG2_PROTOCOL                 *Tcg2Protocol;
+  UINT8                             *EventPtr;
+  EFI_CC_MR_INDEX                   MrIndex;
 
   Status        = EFI_UNSUPPORTED;
   ImageLoad     = NULL;
+  EventPtr      = NULL;
+
+  Tcg2Protocol  = MeasureBootProtocols->Tcg2Protocol;
+  CcProtocol    = MeasureBootProtocols->CcProtocol;
+
+  if (Tcg2Protocol == NULL && CcProtocol == NULL) {
+    ASSERT (FALSE);
+    return EFI_UNSUPPORTED;
+  }
+
   FilePathSize  = (UINT32) GetDevicePathSize (FilePath);
 
   //
   // Determine destination PCR by BootPolicy
   //
   EventSize = sizeof (*ImageLoad) - sizeof (ImageLoad->DevicePath) + FilePathSize;
-  Tcg2Event = AllocateZeroPool (EventSize + sizeof (EFI_TCG2_EVENT) - sizeof(Tcg2Event->Event));
-  if (Tcg2Event == NULL) {
+  EventPtr = AllocateZeroPool (EventSize + sizeof (EFI_TCG2_EVENT) - sizeof(Tcg2Event->Event));
+  if (EventPtr == NULL) {
     return EFI_OUT_OF_RESOURCES;
   }
 
+  Tcg2Event = (EFI_TCG2_EVENT *)EventPtr;
   Tcg2Event->Size = EventSize + sizeof (EFI_TCG2_EVENT) - sizeof(Tcg2Event->Event);
   Tcg2Event->Header.HeaderSize    = sizeof(EFI_TCG2_EVENT_HEADER);
   Tcg2Event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION;
@@ -334,7 +427,7 @@ Tcg2MeasurePeImage (
       break;
     default:
       DEBUG ((
-        EFI_D_ERROR,
+        DEBUG_ERROR,
         "Tcg2MeasurePeImage: Unknown subsystem type %d",
         ImageType
         ));
@@ -352,13 +445,37 @@ Tcg2MeasurePeImage (
   //
   // Log the PE data
   //
-  Status = Tcg2Protocol->HashLogExtendEvent (
-             Tcg2Protocol,
-             PE_COFF_IMAGE,
-             ImageAddress,
-             ImageSize,
-             Tcg2Event
-             );
+  if (CcProtocol != NULL) {
+    Status = CcProtocol->MapPcrToMrIndex (CcProtocol, Tcg2Event->Header.PCRIndex, &MrIndex);
+    if (EFI_ERROR (Status)) {
+      DEBUG ((DEBUG_ERROR, "Cannot map PcrIndex(%d) to MrIndex\n", Tcg2Event->Header.PCRIndex));
+      goto Finish;
+    }
+
+    CcEvent = (EFI_CC_EVENT *)EventPtr;
+    CcEvent->Header.MrIndex = MrIndex;
+
+    Status = CcProtocol->HashLogExtendEvent (
+               CcProtocol,
+               PE_COFF_IMAGE,
+               ImageAddress,
+               ImageSize,
+               CcEvent
+               );
+    DEBUG ((DEBUG_INFO, "DxeTpm2MeasureBootHandler - Cc MeasurePeImage - %r\n", Status));
+
+  } else if (Tcg2Protocol != NULL) {
+
+    Status = Tcg2Protocol->HashLogExtendEvent (
+               Tcg2Protocol,
+               PE_COFF_IMAGE,
+               ImageAddress,
+               ImageSize,
+               Tcg2Event
+               );
+    DEBUG ((DEBUG_INFO, "DxeTpm2MeasureBootHandler - Tcg2 MeasurePeImage - %r\n", Status));
+  }
+
   if (Status == EFI_VOLUME_FULL) {
     //
     // Volume full here means the image is hashed and its result is extended to PCR.
@@ -369,11 +486,77 @@ Tcg2MeasurePeImage (
   }
 
 Finish:
-  FreePool (Tcg2Event);
+  if (EventPtr != NULL) {
+    FreePool (EventPtr);
+  }
 
   return Status;
 }
 
+/**
+  Get the measure boot protocols.
+
+  There are 2 measure boot, TCG2 protocol based and Cc measurement protocol based.
+
+  @param  MeasureBootProtocols  Pointer to the located measure boot protocol instances.
+
+  @retval EFI_SUCCESS           Sucessfully locate the measure boot protocol instances (at least one instance).
+  @retval EFI_UNSUPPORTED       Measure boot is not supported.
+**/
+EFI_STATUS
+EFIAPI
+GetMeasureBootProtocols (
+  MEASURE_BOOT_PROTOCOLS    *MeasureBootProtocols
+  )
+{
+  EFI_STATUS                          Status;
+  EFI_TCG2_PROTOCOL                   *Tcg2Protocol;
+  EFI_CC_MEASUREMENT_PROTOCOL         *CcProtocol;
+  EFI_TCG2_BOOT_SERVICE_CAPABILITY    Tcg2ProtocolCapability;
+  EFI_CC_BOOT_SERVICE_CAPABILITY      CcProtocolCapability;
+
+  CcProtocol = NULL;
+  Status = gBS->LocateProtocol (&gEfiCcMeasurementProtocolGuid, NULL, (VOID **) &CcProtocol);
+  if (EFI_ERROR (Status)) {
+    //
+    // Cc Measurement protocol is not installed.
+    //
+    DEBUG ((DEBUG_VERBOSE, "CcMeasurementProtocol is not installed. - %r\n", Status));
+  } else {
+    ZeroMem (&CcProtocolCapability, sizeof (CcProtocolCapability));
+    CcProtocolCapability.Size = sizeof (CcProtocolCapability);
+    Status = CcProtocol->GetCapability (CcProtocol, &CcProtocolCapability);
+    if (EFI_ERROR (Status) || CcProtocolCapability.CcType.Type == EFI_CC_TYPE_NONE) {
+      DEBUG ((DEBUG_ERROR, " CcProtocol->GetCapability returns : %x, %r\n", CcProtocolCapability.CcType.Type, Status));
+      CcProtocol = NULL;
+    }
+  }
+
+  Tcg2Protocol = NULL;
+  Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **) &Tcg2Protocol);
+  if (EFI_ERROR (Status)) {
+    //
+    // Tcg2 protocol is not installed. So, TPM2 is not present.
+    //
+    DEBUG ((DEBUG_VERBOSE, "Tcg2Protocol is not installed. - %r\n", Status));
+  } else {
+    Tcg2ProtocolCapability.Size = (UINT8) sizeof (Tcg2ProtocolCapability);
+    Status = Tcg2Protocol->GetCapability (Tcg2Protocol, &Tcg2ProtocolCapability);
+    if (EFI_ERROR (Status) || (!Tcg2ProtocolCapability.TPMPresentFlag)) {
+      //
+      // TPM device doesn't work or activate.
+      //
+      DEBUG ((DEBUG_ERROR, "TPMPresentFlag=FALSE %r\n", Status));
+      Tcg2Protocol = NULL;
+    }
+  }
+
+  MeasureBootProtocols->Tcg2Protocol = Tcg2Protocol;
+  MeasureBootProtocols->CcProtocol   = CcProtocol;
+
+  return (Tcg2Protocol == NULL && CcProtocol == NULL) ? EFI_UNSUPPORTED: EFI_SUCCESS;
+}
+
 /**
   The security handler is used to abstract platform-specific policy
   from the DXE core response to an attempt to use a file that returns a
@@ -422,9 +605,8 @@ DxeTpm2MeasureBootHandler (
   IN  BOOLEAN                          BootPolicy
   )
 {
-  EFI_TCG2_PROTOCOL                   *Tcg2Protocol;
+  MEASURE_BOOT_PROTOCOLS              MeasureBootProtocols;
   EFI_STATUS                          Status;
-  EFI_TCG2_BOOT_SERVICE_CAPABILITY    ProtocolCapability;
   EFI_DEVICE_PATH_PROTOCOL            *DevicePathNode;
   EFI_DEVICE_PATH_PROTOCOL            *OrigDevicePathNode;
   EFI_HANDLE                          Handle;
@@ -435,28 +617,23 @@ DxeTpm2MeasureBootHandler (
   EFI_PHYSICAL_ADDRESS                FvAddress;
   UINT32                              Index;
 
-  Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **) &Tcg2Protocol);
+  MeasureBootProtocols.Tcg2Protocol = NULL;
+  MeasureBootProtocols.CcProtocol   = NULL;
+
+  Status = GetMeasureBootProtocols(&MeasureBootProtocols);
+
   if (EFI_ERROR (Status)) {
     //
-    // Tcg2 protocol is not installed. So, TPM2 is not present.
+    // None of Measured boot protocols (Tcg2, Cc) is installed.
     // Don't do any measurement, and directly return EFI_SUCCESS.
     //
-    DEBUG ((EFI_D_VERBOSE, "DxeTpm2MeasureBootHandler - Tcg2 - %r\n", Status));
+    DEBUG ((DEBUG_INFO, "None of Tcg2Protocol/CcMeasurementProtocol is installed.\n"));
     return EFI_SUCCESS;
   }
 
-  ProtocolCapability.Size = (UINT8) sizeof (ProtocolCapability);
-  Status = Tcg2Protocol->GetCapability (
-                           Tcg2Protocol,
-                           &ProtocolCapability
-                           );
-  if (EFI_ERROR (Status) || (!ProtocolCapability.TPMPresentFlag)) {
-    //
-    // TPM device doesn't work or activate.
-    //
-    DEBUG ((EFI_D_ERROR, "DxeTpm2MeasureBootHandler (%r) - TPMPresentFlag - %x\n", Status, ProtocolCapability.TPMPresentFlag));
-    return EFI_SUCCESS;
-  }
+  DEBUG ((DEBUG_INFO, "Tcg2Protocol = %p, CcMeasurementProtocol = %p\n",
+                      MeasureBootProtocols.Tcg2Protocol,
+                      MeasureBootProtocols.CcProtocol));
 
   //
   // Copy File Device Path
@@ -502,8 +679,8 @@ DxeTpm2MeasureBootHandler (
             //
             // Measure GPT disk.
             //
-            Status = Tcg2MeasureGptTable (Tcg2Protocol, Handle);
-            DEBUG ((EFI_D_INFO, "DxeTpm2MeasureBootHandler - Tcg2MeasureGptTable - %r\n", Status));
+            Status = Tcg2MeasureGptTable (&MeasureBootProtocols, Handle);
+
             if (!EFI_ERROR (Status)) {
               //
               // GPT disk check done.
@@ -647,14 +824,13 @@ DxeTpm2MeasureBootHandler (
     // Measure PE image into TPM log.
     //
     Status = Tcg2MeasurePeImage (
-               Tcg2Protocol,
+               &MeasureBootProtocols,
                (EFI_PHYSICAL_ADDRESS) (UINTN) FileBuffer,
                FileSize,
                (UINTN) ImageContext.ImageAddress,
                ImageContext.ImageType,
                DevicePathNode
                );
-    DEBUG ((EFI_D_INFO, "DxeTpm2MeasureBootHandler - Tcg2MeasurePeImage - %r\n", Status));
   }
 
   //
@@ -665,7 +841,7 @@ Finish:
     FreePool (OrigDevicePathNode);
   }
 
-  DEBUG ((EFI_D_INFO, "DxeTpm2MeasureBootHandler - %r\n", Status));
+  DEBUG ((DEBUG_INFO, "DxeTpm2MeasureBootHandler - %r\n", Status));
 
   return Status;
 }
diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf
index 2506abbe7c8b..6dca79a20c93 100644
--- a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf
+++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf
@@ -1,5 +1,5 @@
 ## @file
-#  Provides security service for TPM 2.0 measured boot
+#  Provides security service for TPM 2.0 measured boot and Confidential Computing measure boot.
 #
 #  Spec Compliance Info:
 #    "TCG PC Client Platform Firmware Profile Specification for TPM Family 2.0 Level 00 Revision 1.03 v51"
@@ -61,6 +61,7 @@
 
 [Protocols]
   gEfiTcg2ProtocolGuid                  ## SOMETIMES_CONSUMES
+  gEfiCcMeasurementProtocolGuid         ## SOMETIMES_CONSUMES
   gEfiFirmwareVolumeBlockProtocolGuid   ## SOMETIMES_CONSUMES
   gEfiBlockIoProtocolGuid               ## SOMETIMES_CONSUMES
   gEfiDiskIoProtocolGuid                ## SOMETIMES_CONSUMES


Re: [PATCH] UefiCpuPkg: Fix CPU stack guard support by aligning GDT buffer

Leif Lindholm
 



On Fri, Nov 5, 2021 at 7:37 PM Vitaly Cheptsov <cheptsov@...> wrote:
>
> Hi Leif,
>
> I assume you mean the commit description, because the commit message is in the topic.

A topic is not a commit message. The commit message is what comes after the topic.

>  I believe something like that would do:
>
> CpuExceptionHandlerLib supplies misaligned GDT to the outer world
> (e.g. ArchSetupExceptionStack) when PcdCpuStackGuard is enabled.
> This happens because it uses an array of UINT8 for the mNewGdt
> variable, which alignment is 1 byte versus required 8 bytes. As a result
> ArchSetupExceptionStack always returns EFI_INVALID_PARAMETER in OVMF Ia32
> with XCODE5 and CLANGPDB at least.
>
> Fix this by allocating extra space in mNewGdt and then aligning the pointer
> upwards.

But I'm happy with this one.

Best Regards,

Leif

> Best wishes,
> Vitaly
>
> > On 5 Nov 2021, at 22:28, Leif Lindholm <leif@...> wrote:
> >
> > UefiCpuPkg maintainers - please respond.
> >
> > Meanwhile, Vitaly, could you please provide a commit message?
> > The BZ link is needed, but it's not a substitute.
> >
> > /
> >    Leif
> >
> > On Mon, Sep 20, 2021 at 17:13:47 +0300, Vitaly Cheptsov wrote:
> >> REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3639
> >>
> >>
> >>
> >> Cc: Jiewen Yao <jiewen.yao@...>
> >>
> >> Cc: Eric Dong <eric.dong@...>
> >>
> >> Cc: Michael Kinney <michael.d.kinney@...>
> >>
> >> Cc: Jian J Wang <jian.j.wang@...>
> >>
> >> Cc: Jeff Fan <vanjeff_919@...>
> >>
> >> Cc: Mikhail Krichanov <krichanov@...>
> >>
> >> Cc: Marvin Häuser <mhaeuser@...>
> >>
> >> Signed-off-by: Vitaly Cheptsov <cheptsov@...>
> >>
> >> ---
> >>
> >> .../Library/CpuExceptionHandlerLib/DxeException.c    | 12 +++++++-----
> >>
> >> 1 file changed, 7 insertions(+), 5 deletions(-)
> >>
> >>
> >>
> >> diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c
> >>
> >> index fd59f09ecd..12874811e1 100644
> >>
> >> --- a/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c
> >>
> >> +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/DxeException.c
> >>
> >> @@ -22,7 +22,7 @@ EXCEPTION_HANDLER_DATA      mExceptionHandlerData;
> >>
> >>
> >>
> >> UINT8                       mNewStack[CPU_STACK_SWITCH_EXCEPTION_NUMBER *
> >>
> >>                                       CPU_KNOWN_GOOD_STACK_SIZE];
> >>
> >> -UINT8                       mNewGdt[CPU_TSS_GDT_SIZE];
> >>
> >> +UINT8                       mNewGdt[CPU_TSS_GDT_SIZE + IA32_GDT_ALIGNMENT];
> >>
> >>
> >>
> >> /**
> >>
> >>   Common exception handler.
> >>
> >> @@ -238,6 +238,7 @@ InitializeCpuExceptionHandlersEx (
> >>
> >>   CPU_EXCEPTION_INIT_DATA           EssData;
> >>
> >>   IA32_DESCRIPTOR                   Idtr;
> >>
> >>   IA32_DESCRIPTOR                   Gdtr;
> >>
> >> +  UINT8                             *Gdt;
> >>
> >>
> >>
> >>   //
> >>
> >>   // To avoid repeat initialization of default handlers, the caller should pass
> >>
> >> @@ -259,6 +260,7 @@ InitializeCpuExceptionHandlersEx (
> >>
> >>     if (PcdGetBool (PcdCpuStackGuard)) {
> >>
> >>       if (InitData == NULL) {
> >>
> >>         SetMem (mNewGdt, sizeof (mNewGdt), 0);
> >>
> >> +        Gdt = ALIGN_POINTER (mNewGdt, IA32_GDT_ALIGNMENT);
> >>
> >>
> >>
> >>         AsmReadIdtr (&Idtr);
> >>
> >>         AsmReadGdtr (&Gdtr);
> >>
> >> @@ -270,11 +272,11 @@ InitializeCpuExceptionHandlersEx (
> >>
> >>         EssData.X64.StackSwitchExceptionNumber = CPU_STACK_SWITCH_EXCEPTION_NUMBER;
> >>
> >>         EssData.X64.IdtTable = (VOID *)Idtr.Base;
> >>
> >>         EssData.X64.IdtTableSize = Idtr.Limit + 1;
> >>
> >> -        EssData.X64.GdtTable = mNewGdt;
> >>
> >> -        EssData.X64.GdtTableSize = sizeof (mNewGdt);
> >>
> >> -        EssData.X64.ExceptionTssDesc = mNewGdt + Gdtr.Limit + 1;
> >>
> >> +        EssData.X64.GdtTable = Gdt;
> >>
> >> +        EssData.X64.GdtTableSize = CPU_TSS_GDT_SIZE;
> >>
> >> +        EssData.X64.ExceptionTssDesc = Gdt + Gdtr.Limit + 1;
> >>
> >>         EssData.X64.ExceptionTssDescSize = CPU_TSS_DESC_SIZE;
> >>
> >> -        EssData.X64.ExceptionTss = mNewGdt + Gdtr.Limit + 1 + CPU_TSS_DESC_SIZE;
> >>
> >> +        EssData.X64.ExceptionTss = Gdt + Gdtr.Limit + 1 + CPU_TSS_DESC_SIZE;
> >>
> >>         EssData.X64.ExceptionTssSize = CPU_TSS_SIZE;
> >>
> >>
> >>
> >>         InitData = &EssData;
> >>
> >> --
> >>
> >> 2.30.1 (Apple Git-130)
> >>
> >>
> >>
> >>
> >>
> >>
> >>
> >>


Re: Soft Feature Freeze will start on 2021-11-08 for edk2-stable202111

PierreGondois
 

Hi Liming,

The "Add SSDT PCI generator in DynamicTablesPkg" feature at
https://bugzilla.tianocore.org/show_bug.cgi?id=3682 won't be merged
before hard feature freeze, so I think it should be removed from the
list of proposed features,

Regards,

Pierre

On 11/5/21 05:53, gaoliming via groups.io wrote:

Hi, all

We will enter into Soft Feature Freeze phase on 2021-11-08. In this
phase, the feature under review will not be allowed to be pushed. The
feature passed review can still be merged.

 

The patch review can continue without break in edk2 community. If the
patch is sent before Soft Feature Freeze, and plans to catch this
stable tag, the patch contributor need reply to his patch and notify
edk2 community. If the patch is sent after Soft Feature Freeze, and
plans to catch this stable tag, please add edk2-stable202111 key words
in the patch title and BZ, so the community know this patch target and
give the feedback.

 

Below is edk2-stable202111 tag planning
https://github.com/tianocore/tianocore.github.io/wiki/EDK-II-Release-Planning
Proposed Schedule

Date (00:00:00 UTC-8) Description

2021-08-30  Beginning of development

2021-11-08  Soft Feature Freeze

2021-11-12  Hard Feature Freeze

2021-11-26  Release

 

Thanks

Liming


Re: [PATCH v2 3/4] OvmfPkg: Enable physical presence interface for TPM 1.2

Yao, Jiewen
 

The PPFlag variable MUST to be locked to prevent malicious modification.
Otherwise, anyone can change the PP configuration without confirmation from end user.

Thank you
Yao Jiewen

-----Original Message-----
From: Gerd Hoffmann <kraxel@...>
Sent: Monday, November 8, 2021 7:58 PM
To: Stefan Berger <stefanb@...>
Cc: devel@edk2.groups.io; marcandre.lureau@...; Yao, Jiewen
<jiewen.yao@...>; Wang, Jian J <jian.j.wang@...>; Ard Biesheuvel
<ardb+tianocore@...>; Justen, Jordan L <jordan.l.justen@...>
Subject: Re: [edk2-devel] [PATCH v2 3/4] OvmfPkg: Enable physical presence
interface for TPM 1.2

On Sat, Nov 06, 2021 at 09:19:33PM -0400, Stefan Berger wrote:

On 11/5/21 08:17, Gerd Hoffmann wrote:
On Tue, Nov 02, 2021 at 11:49:09AM -0400, Stefan Berger wrote:
Enable the physical presence interface for TPM 1.2. It is required for the
TPM 1.2 menu to work.

The changes to DxeTcgPhysicalPresenceLib.c are due to the device we are
using
in QEMU for presenting the supported PPI commands and results to the OS
via
ACPI as well as to store the PPI opcode to execute.
Fails to build for microvm.

+
TcgPhysicalPresenceLib|OvmfPkg/Library/TcgPhysicalPresenceLibNull/DxeTcgPh
ysicalPresenceLib.inf
I guess this line is needed just next to Tcg2PhysicalPresenceLibNull
line?
(same problem on OvmfXen.dsc)
Fixed in v3 for microvm and Xen and Bhyve also.

You happen to know about the variable lock issue? Why does the variable need
to be locked?
No clue, sorry. That's a topic I have to learn about myself. Noticed
the variable locking deprecation warning in the ovmf boot log too, but
havn't found the time yet to look into that.

take care,
Gerd

11221 - 11240 of 94602