Date   

[PATCH v2 4/6] ArmPkg: MmCommunicationDxe: Update MM communicate `CommBuffer**` checks

Kun Qin
 

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

Current MM communicate routine from ArmPkg would conduct few checks prior
to proceeding with SMC calls. However, the inspection step is different
from PI specification.

This patch updated MM communicate input argument inspection routine to
assure that return code `EFI_INVALID_PARAMETER` represents "the
`CommBuffer**` parameters do not refer to the same location in memory",
as described by `EFI_MM_COMMUNICATION2_PROTOCOL.Communicate()` section
in PI specification.

Cc: Leif Lindholm <leif@...>
Cc: Ard Biesheuvel <ardb+tianocore@...>
Cc: Bret Barkelew <Bret.Barkelew@...>
Cc: Michael Kubacki <michael.kubacki@...>

Signed-off-by: Kun Qin <kuqin12@...>
---

Notes:
v2:
- Splitting patch into 2 of 4 [Ard]
- Uncrustify style update

ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c b/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c
index 7f756a32d4e0..0283be430dff 100644
--- a/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c
+++ b/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c
@@ -83,7 +83,7 @@ MmCommunication2Communicate (
//
// Check parameters
//
- if (CommBufferVirtual == NULL) {
+ if ((CommBufferVirtual == NULL) || (CommBufferPhysical == NULL)) {
return EFI_INVALID_PARAMETER;
}

--
2.32.0.windows.1


[PATCH v2 3/6] ArmPkg: MmCommunicationDxe: MM communicate function argument attributes

Kun Qin
 

Current MM communicate2 function from ArmPkg described input arguments
`CommBufferPhysical`, `CommBufferVirtual` and `CommSize` as input only,
which mismatches with the "input and output type" as in PI specification.

This change updated function descriptions of MM communite2 to match input
argument types.

Cc: Leif Lindholm <leif@...>
Cc: Ard Biesheuvel <ardb+tianocore@...>
Cc: Bret Barkelew <Bret.Barkelew@...>
Cc: Michael Kubacki <michael.kubacki@...>

Signed-off-by: Kun Qin <kuqin12@...>
---

Notes:
v2:
- Splitting patch into 1 of 4 [Ard]

ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c b/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c
index 7c8284104d87..7f756a32d4e0 100644
--- a/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c
+++ b/ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c
@@ -41,12 +41,13 @@ STATIC EFI_HANDLE mMmCommunicateHandle;

This function provides a service to send and receive messages from a registered UEFI service.

- @param[in] This The EFI_MM_COMMUNICATION_PROTOCOL instance.
- @param[in] CommBufferPhysical Physical address of the MM communication buffer
- @param[in] CommBufferVirtual Virtual address of the MM communication buffer
- @param[in] CommSize The size of the data buffer being passed in. On exit, the size of data
- being returned. Zero if the handler does not wish to reply with any data.
- This parameter is optional and may be NULL.
+ @param[in] This The EFI_MM_COMMUNICATION_PROTOCOL instance.
+ @param[in, out] CommBufferPhysical Physical address of the MM communication buffer
+ @param[in, out] CommBufferVirtual Virtual address of the MM communication buffer
+ @param[in, out] CommSize The size of the data buffer being passed in. On exit, the
+ size of data being returned. Zero if the handler does not
+ wish to reply with any data. This parameter is optional
+ and may be NULL.

@retval EFI_SUCCESS The message was successfully posted.
@retval EFI_INVALID_PARAMETER CommBufferPhysical was NULL or CommBufferVirtual was NULL.
--
2.32.0.windows.1


[PATCH v2 2/6] MdePkg: MmCommunication2: Update MM communicate2 function description

Kun Qin
 

Current MM communicate2 function definition described input arguments
`CommBufferPhysical`, `CommBufferVirtual` and `CommSize` as input only,
which mismatches with the "input and output type" as in PI specification.

This change updated function descriptions of MM communite2 definition to
match input argument types.

Cc: Michael D Kinney <michael.d.kinney@...>
Cc: Liming Gao <gaoliming@...>
Cc: Zhiguang Liu <zhiguang.liu@...>

Signed-off-by: Kun Qin <kuqin12@...>
---

Notes:
v2:
- Newly added

MdePkg/Include/Protocol/MmCommunication2.h | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/MdePkg/Include/Protocol/MmCommunication2.h b/MdePkg/Include/Protocol/MmCommunication2.h
index 3495a7327f76..1b56320c7fff 100644
--- a/MdePkg/Include/Protocol/MmCommunication2.h
+++ b/MdePkg/Include/Protocol/MmCommunication2.h
@@ -27,12 +27,13 @@ typedef struct _EFI_MM_COMMUNICATION2_PROTOCOL EFI_MM_COMMUNICATION2_PROTOCOL;

This function provides a service to send and receive messages from a registered UEFI service.

- @param[in] This The EFI_MM_COMMUNICATION_PROTOCOL instance.
- @param[in] CommBufferPhysical Physical address of the MM communication buffer
- @param[in] CommBufferVirtual Virtual address of the MM communication buffer
- @param[in] CommSize The size of the data buffer being passed in. On exit, the size of data
- being returned. Zero if the handler does not wish to reply with any data.
- This parameter is optional and may be NULL.
+ @param[in] This The EFI_MM_COMMUNICATION_PROTOCOL instance.
+ @param[in, out] CommBufferPhysical Physical address of the MM communication buffer
+ @param[in, out] CommBufferVirtual Virtual address of the MM communication buffer
+ @param[in, out] CommSize The size of the data buffer being passed in. On exit, the
+ size of data being returned. Zero if the handler does not
+ wish to reply with any data. This parameter is optional
+ and may be NULL.

@retval EFI_SUCCESS The message was successfully posted.
@retval EFI_INVALID_PARAMETER CommBufferPhysical was NULL or CommBufferVirtual was NULL.
--
2.32.0.windows.1


[PATCH v2 1/6] MdeModulePkg: VariableSmmRuntimeDxe: Fix Variable Policy Message Length

Kun Qin
 

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

In EDKII implementation of variable policy, the DXE runtime agent would
communicate to MM to disable, register or query policies. However, these
operations populate the value of MessageLength that includes communicate
header to include MM communicate header, which mismatches with the
description of PI specification.

This fix will correct the MessageLength field calculation to exclude
the size of MM_COMMUNICATE_HEADER.

Cc: Jian J Wang <jian.j.wang@...>
Cc: Liming Gao <gaoliming@...>
Cc: Hao A Wu <hao.a.wu@...>
Cc: Bret Barkelew <Bret.Barkelew@...>
Cc: Michael Kubacki <michael.kubacki@...>

Signed-off-by: Kun Qin <kuqin12@...>
---

Notes:
v2:
- No review, no updates

MdeModulePkg/Universal/Variable/RuntimeDxe/VariablePolicySmmDxe.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariablePolicySmmDxe.c b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariablePolicySmmDxe.c
index 672a2293bcb1..b2094fbcd6ea 100644
--- a/MdeModulePkg/Universal/Variable/RuntimeDxe/VariablePolicySmmDxe.c
+++ b/MdeModulePkg/Universal/Variable/RuntimeDxe/VariablePolicySmmDxe.c
@@ -89,7 +89,7 @@ ProtocolDisableVariablePolicy (
CommHeader = mMmCommunicationBuffer;
PolicyHeader = (VAR_CHECK_POLICY_COMM_HEADER *)&CommHeader->Data;
CopyGuid (&CommHeader->HeaderGuid, &gVarCheckPolicyLibMmiHandlerGuid);
- CommHeader->MessageLength = BufferSize;
+ CommHeader->MessageLength = BufferSize - OFFSET_OF (EFI_MM_COMMUNICATE_HEADER, Data);
PolicyHeader->Signature = VAR_CHECK_POLICY_COMM_SIG;
PolicyHeader->Revision = VAR_CHECK_POLICY_COMM_REVISION;
PolicyHeader->Command = VAR_CHECK_POLICY_COMMAND_DISABLE;
@@ -138,7 +138,7 @@ ProtocolIsVariablePolicyEnabled (
PolicyHeader = (VAR_CHECK_POLICY_COMM_HEADER *)&CommHeader->Data;
CommandParams = (VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS *)(PolicyHeader + 1);
CopyGuid (&CommHeader->HeaderGuid, &gVarCheckPolicyLibMmiHandlerGuid);
- CommHeader->MessageLength = BufferSize;
+ CommHeader->MessageLength = BufferSize - OFFSET_OF (EFI_MM_COMMUNICATE_HEADER, Data);
PolicyHeader->Signature = VAR_CHECK_POLICY_COMM_SIG;
PolicyHeader->Revision = VAR_CHECK_POLICY_COMM_REVISION;
PolicyHeader->Command = VAR_CHECK_POLICY_COMMAND_IS_ENABLED;
@@ -213,7 +213,7 @@ ProtocolRegisterVariablePolicy (
PolicyHeader = (VAR_CHECK_POLICY_COMM_HEADER *)&CommHeader->Data;
PolicyBuffer = (VOID *)(PolicyHeader + 1);
CopyGuid (&CommHeader->HeaderGuid, &gVarCheckPolicyLibMmiHandlerGuid);
- CommHeader->MessageLength = BufferSize;
+ CommHeader->MessageLength = BufferSize - OFFSET_OF (EFI_MM_COMMUNICATE_HEADER, Data);
PolicyHeader->Signature = VAR_CHECK_POLICY_COMM_SIG;
PolicyHeader->Revision = VAR_CHECK_POLICY_COMM_REVISION;
PolicyHeader->Command = VAR_CHECK_POLICY_COMMAND_REGISTER;
@@ -270,7 +270,7 @@ DumpVariablePolicyHelper (
PolicyHeader = (VAR_CHECK_POLICY_COMM_HEADER *)&CommHeader->Data;
CommandParams = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS *)(PolicyHeader + 1);
CopyGuid (&CommHeader->HeaderGuid, &gVarCheckPolicyLibMmiHandlerGuid);
- CommHeader->MessageLength = BufferSize;
+ CommHeader->MessageLength = BufferSize - OFFSET_OF (EFI_MM_COMMUNICATE_HEADER, Data);
PolicyHeader->Signature = VAR_CHECK_POLICY_COMM_SIG;
PolicyHeader->Revision = VAR_CHECK_POLICY_COMM_REVISION;
PolicyHeader->Command = VAR_CHECK_POLICY_COMMAND_DUMP;
@@ -397,7 +397,7 @@ ProtocolLockVariablePolicy (
CommHeader = mMmCommunicationBuffer;
PolicyHeader = (VAR_CHECK_POLICY_COMM_HEADER *)&CommHeader->Data;
CopyGuid (&CommHeader->HeaderGuid, &gVarCheckPolicyLibMmiHandlerGuid);
- CommHeader->MessageLength = BufferSize;
+ CommHeader->MessageLength = BufferSize - OFFSET_OF (EFI_MM_COMMUNICATE_HEADER, Data);
PolicyHeader->Signature = VAR_CHECK_POLICY_COMM_SIG;
PolicyHeader->Revision = VAR_CHECK_POLICY_COMM_REVISION;
PolicyHeader->Command = VAR_CHECK_POLICY_COMMAND_LOCK;
--
2.32.0.windows.1


[PATCH v2 0/6] MM communicate functionality in variable policy

Kun Qin
 

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

This patch series is a follow up of previous submission:
https://edk2.groups.io/g/devel/message/84140

v2 patches mainly focus on feedback for commits submitted in v1 patches:
a. Splitted the original ArmPkg patch into 4 separate patches;
b. Updated patches according to Uncrustify scanning results;

Patch v2 branch: https://github.com/kuqin12/edk2/tree/mm_communicate_check_v2

Cc: Jian J Wang <jian.j.wang@...>
Cc: Liming Gao <gaoliming@...>
Cc: Hao A Wu <hao.a.wu@...>
Cc: Michael D Kinney <michael.d.kinney@...>
Cc: Zhiguang Liu <zhiguang.liu@...>
Cc: Leif Lindholm <leif@...>
Cc: Ard Biesheuvel <ardb+tianocore@...>
Cc: Bret Barkelew <Bret.Barkelew@...>
Cc: Michael Kubacki <michael.kubacki@...>

Kun Qin (6):
MdeModulePkg: VariableSmmRuntimeDxe: Fix Variable Policy Message
Length
MdePkg: MmCommunication2: Update MM communicate2 function description
ArmPkg: MmCommunicationDxe: MM communicate function argument
attributes
ArmPkg: MmCommunicationDxe: Update MM communicate `CommBuffer**`
checks
ArmPkg: MmCommunicationDxe: Update MM communicate `CommSize` check
ArmPkg: MmCommunicationDxe: Update MM communicate `MessageLength`
check

ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.c | 46 ++++++++++++--------
MdeModulePkg/Universal/Variable/RuntimeDxe/VariablePolicySmmDxe.c | 10 ++---
MdePkg/Include/Protocol/MmCommunication2.h | 13 +++---
3 files changed, 41 insertions(+), 28 deletions(-)

--
2.32.0.windows.1


Re: [PATCH] IntelFsp2WrapperPkg : Remove EFIAPI from local functions.

Zeng, Star
 

Reviewed-by: Star Zeng <star.zeng@...>

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Chiu, Chasel
Sent: Tuesday, December 21, 2021 8:40 AM
To: devel@edk2.groups.io
Cc: Chiu, Chasel <chasel.chiu@...>; Desimone, Nathaniel L <nathaniel.l.desimone@...>; Zeng, Star <star.zeng@...>; Ni, Ray <ray.ni@...>; S, Ashraf Ali <ashraf.ali.s@...>
Subject: [edk2-devel] [PATCH] IntelFsp2WrapperPkg : Remove EFIAPI from local functions.

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

Local functions do not need EFIAPI.

Cc: Nate DeSimone <nathaniel.l.desimone@...>
Cc: Star Zeng <star.zeng@...>
Cc: Ray Ni <ray.ni@...>
Cc: Ashraf Ali S <ashraf.ali.s@...>
Signed-off-by: Chasel Chiu <chasel.chiu@...>
---
IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c | 1 -
IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c | 1 -
2 files changed, 2 deletions(-)

diff --git a/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c b/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c
index 49fbb27eca..b0c6b2f8a6 100644
--- a/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c
+++ b/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c
@@ -45,7 +45,6 @@ extern EFI_GUID gFspHobGuid;
**/



UINTN

-EFIAPI

GetFspmUpdDataAddress (

VOID

)

diff --git a/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c b/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c
index ddee9cd029..fadadd40e6 100644
--- a/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c
+++ b/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c
@@ -188,7 +188,6 @@ FspSiliconInitDoneGetFspHobList (
**/



UINTN

-EFIAPI

GetFspsUpdDataAddress (

VOID

)

--
2.28.0.windows.1



-=-=-=-=-=-=
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#85113): https://edk2.groups.io/g/devel/message/85113
Mute This Topic: https://groups.io/mt/87869015/1779220
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [star.zeng@...]
-=-=-=-=-=-=


Re: [PATCH v8] IntelFsp2WrapperPkg : FSPM/S UPD data address based on Build Type

Chiu, Chasel
 

Thanks Ray! I have sent a code review for removing EFIAPI, please help to review.

Thanks,
Chasel

-----Original Message-----
From: Ni, Ray <ray.ni@...>
Sent: Monday, December 20, 2021 1:15 PM
To: S, Ashraf Ali <ashraf.ali.s@...>; devel@edk2.groups.io
Cc: Chiu, Chasel <chasel.chiu@...>; Desimone, Nathaniel L
<nathaniel.l.desimone@...>; Zeng, Star <star.zeng@...>; Kuo, Ted
<ted.kuo@...>; Duggapu, Chinni B <chinni.b.duggapu@...>;
Chaganty, Rangasai V <rangasai.v.chaganty@...>; Solanki, Digant H
<digant.h.solanki@...>; V, Sangeetha <sangeetha.v@...>
Subject: RE: [PATCH v8] IntelFsp2WrapperPkg : FSPM/S UPD data address based
on Build Type


+UINTN
+EFIAPI
+GetFspmUpdDataAddress (
+ VOID
+ )

This is internal function. Please remove "EFIAPI".


[PATCH] IntelFsp2WrapperPkg : Remove EFIAPI from local functions.

Chiu, Chasel
 

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

Local functions do not need EFIAPI.

Cc: Nate DeSimone <nathaniel.l.desimone@...>
Cc: Star Zeng <star.zeng@...>
Cc: Ray Ni <ray.ni@...>
Cc: Ashraf Ali S <ashraf.ali.s@...>
Signed-off-by: Chasel Chiu <chasel.chiu@...>
---
IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c | 1 -
IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c | 1 -
2 files changed, 2 deletions(-)

diff --git a/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c b/IntelF=
sp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c
index 49fbb27eca..b0c6b2f8a6 100644
--- a/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c
+++ b/IntelFsp2WrapperPkg/FspmWrapperPeim/FspmWrapperPeim.c
@@ -45,7 +45,6 @@ extern EFI_GUID gFspHobGuid;
**/=0D
=0D
UINTN=0D
-EFIAPI=0D
GetFspmUpdDataAddress (=0D
VOID=0D
)=0D
diff --git a/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c b/IntelF=
sp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c
index ddee9cd029..fadadd40e6 100644
--- a/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c
+++ b/IntelFsp2WrapperPkg/FspsWrapperPeim/FspsWrapperPeim.c
@@ -188,7 +188,6 @@ FspSiliconInitDoneGetFspHobList (
**/=0D
=0D
UINTN=0D
-EFIAPI=0D
GetFspsUpdDataAddress (=0D
VOID=0D
)=0D
--=20
2.28.0.windows.1


Re: [PATCH] BaseTools/Brotli: update to latest brotli submodule

Pedro Falcato
 

Ross,

This is a duplicate of https://edk2.groups.io/g/devel/message/84497, which has already been Rb'd and is just waiting for a maintainer to merge it.

Best regards,
Pedro

On Mon, Dec 20, 2021 at 4:55 PM Ross Burton <ross@...> wrote:
Update to the latest Brotli commit, and extend the makefiles as needed.

Specifically, this is needed for 0a3944 in brotli, to fix VLA parameter
warnings with new compilers.

Signed-off-by: Ross Burton <ross.burton@...>
Change-Id: I36d28cadb9bf2d9e01ac0341e69509fa1b8d6969
---
 BaseTools/Source/C/BrotliCompress/GNUmakefile |  7 +++++++
 BaseTools/Source/C/BrotliCompress/Makefile    | 11 ++++++++++-
 BaseTools/Source/C/BrotliCompress/brotli      |  2 +-
 3 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/BaseTools/Source/C/BrotliCompress/GNUmakefile b/BaseTools/Source/C/BrotliCompress/GNUmakefile
index b150e5dd2b..1a946140a3 100644
--- a/BaseTools/Source/C/BrotliCompress/GNUmakefile
+++ b/BaseTools/Source/C/BrotliCompress/GNUmakefile
@@ -10,8 +10,12 @@ APPNAME = BrotliCompress

 OBJECTS = \
   BrotliCompress.o \
+  brotli/c/common/constants.o \
+  brotli/c/common/context.o \
   brotli/c/common/dictionary.o \
   brotli/c/common/transform.o \
+  brotli/c/common/platform.o \
+  brotli/c/common/shared_dictionary.o \
   brotli/c/dec/bit_reader.o \
   brotli/c/dec/decode.o \
   brotli/c/dec/huffman.o \
@@ -22,12 +26,15 @@ OBJECTS = \
   brotli/c/enc/block_splitter.o \
   brotli/c/enc/brotli_bit_stream.o \
   brotli/c/enc/cluster.o \
+  brotli/c/enc/compound_dictionary.o \
   brotli/c/enc/compress_fragment.o \
   brotli/c/enc/compress_fragment_two_pass.o \
+  brotli/c/enc/command.o \
   brotli/c/enc/dictionary_hash.o \
   brotli/c/enc/encode.o \
   brotli/c/enc/encoder_dict.o \
   brotli/c/enc/entropy_encode.o \
+  brotli/c/enc/fast_log.o \
   brotli/c/enc/histogram.o \
   brotli/c/enc/literal_cost.o \
   brotli/c/enc/memory.o \
diff --git a/BaseTools/Source/C/BrotliCompress/Makefile b/BaseTools/Source/C/BrotliCompress/Makefile
index 038d1ec242..918497dc7b 100644
--- a/BaseTools/Source/C/BrotliCompress/Makefile
+++ b/BaseTools/Source/C/BrotliCompress/Makefile
@@ -13,7 +13,13 @@ APPNAME = BrotliCompress

 #LIBS = $(LIB_PATH)\Common.lib

-COMMON_OBJ = brotli\c\common\dictionary.obj brotli\c\common\transform.obj
+COMMON_OBJ = \
+  brotli\c\common\constants.obj \
+  brotli\c\common\context.obj \
+  brotli\c\common\dictionary.obj \
+  brotli\c\common\transform.obj \
+  brotli\c\common\platform.obj \
+  brotli\c\common\shared_dictionary.obj
 DEC_OBJ = \
   brotli\c\dec\bit_reader.obj \
   brotli\c\dec\decode.obj \
@@ -26,12 +32,15 @@ ENC_OBJ = \
   brotli\c\enc\block_splitter.obj \
   brotli\c\enc\brotli_bit_stream.obj \
   brotli\c\enc\cluster.obj \
+  brotli\c\enc\compound_dictionary.obj \
   brotli\c\enc\compress_fragment.obj \
   brotli\c\enc\compress_fragment_two_pass.obj \
+  brotli\c\enc\command.obj \
   brotli\c\enc\dictionary_hash.obj \
   brotli\c\enc\encode.obj \
   brotli\c\enc\encoder_dict.obj \
   brotli\c\enc\entropy_encode.obj \
+  brotli\c\enc\fast_log.obj \
   brotli\c\enc\histogram.obj \
   brotli\c\enc\literal_cost.obj \
   brotli\c\enc\memory.obj \
diff --git a/BaseTools/Source/C/BrotliCompress/brotli b/BaseTools/Source/C/BrotliCompress/brotli
index 666c3280cc..e83c7b8e8f 160000
--- a/BaseTools/Source/C/BrotliCompress/brotli
+++ b/BaseTools/Source/C/BrotliCompress/brotli
@@ -1 +1 @@
-Subproject commit 666c3280cc11dc433c303d79a83d4ffbdd12cc8d
+Subproject commit e83c7b8e8fb8b696a1df6866bc46cbb76d7e0348
--
2.25.1



------------
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#85111): https://edk2.groups.io/g/devel/message/85111
Mute This Topic: https://groups.io/mt/87860390/5946980
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [pedro.falcato@...]
------------




--
Pedro Falcato


[PATCH] BaseTools/Brotli: update to latest brotli submodule

Ross Burton <ross@...>
 

Update to the latest Brotli commit, and extend the makefiles as needed.

Specifically, this is needed for 0a3944 in brotli, to fix VLA parameter
warnings with new compilers.

Signed-off-by: Ross Burton <ross.burton@...>
Change-Id: I36d28cadb9bf2d9e01ac0341e69509fa1b8d6969
---
BaseTools/Source/C/BrotliCompress/GNUmakefile | 7 +++++++
BaseTools/Source/C/BrotliCompress/Makefile | 11 ++++++++++-
BaseTools/Source/C/BrotliCompress/brotli | 2 +-
3 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/BaseTools/Source/C/BrotliCompress/GNUmakefile b/BaseTools/Sour=
ce/C/BrotliCompress/GNUmakefile
index b150e5dd2b..1a946140a3 100644
--- a/BaseTools/Source/C/BrotliCompress/GNUmakefile
+++ b/BaseTools/Source/C/BrotliCompress/GNUmakefile
@@ -10,8 +10,12 @@ APPNAME =3D BrotliCompress
=0D
OBJECTS =3D \=0D
BrotliCompress.o \=0D
+ brotli/c/common/constants.o \=0D
+ brotli/c/common/context.o \=0D
brotli/c/common/dictionary.o \=0D
brotli/c/common/transform.o \=0D
+ brotli/c/common/platform.o \=0D
+ brotli/c/common/shared_dictionary.o \=0D
brotli/c/dec/bit_reader.o \=0D
brotli/c/dec/decode.o \=0D
brotli/c/dec/huffman.o \=0D
@@ -22,12 +26,15 @@ OBJECTS =3D \
brotli/c/enc/block_splitter.o \=0D
brotli/c/enc/brotli_bit_stream.o \=0D
brotli/c/enc/cluster.o \=0D
+ brotli/c/enc/compound_dictionary.o \=0D
brotli/c/enc/compress_fragment.o \=0D
brotli/c/enc/compress_fragment_two_pass.o \=0D
+ brotli/c/enc/command.o \=0D
brotli/c/enc/dictionary_hash.o \=0D
brotli/c/enc/encode.o \=0D
brotli/c/enc/encoder_dict.o \=0D
brotli/c/enc/entropy_encode.o \=0D
+ brotli/c/enc/fast_log.o \=0D
brotli/c/enc/histogram.o \=0D
brotli/c/enc/literal_cost.o \=0D
brotli/c/enc/memory.o \=0D
diff --git a/BaseTools/Source/C/BrotliCompress/Makefile b/BaseTools/Source/=
C/BrotliCompress/Makefile
index 038d1ec242..918497dc7b 100644
--- a/BaseTools/Source/C/BrotliCompress/Makefile
+++ b/BaseTools/Source/C/BrotliCompress/Makefile
@@ -13,7 +13,13 @@ APPNAME =3D BrotliCompress
=0D
#LIBS =3D $(LIB_PATH)\Common.lib=0D
=0D
-COMMON_OBJ =3D brotli\c\common\dictionary.obj brotli\c\common\transform.ob=
j=0D
+COMMON_OBJ =3D \=0D
+ brotli\c\common\constants.obj \=0D
+ brotli\c\common\context.obj \=0D
+ brotli\c\common\dictionary.obj \=0D
+ brotli\c\common\transform.obj \=0D
+ brotli\c\common\platform.obj \=0D
+ brotli\c\common\shared_dictionary.obj=0D
DEC_OBJ =3D \=0D
brotli\c\dec\bit_reader.obj \=0D
brotli\c\dec\decode.obj \=0D
@@ -26,12 +32,15 @@ ENC_OBJ =3D \
brotli\c\enc\block_splitter.obj \=0D
brotli\c\enc\brotli_bit_stream.obj \=0D
brotli\c\enc\cluster.obj \=0D
+ brotli\c\enc\compound_dictionary.obj \=0D
brotli\c\enc\compress_fragment.obj \=0D
brotli\c\enc\compress_fragment_two_pass.obj \=0D
+ brotli\c\enc\command.obj \=0D
brotli\c\enc\dictionary_hash.obj \=0D
brotli\c\enc\encode.obj \=0D
brotli\c\enc\encoder_dict.obj \=0D
brotli\c\enc\entropy_encode.obj \=0D
+ brotli\c\enc\fast_log.obj \=0D
brotli\c\enc\histogram.obj \=0D
brotli\c\enc\literal_cost.obj \=0D
brotli\c\enc\memory.obj \=0D
diff --git a/BaseTools/Source/C/BrotliCompress/brotli b/BaseTools/Source/C/=
BrotliCompress/brotli
index 666c3280cc..e83c7b8e8f 160000
--- a/BaseTools/Source/C/BrotliCompress/brotli
+++ b/BaseTools/Source/C/BrotliCompress/brotli
@@ -1 +1 @@
-Subproject commit 666c3280cc11dc433c303d79a83d4ffbdd12cc8d
+Subproject commit e83c7b8e8fb8b696a1df6866bc46cbb76d7e0348
--=20
2.25.1


Re: EDK build error

Andrew Fish
 

Jacek,

Since you are not subscribed to the mailing list this mail got sent to moderation….

Sometimes syntax errors in your build config files can crash the tools. This looks like a PCD value in a DSC might not be formatted correctly? If you have modified something in that area you can look into that. 

Thanks,

Andrew Fish

On Dec 18, 2021, at 12:57 PM, Kolakowski, Jacek <Jacek.Kolakowski@...> wrote:

Hi
I got the below build error. Can anybody help resolve it?
 
Thanks,
Jacek 
 
(Please send email to devel@edk2.groups.io for help, attaching following call stack trace!)
 
  (Python 3.8.6 on win32) Traceback (most recent call last):
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2695, in Main
  MyBuild.Launch()
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2490, in Launch
  self._MultiThreadBuildPlatform()
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2282, in _MultiThreadBuildPlatform
  Wa, self.BuildModules = self.PerformAutoGen(BuildTarget,ToolChain)
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2133, in PerformAutoGen
  Wa = WorkspaceAutoGen(
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\AutoGen\WorkspaceAutoGen.py", line 43, in __init__
  self._InitWorker(Workspace, MetaFile, Target, Toolchain, Arch, *args, **kwargs)
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\AutoGen\WorkspaceAutoGen.py", line 117, in _InitWorker
  self.ProcessPcdType()
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\AutoGen\WorkspaceAutoGen.py", line 264, in ProcessPcdType
  Platform.Pcds
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\Workspace\DscBuildData.py", line 1218, in Pcds
  self._Pcds = self.UpdateStructuredPcds(MODEL_PCD_TYPE_LIST, self._Pcds)
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\Workspace\DscBuildData.py", line 1600, in UpdateStructuredPcds
  Str_Pcd_Values = self.GenerateByteArrayValue(S_pcd_set)
  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\Workspace\DscBuildData.py", line 3036, in GenerateByteArrayValue
  EdkLogger.warn('Build', COMMAND_FAILURE, 'Can not collect output from command: %s\n%s\n' % (Command, StdOut, StdErr))
  TypeError: not all arguments converted during string formatting
 
 
  - Failed -
  Build end time: 18:29:42, Dec.18 2021
  Build total time: 00:00:13


Intel Technology Poland sp. z o.o.
ul. Słowackiego 173 | 80-298 Gdańsk | Sąd Rejonowy Gdańsk Północ | VII Wydział Gospodarczy Krajowego Rejestru Sądowego - KRS 101882 | NIP 957-07-52-316 | Kapitał zakładowy 200.000 PLN.

Ta wiadomość wraz z załącznikami jest przeznaczona dla określonego adresata i może zawierać informacje poufne. W razie przypadkowego otrzymania tej wiadomości, prosimy o powiadomienie nadawcy oraz trwałe jej usunięcie; jakiekolwiek przeglądanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.



EDK build error

Kolakowski, Jacek <Jacek.Kolakowski@...>
 

Hi

I got the below build error. Can anybody help resolve it?

 

Thanks,

Jacek

 

(Please send email to devel@edk2.groups.io for help, attaching following call stack trace!)

 

  (Python 3.8.6 on win32) Traceback (most recent call last):

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2695, in Main

  MyBuild.Launch()

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2490, in Launch

  self._MultiThreadBuildPlatform()

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2282, in _MultiThreadBuildPlatform

  Wa, self.BuildModules = self.PerformAutoGen(BuildTarget,ToolChain)

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\build\build.py", line 2133, in PerformAutoGen

  Wa = WorkspaceAutoGen(

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\AutoGen\WorkspaceAutoGen.py", line 43, in __init__

  self._InitWorker(Workspace, MetaFile, Target, Toolchain, Arch, *args, **kwargs)

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\AutoGen\WorkspaceAutoGen.py", line 117, in _InitWorker

  self.ProcessPcdType()

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\AutoGen\WorkspaceAutoGen.py", line 264, in ProcessPcdType

  Platform.Pcds

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\Workspace\DscBuildData.py", line 1218, in Pcds

  self._Pcds = self.UpdateStructuredPcds(MODEL_PCD_TYPE_LIST, self._Pcds)

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\Workspace\DscBuildData.py", line 1600, in UpdateStructuredPcds

  Str_Pcd_Values = self.GenerateByteArrayValue(S_pcd_set)

  File "C:\source\7nmGnr2\edk2\BaseTools\Source\Python\Workspace\DscBuildData.py", line 3036, in GenerateByteArrayValue

  EdkLogger.warn('Build', COMMAND_FAILURE, 'Can not collect output from command: %s\n%s\n' % (Command, StdOut, StdErr))

  TypeError: not all arguments converted during string formatting

 

 

  - Failed -

  Build end time: 18:29:42, Dec.18 2021

  Build total time: 00:00:13


Intel Technology Poland sp. z o.o.
ul. Słowackiego 173 | 80-298 Gdańsk | Sąd Rejonowy Gdańsk Północ | VII Wydział Gospodarczy Krajowego Rejestru Sądowego - KRS 101882 | NIP 957-07-52-316 | Kapitał zakładowy 200.000 PLN.

Ta wiadomość wraz z załącznikami jest przeznaczona dla określonego adresata i może zawierać informacje poufne. W razie przypadkowego otrzymania tej wiadomości, prosimy o powiadomienie nadawcy oraz trwałe jej usunięcie; jakiekolwiek przeglądanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.


[PATCH 1/1] UsbBusDxe: fix NOOPT build error

Gerd Hoffmann
 

gcc-11 (fedora 35):

/home/kraxel/projects/edk2/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbBus.c: In function ‘UsbIoBulkTransfer’:
/home/kraxel/projects/edk2/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbBus.c:277:12: error: ‘UsbHcBulkTransfer’ accessing 80 bytes in a region of size 8 [-Werror=stringop-overflow=]

Signed-off-by: Gerd Hoffmann <kraxel@...>
---
MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.h | 2 +-
MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.h b/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.h
index 04cf36d3c860..d93370a6c21e 100644
--- a/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.h
+++ b/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.h
@@ -148,7 +148,7 @@ UsbHcBulkTransfer (
IN UINT8 DevSpeed,
IN UINTN MaxPacket,
IN UINT8 BufferNum,
- IN OUT VOID *Data[EFI_USB_MAX_BULK_BUFFER_NUM],
+ IN OUT VOID *Data[],
IN OUT UINTN *DataLength,
IN OUT UINT8 *DataToggle,
IN UINTN TimeOut,
diff --git a/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.c b/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.c
index 12d08c0b740f..740e7babb0ca 100644
--- a/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.c
+++ b/MdeModulePkg/Bus/Usb/UsbBusDxe/UsbUtility.c
@@ -267,7 +267,7 @@ UsbHcBulkTransfer (
IN UINT8 DevSpeed,
IN UINTN MaxPacket,
IN UINT8 BufferNum,
- IN OUT VOID *Data[EFI_USB_MAX_BULK_BUFFER_NUM],
+ IN OUT VOID *Data[],
IN OUT UINTN *DataLength,
IN OUT UINT8 *DataToggle,
IN UINTN TimeOut,
--
2.33.1


Re: [PATCH] Maintainers.txt: Replace Pete with Jeremy as RPi reviewer

Leif Lindholm
 

On Fri, Dec 17, 2021 at 17:54:09 +0100, Ard Biesheuvel wrote:
On Fri, 17 Dec 2021 at 17:52, Jeremy Linton <jeremy.linton@...> wrote:

First a huge thank you to Pete Batard for all the hard work
landing the RPi code here, and keeping everyone in line.

But, he has lots of commitments, and its time to give him
a breather. As such, I will take over as a platform reviewer.

Cc: Pete Batard <pete@...>
Cc: Ard Biesheuvel <ardb+tianocore@...>
Cc: Leif Lindholm <leif@...>
Cc: Andrei Warkentin <awarkentin@...>
Cc: Samer El-Haj-Mahmoud <Samer.El-Haj-Mahmoud@...>
Signed-off-by: Jeremy Linton <jeremy.linton@...>
Many thanks to you both for the excellent work on the RPi platforms.

Acked-by: Ard Biesheuvel <ardb@...>
Seconded! Many thanks Pete, and don't be a stranger.

Acked-by: Leif Lindholm <leif@...>

Pushed as eb2d49b5a7d1.

---
Maintainers.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Maintainers.txt b/Maintainers.txt
index 2cad0a597d..a6ce4eee0f 100644
--- a/Maintainers.txt
+++ b/Maintainers.txt
@@ -346,7 +346,7 @@ F: Platform/RaspberryPi/
F: Silicon/Broadcom/
M: Ard Biesheuvel <ardb+tianocore@...>
M: Leif Lindholm <leif@...>
-R: Pete Batard <pete@...>
+R: Jeremy Linton <jeremy.linton@...>

RPMB driver for OP-TEE
F: Drivers/OpTee/OpteeRpmbPkg/
--
2.13.7



------------
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#85079): https://edk2.groups.io/g/devel/message/85079
Mute This Topic: https://groups.io/mt/87792984/1131722
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [ardb@...]
------------


Re: [PATCH 08/10] OvmfPkg: Update Sec to support Tdvf Config-B

Gerd Hoffmann
 

Hi,

Why? Booting non-tdx guests without PEI shouldn't be fundamentally
different from a TDX guest. Memory detection needs fw_cfg instead of the
td_hob, and you have to skip some tdx setup steps, but that should be it.
Code for all that exists in PlatformPei, it only needs to be moved to a place
where SEC can use it too.
We would like to split TDVF Config-B into below stages.
1. Basic Config-B (wave-3)
1.1 A standalone IntelTdxX64.dsc/.fdf. Un-used drivers/libs are removed from the fdf, such as network components, SMM drivers, TPM drivers, etc.
1.2 PEI FV is excluded from the build. Only DxeFV is included.
1.3 Since PEI FV is excluded from the build, so Basic Config-B can only bring up Tdx guest. It *CAN NOT* bring up legacy guest.
What blocks legacy guest bringup?

See above, I think it should not be hard to do, and given that
TDX-capable hardware is not yet production ready I find it rather
important that testing the PEI-less boot workflow does not require
TDX.

It'll also make it much easier to add CI coverage.

3.1 Add *basic* Ovmf feature without PEI, to achieve *ONE Binary* goal. (here basic means S3 is not supported without PEI)
Sure, pei-less ovmf has to drop some features, that is perfectly fine.

take care,
Gerd


Re: [PATCH edk2-platforms v1 03/10] Platform/ARM: Modify duplicated GUID in ArmVExpressLibSec

wenyi,xie
 

On 2021/12/18 0:39, Sami Mujawar wrote:
Hi Ard,


On 17/12/2021, 16:34, "Ard Biesheuvel" <ardb@...> wrote:

On Fri, 17 Dec 2021 at 17:30, Sami Mujawar <sami.mujawar@...> wrote:
>
> Hi Leif, Ard, Wenyi,
>
> Is it possible to review and provde feedback for this change, please?
>
Sorry to reply late, it's OK to update the GUID in ArmPlatformLibSec.inf.

Regards
Wenyi

I agree that this should be resolved, and I don't think it matters
which one we change. The INF GUIDs of libraries are never used anyway,
as far as I know.
In that case I will update this patch to modify the ArmVExpressLibRTSM version.

Regards,

Sami Mujawar

>
>
> On 17/12/2021 02:15 PM, Pierre Gondois wrote:
> > Hi Sami,
> >
> > You are correct, as stated in Silicon/Hisilicon/Library/ArmPlatformLibHisilicon/ArmPlatformLibSec.inf:
> >
> > "Based on the files under ArmPlatformPkg/ArmVExpressPkg/Library/ArmVExpressLibRTSM/"
> >
> > Thanks for updating the commit,
> >
> > Regards,
> >
> > Pierre
> >
> >
> >
> > On 12/17/21 2:59 PM, Sami Mujawar wrote:
> >> Hi Pierre,
> >>
> >> Please find my response inline marked [SAMI].
> >>
> >> Regards,
> >>
> >> Sami Mujawar
> >>
> >>
> >> On 24/11/2021 04:23 PM, Pierre.Gondois@... wrote:
> >>> From: Pierre Gondois <Pierre.Gondois@...>
> >>>
> >>> The two following modules have the same GUID:
> >>> - Platform/ARM/VExpressPkg/Library/
> >>> ArmVExpressLibRTSM/ArmVExpressLibSec.inf
> >>> - Silicon/Hisilicon/Library/
> >>> ArmPlatformLibHisilicon/ArmPlatformLibSec.inf
> >>>
> >>> The inf file in the Platform/ARM/ folder is based
> >>> on the one in Silicon/Hisilicon/. Modify the one
> >> [SAMI] I believe you want to say the inf file in Silicon/Hisilicon/is based on the one in Platform/ARM and therefore you are modifying the inf file in Silicon/Hisilicon.
> >> Please let me know if this is correct, and I will update this in the commit message before merging the change.
> >> [/SAMI]
> >>> in the Silicon/Hisilicon/.
> >>>
> >>> Signed-off-by: Pierre Gondois <Pierre.Gondois@...>
> >>> ---
> >>> .../Library/ArmPlatformLibHisilicon/ArmPlatformLibSec.inf | 2 +-
> >>> 1 file changed, 1 insertion(+), 1 deletion(-)
> >>>
> >>> diff --git a/Silicon/Hisilicon/Library/ArmPlatformLibHisilicon/ArmPlatformLibSec.inf b/Silicon/Hisilicon/Library/ArmPlatformLibHisilicon/ArmPlatformLibSec.inf
> >>> index ac587deedfd8..7fd7b5183e5b 100644
> >>> --- a/Silicon/Hisilicon/Library/ArmPlatformLibHisilicon/ArmPlatformLibSec.inf
> >>> +++ b/Silicon/Hisilicon/Library/ArmPlatformLibHisilicon/ArmPlatformLibSec.inf
> >>> @@ -12,7 +12,7 @@
> >>> [Defines]
> >>> INF_VERSION = 0x00010005
> >>> BASE_NAME = ArmPlatformLibSec
> >>> - FILE_GUID = a79eed97-4b98-4974-9690-37b32d6a5b56
> >>> + FILE_GUID = ABF3B82B-892F-438F-901F-F148C2DF89E6
> >>> MODULE_TYPE = BASE
> >>> VERSION_STRING = 1.0
> >>> LIBRARY_CLASS = ArmPlatformLib
> >>> --
> >>> 2.25.1
>
> IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
>
>
>
>
>


[edk2-platforms: PATCH] REF: https://bugzilla.tianocore.org/show_bug.cgi?id=3787

JackX Lin
 

System will occur a CPU exception error when sorting CPU
APIC map, because of a pointer point to an wrong space.

Signed-off-by: JackX Lin <JackX.Lin@...>
Cc: Chasel Chiu <chasel.chiu@...>
Cc: Dong Eric <eric.dong@...>
Cc: Jiewen Yao <jiewen.yao@...>
Cc: Ray Ni <ray.ni@...>
Cc: Rangasai V Chaganty <rangasai.v.chaganty@...>
Cc: Donald Kuo <Donald.Kuo@...>
Cc: Chandana C Kumar <chandana.c.kumar@...>
Cc: JackX Lin <JackX.Lin@...>
---
Platform/Intel/MinPlatformPkg/Acpi/AcpiTables/AcpiPlatform.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Platform/Intel/MinPlatformPkg/Acpi/AcpiTables/AcpiPlatform.c b/Platform/Intel/MinPlatformPkg/Acpi/AcpiTables/AcpiPlatform.c
index 785cf4c2f9..e346d781db 100644
--- a/Platform/Intel/MinPlatformPkg/Acpi/AcpiTables/AcpiPlatform.c
+++ b/Platform/Intel/MinPlatformPkg/Acpi/AcpiTables/AcpiPlatform.c
@@ -231,9 +231,9 @@ SortCpuLocalApicInTable (
if (TempCpuApicIdOrderTable[0].ApicId != BspApicId) {
for (Index = 0; Index < mNumberOfCpus; Index++) {
if ((TempCpuApicIdOrderTable[Index].Flags == 1) && (TempCpuApicIdOrderTable[Index].ApicId == BspApicId)) {
- CopyMem (&TempVal, &TempCpuApicIdOrderTable[Index], sizeof (EFI_CPU_ID_ORDER_MAP));
+ CopyMem (TempVal, &TempCpuApicIdOrderTable[Index], sizeof (EFI_CPU_ID_ORDER_MAP));
CopyMem (&TempCpuApicIdOrderTable[Index], &TempCpuApicIdOrderTable[0], sizeof (EFI_CPU_ID_ORDER_MAP));
- CopyMem (&TempCpuApicIdOrderTable[0], &TempVal, sizeof (EFI_CPU_ID_ORDER_MAP));
+ CopyMem (&TempCpuApicIdOrderTable[0], TempVal, sizeof (EFI_CPU_ID_ORDER_MAP));
break;
}
}
--
2.32.0.windows.2


[Patch V6 1/1] BaseTools: Add FMMT Tool

Yuwei Chen
 

The FMMT python tool is used for firmware files operation, which has
the Fv/FFs-based 'View'&'Add'&'Delete'&'Replace' operation function:

1.Parse a FD(Firmware Device) / FV(Firmware Volume) / FFS(Firmware Files)
2.Add a new FFS into a FV file (both included in a FD file or not)
3.Replace an FFS in a FV file with a new FFS file
4.Delete an FFS in a FV file (both included in a FD file or not)
5.Extract the FFS from a FV file (both included in a FD file or not)

This version of FMMT Python tool does not support PEIM rebase feature,
this feature will be added in future update.

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.

REF: https://bugzilla.tianocore.org/show_bug.cgi?id=1847
RFC Link: 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@...>
---
V6 modifies the Readme file.
BaseTools/BinWrappers/PosixLike/FMMT | 14 ++++++++++++++
BaseTools/BinWrappers/WindowsLike/FMMT.bat | 4 ++++
BaseTools/Source/Python/FMMT/FMMT.py | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/FMMTConfig.ini | 11 +++++++++++
BaseTools/Source/Python/FMMT/Img/FirmwareVolumeFormat.png | Bin 0 -> 29515 bytes
BaseTools/Source/Python/FMMT/Img/NodeTreeFormat.png | Bin 0 -> 79906 bytes
BaseTools/Source/Python/FMMT/PI/Common.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/PI/FfsFileHeader.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/PI/FvHeader.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/PI/SectionHeader.py | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/PI/__init__.py | 6 ++++++
BaseTools/Source/Python/FMMT/README.md | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/__init__.py | 6 ++++++
BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/core/BiosTree.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/core/BiosTreeNode.py | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/core/FMMTOperation.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/core/FMMTParser.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/core/FvHandler.py | 521 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/core/GuidTools.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
BaseTools/Source/Python/FMMT/utils/FmmtLogger.py | 18 ++++++++++++++++++
BaseTools/Source/Python/FMMT/utils/FvLayoutPrint.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 2449 insertions(+)

diff --git a/BaseTools/BinWrappers/PosixLike/FMMT b/BaseTools/BinWrappers/PosixLike/FMMT
new file mode 100644
index 000000000000..c90a770f2e00
--- /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..f0551c4ac54a
--- /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..5028d8446ee0
--- /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..aa2444f11b38
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/FMMTConfig.ini
@@ -0,0 +1,11 @@
+## @file
+# This file is used to define the FMMT dependent external tool guid.
+#
+# Copyright (c) 2021-, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+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..efab874ee08b
--- /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..33c49ffb50bc
--- /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..aae2feae844a
--- /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..c2cc8e0172fb
--- /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..4e8296fad1ba
--- /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..deb3d1ba0ad5
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/README.md
@@ -0,0 +1,191 @@
+# 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 reliable.
+
+# FMMT User Guide
+
+#### Last updated December 20, 2021
+
+Important Changes and Updates:
+
+- Dec 20, 2021 Initial Draft of FMMT Python Tool;
+
+#### Known issues & Notes
+
+- The initial draft of FMMT Python Tool did not support the PEIM Rebase feature, it will be implemented in future update. For this feature, please use the origin FMMT C Tool, usage is shown as follows.
+
+#### **Origin C Tools Usage**
+
+- Tools Location: https://github.com/tianocore/edk2-staging/tree/FceFmmt
+- How to use:
+ - git clone https://github.com/tianocore/edk2-staging/tree/FceFmmt
+ - edksetup.bat
+ - FMMT -v/-a/-d/-r/-e parameters
+
+# 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)
+
+<center>Figure 1. The Firmware Volume Format</center>
+
+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 >***
+
+- **Explanation**: 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.
+- **Example**: *FMMT -v test.fd / FMMT -v test.fd -l txt*
+
+### 2.2.2 Syntax for Add a new FFS
+
+***-a < Inputfile > < TargetFvName/TargetFvGuid > < NewFfsFile > < Outputfile >***
+
+- **Explanation**: Add the *NewFfsFile* into *Inputfile*. *TargetFvName/TargetFvGuid* (Name or Guid) is the TargetFv which *NewFfsFile* will be added into.
+- **Example**: *FMMT -a test.fd 7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1 PcdDxe out.fd*
+
+### 2.2.3 Syntax for Delete an FFS
+
+***-d < Inputfile > < TargetFfsName > < Outputfile > < TargetFvName/TargetFvGuid >***
+
+- **Explanation**: Delete the Ffs from *Inputfile*. TargetFfsName (Guid) is the TargetFfs which will be deleted. *TargetFvName/TargetFvGuid* is optional, which is the parent of TargetFfs*.*
+- **Example**: *FMMT -d test.fd PcdDxe out.fd 7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1*
+
+### 2.2.4 Syntax for Replace an FFS
+
+***-r < Inputfile > < TargetFfsName > < NewFfsFile > < Outputfile > < TargetFvName/TargetFvGuid >***
+
+- **Explanation**: 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*.*
+- **Example**: *FMMT -r test.fd PcdDxe NewPcdDxe.ffs out.fd 7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1*
+
+### 2.2.5 Syntax for Extract an FFS
+
+***-e < Inputfile > < TargetFfsName > < Outputfile >***
+
+- **Explanation**: Extract the Ffs from the Inputfile. TargetFfsName (Guid) is the TargetFfs which will be extracted.
+- **Example**: *FMMT -e test.fd PcdDxe out.fd*
+
+
+
+# 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)
+
+<center>Figure 2. The NodeTree format</center>
+
+### 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*** |
diff --git a/BaseTools/Source/Python/FMMT/__init__.py b/BaseTools/Source/Python/FMMT/__init__.py
new file mode 100644
index 000000000000..f0e3e444ec86
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/__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
+##
diff --git a/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py b/BaseTools/Source/Python/FMMT/core/BinaryFactoryProduct.py
new file mode 100644
index 000000000000..c7e341c18ace
--- /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:
+ 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:
+ 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:
+ 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..0b6252ecc1bb
--- /dev/null
+++ b/BaseTools/Source/Python/FMMT/core/BiosTree.py
@@ -0,0 +1,198 @@
+## @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 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, ParFvId="") -> 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, ParFvId))
+ space += 2
+ else:
+ Info.append("FvId: {}".format(Key))
+ ParFvId = 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, ParFvId)
+
+ 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..cfb711eea5a2
--- /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.FFS_FILE_SIZE != 0 and self.Header.Attributes != 0xff and self.Header.Attributes & 0x01 == 1:
+ print('Error Ffs Header! Ffs Header Size and Attributes is not matched!')
+ if self.Header.FFS_FILE_SIZE == 0 and self.Header.Attributes & 0x01 == 1:
+ 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..a0432c4093cf
--- /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..6e54b860a9c9
--- /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..f67a73d0690c
--- /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.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.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
+ 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..e21d8e0406ae
--- /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..18f0cff96e4b
--- /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..1a6c67056266
--- /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.26.1.windows.1


Re: [PATCH v4 1/1] UefiCpuPkg: Extend measurement of microcode patches to TPM

Ni, Ray
 

Reviewed-by: Ray Ni <ray.ni@...>

-----Original Message-----
From: Yang, Longlong <longlong.yang@...>
Sent: Tuesday, December 14, 2021 3:19 PM
To: devel@edk2.groups.io
Cc: Yang, Longlong <longlong.yang@...>; Dong, Eric <eric.dong@...>; Ni, Ray <ray.ni@...>; Kumar, Rahul1 <rahul1.kumar@...>; Yao, Jiewen <jiewen.yao@...>; Xu, Min M <min.m.xu@...>; Zhang, Qi1 <qi1.zhang@...>
Subject: [PATCH v4 1/1] UefiCpuPkg: Extend measurement of microcode patches to TPM

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

TCG specification says BIOS should extend measurement of microcode to TPM.
However, reference BIOS is not doing this. BIOS shall extend measurement of
microcode to TPM.

Cc: Eric Dong <eric.dong@...>
Cc: Ray Ni <ray.ni@...>
Cc: Rahul Kumar <rahul1.kumar@...>
Cc: Jiewen Yao <jiewen.yao@...>
Cc: Min M Xu <min.m.xu@...>
Cc: Qi Zhang <qi1.zhang@...>
Signed-off-by: Longlong Yang <longlong.yang@...>
---
.../MicrocodeMeasurementDxe.c | 281 ++++++++++++++++++
.../MicrocodeMeasurementDxe.inf | 56 ++++
.../MicrocodeMeasurementDxe.uni | 15 +
.../MicrocodeMeasurementDxeExtra.uni | 12 +
UefiCpuPkg/UefiCpuPkg.dsc | 1 +
5 files changed, 365 insertions(+)
create mode 100644 UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.c
create mode 100644 UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.inf
create mode 100644 UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.uni
create mode 100644 UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxeExtra.uni

diff --git a/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.c b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.c
new file mode 100644
index 000000000000..762ca159ff0e
--- /dev/null
+++ b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.c
@@ -0,0 +1,281 @@
+/** @file
+ This driver measures microcode patches to TPM.
+
+ This driver consumes gEdkiiMicrocodePatchHobGuid, packs all unique microcode patch found in gEdkiiMicrocodePatchHobGuid to a binary blob, and measures the binary blob to TPM.
+
+ Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <IndustryStandard/UefiTcgPlatform.h>
+#include <Guid/EventGroup.h>
+#include <Guid/MicrocodePatchHob.h>
+#include <Library/DebugLib.h>
+#include <Library/UefiDriverEntryPoint.h>
+#include <Library/UefiLib.h>
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/HobLib.h>
+#include <Library/MicrocodeLib.h>
+#include <Library/TpmMeasurementLib.h>
+
+#define CPU_MICROCODE_MEASUREMENT_DESCRIPTION "Microcode Measurement"
+#define CPU_MICROCODE_MEASUREMENT_EVENT_LOG_DESCRIPTION_LEN sizeof (CPU_MICROCODE_MEASUREMENT_DESCRIPTION)
+
+#pragma pack(1)
+typedef struct {
+ UINT8 Description[CPU_MICROCODE_MEASUREMENT_EVENT_LOG_DESCRIPTION_LEN];
+ UINTN NumberOfMicrocodePatchesMeasured;
+ UINTN SizeOfMicrocodePatchesMeasured;
+} CPU_MICROCODE_MEASUREMENT_EVENT_LOG;
+#pragma pack()
+
+/**
+ Helping function.
+
+ The function is called by QuickSort to compare the order of offsets of
+ two microcode patches in RAM relative to their base address. Elements
+ will be in ascending order.
+
+ @param[in] Offset1 The pointer to the offset of first microcode patch.
+ @param[in] Offset2 The pointer to the offset of second microcode patch.
+
+ @retval 1 The offset of first microcode patch is bigger than that of the second.
+ @retval -1 The offset of first microcode patch is smaller than that of the second.
+ @retval 0 The offset of first microcode patch equals to that of the second.
+**/
+INTN
+EFIAPI
+MicrocodePatchOffsetCompareFunction (
+ IN CONST VOID *Offset1,
+ IN CONST VOID *Offset2
+ )
+{
+ if (*(UINT64 *)(Offset1) > *(UINT64 *)(Offset2)) {
+ return 1;
+ } else if (*(UINT64 *)(Offset1) < *(UINT64 *)(Offset2)) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ This function remove duplicate and invalid offsets in Offsets.
+
+ This function remove duplicate and invalid offsets in Offsets. Invalid offset means MAX_UINT64 in Offsets.
+
+ @param[in] Offsets Microcode offset list.
+ @param[in, out] Count On call as the count of raw microcode offset list; On return as count of the clean microcode offset list.
+ **/
+VOID
+RemoveDuplicateAndInvalidOffset (
+ IN UINT64 *Offsets,
+ IN OUT UINTN *Count
+ )
+{
+ UINTN Index;
+ UINTN NewCount;
+ UINT64 LastOffset;
+ UINT64 QuickSortBuffer;
+
+ //
+ // The order matters when packing all applied microcode patches to a single binary blob.
+ // Therefore it is a must to do sorting before packing.
+ // NOTE: Since microcode patches are sorted by their addresses in memory, the order of
+ // addresses in memory of all the microcode patches before sorting is required to be the
+ // same in every boot flow. If any future updates made this assumption untenable, then
+ // there needs a new solution to measure microcode patches.
+ //
+ QuickSort (
+ Offsets,
+ *Count,
+ sizeof (UINT64),
+ MicrocodePatchOffsetCompareFunction,
+ (VOID *)&QuickSortBuffer
+ );
+
+ NewCount = 0;
+ LastOffset = MAX_UINT64;
+ for (Index = 0; Index < *Count; Index++) {
+ //
+ // When MAX_UINT64 element is met, all following elements are MAX_UINT64.
+ //
+ if (Offsets[Index] == MAX_UINT64) {
+ break;
+ }
+
+ //
+ // Remove duplicated offsets
+ //
+ if (Offsets[Index] != LastOffset) {
+ LastOffset = Offsets[Index];
+ Offsets[NewCount] = Offsets[Index];
+ NewCount++;
+ }
+ }
+
+ *Count = NewCount;
+}
+
+/**
+ Callback function.
+
+ Called after signaling of the Ready to Boot Event. Measure microcode patches binary blob with event type EV_CPU_MICROCODE to PCR[1] in TPM.
+
+ @param[in] Event Event whose notification function is being invoked.
+ @param[in] Context Pointer to the notification function's context.
+
+**/
+VOID
+EFIAPI
+MeasureMicrocodePatches (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ EFI_STATUS Status;
+ UINT32 PCRIndex;
+ UINT32 EventType;
+ CPU_MICROCODE_MEASUREMENT_EVENT_LOG EventLog;
+ UINT32 EventLogSize;
+ EFI_HOB_GUID_TYPE *GuidHob;
+ EDKII_MICROCODE_PATCH_HOB *MicrocodePatchHob;
+ UINT64 *Offsets;
+ UINTN Count;
+ UINTN Index;
+ UINTN TotalMicrocodeSize;
+ UINT8 *MicrocodePatchesBlob;
+
+ PCRIndex = 1;
+ EventType = EV_CPU_MICROCODE;
+ AsciiStrCpyS (
+ (CHAR8 *)(EventLog.Description),
+ CPU_MICROCODE_MEASUREMENT_EVENT_LOG_DESCRIPTION_LEN,
+ CPU_MICROCODE_MEASUREMENT_DESCRIPTION
+ );
+ EventLog.NumberOfMicrocodePatchesMeasured = 0;
+ EventLog.SizeOfMicrocodePatchesMeasured = 0;
+ EventLogSize = sizeof (CPU_MICROCODE_MEASUREMENT_EVENT_LOG);
+ Offsets = NULL;
+ TotalMicrocodeSize = 0;
+ Count = 0;
+
+ GuidHob = GetFirstGuidHob (&gEdkiiMicrocodePatchHobGuid);
+ if (NULL == GuidHob) {
+ DEBUG ((DEBUG_ERROR, "ERROR: GetFirstGuidHob (&gEdkiiMicrocodePatchHobGuid) failed.\n"));
+ return;
+ }
+
+ MicrocodePatchHob = GET_GUID_HOB_DATA (GuidHob);
+ DEBUG (
+ (DEBUG_INFO,
+ "INFO: Got MicrocodePatchHob with microcode patches starting address:0x%x, microcode patches region size:0x%x, processor count:0x%x\n",
+ MicrocodePatchHob->MicrocodePatchAddress, MicrocodePatchHob->MicrocodePatchRegionSize,
+ MicrocodePatchHob->ProcessorCount)
+ );
+
+ Offsets = AllocateCopyPool (
+ MicrocodePatchHob->ProcessorCount * sizeof (UINT64),
+ MicrocodePatchHob->ProcessorSpecificPatchOffset
+ );
+ Count = MicrocodePatchHob->ProcessorCount;
+
+ RemoveDuplicateAndInvalidOffset (Offsets, &Count);
+
+ if (0 == Count) {
+ DEBUG ((DEBUG_INFO, "INFO: No microcode patch is ever applied, skip the measurement of microcode!\n"));
+ FreePool (Offsets);
+ return;
+ }
+
+ for (Index = 0; Index < Count; Index++) {
+ TotalMicrocodeSize +=
+ GetMicrocodeLength ((CPU_MICROCODE_HEADER *)((UINTN)(MicrocodePatchHob->MicrocodePatchAddress + Offsets[Index])));
+ }
+
+ EventLog.NumberOfMicrocodePatchesMeasured = Count;
+ EventLog.SizeOfMicrocodePatchesMeasured = TotalMicrocodeSize;
+
+ MicrocodePatchesBlob = AllocateZeroPool (TotalMicrocodeSize);
+ if (NULL == MicrocodePatchesBlob) {
+ DEBUG ((DEBUG_ERROR, "ERROR: AllocateZeroPool to MicrocodePatchesBlob failed!\n"));
+ FreePool (Offsets);
+ return;
+ }
+
+ TotalMicrocodeSize = 0;
+ for (Index = 0; Index < Count; Index++) {
+ CopyMem (
+ (VOID *)(MicrocodePatchesBlob + TotalMicrocodeSize),
+ (VOID *)((UINTN)(MicrocodePatchHob->MicrocodePatchAddress + Offsets[Index])),
+ (UINTN)(GetMicrocodeLength (
+ (CPU_MICROCODE_HEADER *)((UINTN)(MicrocodePatchHob->MicrocodePatchAddress +
+ Offsets[Index]))
+ ))
+ );
+ TotalMicrocodeSize +=
+ GetMicrocodeLength ((CPU_MICROCODE_HEADER *)((UINTN)(MicrocodePatchHob->MicrocodePatchAddress + Offsets[Index])));
+ }
+
+ Status = TpmMeasureAndLogData (
+ PCRIndex, // PCRIndex
+ EventType, // EventType
+ &EventLog, // EventLog
+ EventLogSize, // LogLen
+ MicrocodePatchesBlob, // HashData
+ TotalMicrocodeSize // HashDataLen
+ );
+ if (!EFI_ERROR (Status)) {
+ gBS->CloseEvent (Event);
+ DEBUG (
+ (DEBUG_INFO,
+ "INFO: %d Microcode patches are successfully extended to TPM! The total size measured to TPM is 0x%x\n",
+ Count,
+ TotalMicrocodeSize)
+ );
+ } else {
+ DEBUG ((DEBUG_ERROR, "ERROR: TpmMeasureAndLogData failed with status %a!\n", Status));
+ }
+
+ FreePool (Offsets);
+ FreePool (MicrocodePatchesBlob);
+ return;
+}
+
+/**
+
+ Driver to produce microcode measurement.
+
+ Driver to produce microcode measurement. Which install a callback function on ready to boot event.
+
+ @param ImageHandle Module's image handle
+ @param SystemTable Pointer of EFI_SYSTEM_TABLE
+
+ @return EFI_SUCCESS This function always complete successfully.
+
+**/
+EFI_STATUS
+EFIAPI
+MicrocodeMeasurementDriverEntryPoint (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ EFI_EVENT Event;
+
+ //
+ // Measure Microcode patches
+ //
+ EfiCreateEventReadyToBootEx (
+ TPL_CALLBACK,
+ MeasureMicrocodePatches,
+ NULL,
+ &Event
+ );
+
+ return EFI_SUCCESS;
+}
diff --git a/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.inf b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.inf
new file mode 100644
index 000000000000..649fb9403fd2
--- /dev/null
+++ b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.inf
@@ -0,0 +1,56 @@
+## @file
+# This driver measures microcode patches to TPM.
+#
+# This driver consumes gEdkiiMicrocodePatchHobGuid, packs all unique
+# microcode patch found in gEdkiiMicrocodePatchHobGuid to a binary blob,
+# and measures the binary blob to TPM.
+#
+# Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 0x00010005
+ BASE_NAME = MicrocodeMeasurementDxe
+ MODULE_UNI_FILE = MicrocodeMeasurementDxe.uni
+ FILE_GUID = 0A32A803-ACDF-4C89-8293-91011548CD91
+ MODULE_TYPE = DXE_DRIVER
+ VERSION_STRING = 1.0
+ ENTRY_POINT = MicrocodeMeasurementDriverEntryPoint
+
+#
+# The following information is for reference only and not required by the build tools.
+#
+# VALID_ARCHITECTURES = IA32 X64
+#
+
+[Sources]
+ MicrocodeMeasurementDxe.c
+
+[Packages]
+ MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
+ UefiCpuPkg/UefiCpuPkg.dec
+
+[LibraryClasses]
+ UefiBootServicesTableLib
+ MemoryAllocationLib
+ BaseMemoryLib
+ BaseLib
+ UefiLib
+ UefiDriverEntryPoint
+ DebugLib
+ HobLib
+ MicrocodeLib
+ TpmMeasurementLib
+
+[Guids]
+ gEdkiiMicrocodePatchHobGuid ## CONSUMES ## HOB
+
+[UserExtensions.TianoCore."ExtraFiles"]
+ MicrocodeMeasurementDxeExtra.uni
+
+[Depex]
+ TRUE
diff --git a/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.uni b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.uni
new file mode 100644
index 000000000000..5a21e955fbbf
--- /dev/null
+++ b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.uni
@@ -0,0 +1,15 @@
+// /** @file
+// This driver measures microcode patches to TPM.
+//
+// This driver consumes gEdkiiMicrocodePatchHobGuid, packs all uniquemicrocode patch found in gEdkiiMicrocodePatchHobGuid to a binary blob, and measures the binary blob to TPM.
+//
+// Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+
+#string STR_MODULE_ABSTRACT #language en-US "This driver measures Microcode Patches to TPM."
+
+#string STR_MODULE_DESCRIPTION #language en-US "This driver consumes gEdkiiMicrocodePatchHobGuid, packs all microcode patch found in gEdkiiMicrocodePatchHobGuid to a binary blob, and measure the binary blob to TPM."
diff --git a/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxeExtra.uni b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxeExtra.uni
new file mode 100644
index 000000000000..6990cee8c6fd
--- /dev/null
+++ b/UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxeExtra.uni
@@ -0,0 +1,12 @@
+// /** @file
+// MicrocodeMeasurementDxe Localized Strings and Content
+//
+// Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+#string STR_PROPERTIES_MODULE_NAME
+#language en-US
+"Microcode Patches Measurement DXE Driver"
diff --git a/UefiCpuPkg/UefiCpuPkg.dsc b/UefiCpuPkg/UefiCpuPkg.dsc
index 870b45284087..d1d61dd6a03b 100644
--- a/UefiCpuPkg/UefiCpuPkg.dsc
+++ b/UefiCpuPkg/UefiCpuPkg.dsc
@@ -119,6 +119,7 @@
UefiCpuPkg/Library/CpuTimerLib/BaseCpuTimerLib.inf
UefiCpuPkg/Library/CpuCacheInfoLib/PeiCpuCacheInfoLib.inf
UefiCpuPkg/Library/CpuCacheInfoLib/DxeCpuCacheInfoLib.inf
+ UefiCpuPkg/MicrocodeMeasurementDxe/MicrocodeMeasurementDxe.inf

[Components.IA32, Components.X64]
UefiCpuPkg/CpuDxe/CpuDxe.inf
--
2.31.1.windows.1


Re: [PATCH v8] IntelFsp2WrapperPkg : FSPM/S UPD data address based on Build Type

Ni, Ray
 

+UINTN
+EFIAPI
+GetFspmUpdDataAddress (
+ VOID
+ )

This is internal function. Please remove "EFIAPI".

11801 - 11820 of 96835