Date   

Re: ArmVirt and Self-Updating Code

Marvin Häuser
 

On 23.07.21 16:34, Ard Biesheuvel wrote:
On Fri, 23 Jul 2021 at 16:27, Marvin Häuser <mhaeuser@posteo.de> wrote:


On 23.07.21 16:09, Ard Biesheuvel wrote:
On Fri, 23 Jul 2021 at 12:47, Marvin Häuser <mhaeuser@posteo.de> wrote:
On 23.07.21 12:13, Ard Biesheuvel wrote:
On Fri, 23 Jul 2021 at 11:55, Marvin Häuser <mhaeuser@posteo.de> wrote:
...
2) emit a GOT, which ends up being converted to PE/COFF Relocations (->
self-relocation), for global data that cannot be referenced relatively?
Is there any way to know/force that no symbol in GOT is accessed up
until the end of the self-relocation process?
Do you maybe have one final comment regarding that second question,
please? :)
The RELA section is not converted into PE/COFF relocations. This would
not achieve a lot, given that no prior PE/COFF loader exists to
process them. There is a snippet of asm code in the startup code that
processes the R_AARCH64_RELATIVE relocation entries before calling
into C code.
I searched for said ASM code till my fingers fell asleep and at last found this: https://github.com/tianocore/edk2/commit/b16fd231f6d8124fa05a0f086840934b8709faf9#diff-3d563cc4775c7720900f4895bf619eed06291044aaa277fcc57eddc7618351a1L12-R148

If I understand the commit message correctly, it is basically "pray the C code does not use globals at all", which is fair enough, so maybe I should document this in my proposed new library? I trust that this is enough of a constraint for both ARM and AArch64, because I do not know them at all.

What worries me is that StandaloneMmCore has no such ASM entry point at all and instead it's just executing C directly. Also, it is not passed the "-fno-jump-tables" flag that is commented to be important in the commit linked above.

Best regards,
Marvin

This also gives us the guarantee that no GOT indirections are
dereferenced, given that our asm code simply does not do that.

Let's drop "GOT" and make it "any instruction that requires prior
relocation to function correctly".
The thing to keep in mind here is that R_AARCH64_RELATIVE relocations
never target instructions, but only memory locations that carry
absolute addresses. This could be locations in .rodata or .data
(global vars carrying pointer values), or GOT entries.

It is not really a GOT. Actually, a GOT is undesirable, as it forces
global variables to be referenced via an absolute address, even when a
relative reference could be used.
Hmm, the GCC docs say a GOT is used for "all constant addresses" (I took
it as "absolute"?), it is kind of vague. I understood it this way:
1) no-pie emits relocations that can target the .text and .data sections
for instructions that embed and variables that hold an absolute address
(I thought this was RELA?)
2) pie emits a GOT such that there are no relocations as described in
1), because all absolute addresses are indirected by GOT (just GOT
references are relocated)
Correct. And this works really well for shared libraries, where all
text and data sections can be shared between processes, as they will
not be modified by the loader. All locations targeted by relocations
will be nicely lumped together in the GOT.

However, for bare metal style programs, there is no sharing, and there
is no advantage to lumping anything together. It is much better to use
relative references where possible, and simply apply relocations
wherever needed across the text and data sections,

If I understood the process right, but the term (GOT) is wrong, sorry,
that is what I gathered from the docs. :)
I have a x86 + PE background, so ARM + ELF is a bit of a learning curve...
The GOT is a special data structure used for implicit variable
accesses, i.e., global vars used in the code. Statically initialized
pointer variables are the other category, which are not code, and for
which the same considerations do not apply, given that the right value
simply needs to be stored in the variable before the program starts.

For instance, a statically initialized pointer always carries an
absolute address, and so it always needs an entry in the RELA table

E.g.,

int foo = 10; // external linkage
static int *bar = &foo;

In this case, there is no way to use relative addressing because the
address of foo is taken at build time.

However, if bar would be something like

static int *bar() { return &foo; }

the address is only taken at runtime, and the compiler can use a
relative reference instead, and no RELA entry is needed. With a GOT,
we force the compiler to allocate a variable that holds the absolute
address, which we would prefer to avoid.
And this is not forced by whatever table -fpie uses, as per my
understanding above?
The selection of 'code model' as it is called is controlled by GCC's
-mcmodel= argument, which defaults to 'small' on AArch64, regardless
of whether you use PIC/PIE or not.
Aha, makes sense, thanks!

Best regards,
Marvin

“Now, StandaloneMmPkg has similar (self-)relocation code too:https://github.com/tianocore/edk2/blob/17143c4837393d42c484b42d1789b85b2cff1aaf/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c#L379-L386

Because I cannot find such elsewhere, I assume it must be for the same ARM virtualised environment as above.
No.

The binary it applies the Relocations to is documented to be the Standalone MM core, but in fact SecCore is located:

https://github.com/tianocore/edk2/blob/17143c4837393d42c484b42d1789b85b2cff1aaf/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/SetPermissions.c#L131-L158
As per your comments below, I think SecCore should not be located here.
Is the Standalone MM core of *type* SecCore in the FFS (without *being*
SecCore)? This confused me the most.
If the FFS SecCore section type is used here, it does not mean that
the image is a SEC image in the strict PI sense.

Perhaps we were just too lazy to add a new type to the FFS spec?
That is what I meant to imply with the middle question (well, not
necessarily "lazy", for ARM there simply seems to not be any reason to
distinguish if the environments are fully separate), just wanted to make
sure I understand what the code does before modifying it.

Thank you again!

Best regards,
Marvin

“This yields the following questions to me:

1) What even invokes Standalone MM on ARM? It is documented it is spawned during SEC, but I could not find any actual invocation.
It is not spawned by the normal world code that runs UEFI. It is a
secure world component that runs in a completely different execution
context (TrustZone). The code does run with the MMU enabled from the
start, but running from an a priori fixed offset was considered to be
a security hazard, so we added self relocation support.

The alternative would have been to add metadata to the StMmCore
component that can be interpreted by the secure world component that
loads it, but this would go beyond any existing specs, and make
portability more problematic.

2) Why does Standalone MM (self-)relocation locate SecCore? Should it not already have been relocated with the code from ArmPlatformPkg? Is Standalone MM embedded into ARM SecCore?
No and no. Standalone MM has nothing to do with the code that runs as
part of UEFI itself. ArmPlatformPkg is completely separate from
StandaloneMmPkg.

3) Why is SecCore the only module relocated? Are all others guaranteed to be "properly" loaded?
SecCore contains a PE/COFF loader, so all subsequent modules are
loaded normally. This is similar to the ArmVirtQemuKernel
self-relocating SEC module, which only relocates itself in this
manner, and relies on standard PE/COFF metadata for loading other
modules.
Interesting... this definitely is vastly different from the x86 side of
things. I think most things became very clear. Thanks a lot!

4) Is there maybe some high-level documented about the ARM boot flow? It seems to be significantly different from the x86 routes quite vastly.”
trustedfirmware.org may have some useful documentation.
I'll check it some time, hopefully this weekend. Thanks!
My pleasure.


SLDP: Usage of PE library context by debugger?

Marvin Häuser
 

Good day everyone,

While refining the port of SourceLevelDebugPkg to my newly proposed PeCoffLib rework (RFC upcoming), I noticed that the address of the PE Image context is written to DR2 [1]. Because the UDK and Intel System Studio debugging tools are closed source, I cannot verify what happens to this value. Does the host read the library context and retrieve data from it? If not, why is its address written to DR2? If so, this would mean the new PeCoffLib implementation breaks the existing debugging tools. The following questions would arise:

1) Which data are retrieved from the context structure? For GDB, I think only the Image address and symbol file path are required (to load the symbols), while PDB is saved in DR1 already.
2) Are there any plans to provide detailed documentation of the host/client communication protocol?
3) Are there any plans to provide an open source debugger, or at least the EDK II communication protocol portion?

Thank you for your time!

Best regards,
Marvin


[1] https://github.com/tianocore/edk2/blob/610bcc69ed3d1e8c016332a1862465d41d95dd6c/SourceLevelDebugPkg/Library/PeCoffExtraActionLibDebug/PeCoffExtraActionLib.c#L126


Re: [RFC PATCH v5 07/28] OvmfPkg/ResetVector: pre-validate the data pages used in SEC phase

Erdem Aktas
 

On Wed, Jun 30, 2021 at 5:54 AM Brijesh Singh <brijesh.singh@amd.com> wrote:

a) Enhance the OVMF reset vector code to validate the pages as described
above (go through step 2 - 3).
OR
b) Validate the pages during the guest creation time. The SEV firmware
provides a command which can be used by the VMM to validate the pages
without affecting the measurement of the launch.
Are you referring to the PAGE_TYPE_UNMEASURED? Does it not affect the
measurement , PAGE_INFO will be still measured, right?

Approach #b seems much simpler; it does not require any changes to the
OVMF reset vector code.
I am worried about verifying the measurement. I understand the secret
page and cpuid page being part of measurement because both of them are
mentioned in the AMD SNP SPEC but now we are introducing a new
parameters (all the 4KB page addresses between SNP_HV_VALIDATED_START
and SNP_HV_VALIDATED_END) that VM owner needs to know to calculate the
measurement and verify the attestation.

Sorry if I am overthinking or missing something here.

-Erdem


Re: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

Yao, Jiewen
 

I agree with Bret. Adding EmbeddedPkg dependency is not a good idea.

 

Another option: Is there any possibility to move the function to EmbeddedPkg and just add a simple function call in StanaloneMmPkg ?

 

Thank you

Yao Jiewen

 

From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Andrew Fish via groups.io
Sent: Saturday, July 31, 2021 8:45 AM
To: edk2-devel-groups-io <devel@edk2.groups.io>; bret.barkelew@...
Cc: sayanta.pattanayak@...; Ard Biesheuvel <ardb+tianocore@...>; Sami Mujawar <sami.mujawar@...>
Subject: Re: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

 

 



On Jul 30, 2021, at 12:34 PM, Bret Barkelew via groups.io <bret.barkelew@...> wrote:

 

I don’t think this is a good dependency. StandaloneMmCore shouldn’t require anything from EmbeddedPkg, and if it does there’s probably some more generalization necessary.

 

Further, we can’t require the MmCore (which should be considered generic at the level of PEI Core or DXE Core) include some external library like Libfdt without significant justification.

 

 

I think we should understand if this is being driven by some kind of standard? Maybe the Flat Device Tree lib should be part of the MdePkg? Or maybe MdeModulePkg?

 

Thanks,

 

Andrew Fish



- Bret 

 

From: Sayanta Pattanayak via groups.io
Sent: Friday, July 30, 2021 10:36 AM
To: devel@edk2.groups.io
Cc: Ard Biesheuvel; Sami Mujawar; Achin Gupta
Subject: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

 

Introduce support to populate StMM boot data via DTS parsing. The DTB is
passed as a boot argument by a binary of higer exception level.
Previously it was achieved by placing the boot data structure in a
shared buffer and the address of this shared buffer was passed by the
binary of higher exception level. Now either of the option can be used
for populating StMM boot info.

StMM boot information structure binding in device tree can be of following
prototype. Property values are not mentioned here.

bootarg {
  compatible = "bootargs";
  h_type  = <..>;
  h_version = <..>;
  h_size    = <..>;
  h_attr    = <..>;
  sp_mem_base         = <..>;
  sp_mem_limit        = <..>;
  sp_image_base       = <..>;
  sp_stack_base       = <..>;
  sp_heap_base        = <..>;
  sp_ns_comm_buf_base = <..>;fc
  sp_shared_buf_base  = <..>;
  sp_image_size       = <..>;
  sp_pcpu_stack_size  = <..>;
  sp_heap_size        = <..>;
  sp_ns_comm_buf_size = <..>;
  sp_shared_buf_size  = <..>;
  num_sp_mem_regions  = <..>;
  num_cpus            = <..>;
};

Addition of DTS supoort involves a dependency on FdtLib from EmbeddedPkg.

Signed-off-by: Sayanta Pattanayak <sayanta.pattanayak@...>
---
 Link to github branch with this patch -
 https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FSayantaP-arm%2Fedk2%2Ftree%2Fstmm-dts&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=gJw%2BH8Dj9lih1DNrIzbNPToJn9nH%2BHqpvpSr2JJrS4I%3D&amp;reserved=0

 StandaloneMmPkg/StandaloneMmPkg.dsc                                                     |   1 +
 StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf       |   3 +
 StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c | 153 ++++++++++++++++++--
 3 files changed, 143 insertions(+), 14 deletions(-)

diff --git a/StandaloneMmPkg/StandaloneMmPkg.dsc b/StandaloneMmPkg/StandaloneMmPkg.dsc
index 0c45df95e2dd..e3a3a6ee3ba1 100644
--- a/StandaloneMmPkg/StandaloneMmPkg.dsc
+++ b/StandaloneMmPkg/StandaloneMmPkg.dsc
@@ -49,6 +49,7 @@
   HobLib|StandaloneMmPkg/Library/StandaloneMmHobLib/StandaloneMmHobLib.inf
   IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
   MemLib|StandaloneMmPkg/Library/StandaloneMmMemLib/StandaloneMmMemLib.inf
+  FdtLib|EmbeddedPkg/Library/FdtLib/FdtLib.inf
   MemoryAllocationLib|StandaloneMmPkg/Library/StandaloneMmCoreMemoryAllocationLib/StandaloneMmCoreMemoryAllocationLib.inf
   MmServicesTableLib|MdePkg/Library/StandaloneMmServicesTableLib/StandaloneMmServicesTableLib.inf
   PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
index 4fa426f58ef4..0a2e519dd664 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
@@ -30,6 +30,7 @@
   X64/StandaloneMmCoreEntryPoint.c
 
 [Packages]
+  EmbeddedPkg/EmbeddedPkg.dec
   MdePkg/MdePkg.dec
   MdeModulePkg/MdeModulePkg.dec
   StandaloneMmPkg/StandaloneMmPkg.dec
@@ -40,10 +41,12 @@
 [LibraryClasses]
   BaseLib
   DebugLib
+  FdtLib
 
 [LibraryClasses.AARCH64]
   StandaloneMmMmuLib
   ArmSvcLib
+  FdtLib
 
 [Guids]
   gMpInformationHobGuid
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
index 6c50f470aa35..cc09d75dac36 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
@@ -16,6 +16,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
 #include <Guid/MmramMemoryReserve.h>
 #include <Guid/MpInformation.h>
 
+#include <libfdt.h>
 #include <Library/ArmMmuLib.h>
 #include <Library/ArmSvcLib.h>
 #include <Library/DebugLib.h>
@@ -45,33 +46,31 @@ STATIC CONST UINT32 mSpmMinorVerFfa = SPM_MINOR_VERSION_FFA;
 PI_MM_ARM_TF_CPU_DRIVER_ENTRYPOINT      CpuDriverEntryPoint = NULL;
 
 /**
-  Retrieve a pointer to and print the boot information passed by privileged
-  secure firmware.
+  Prints boot information.
 
-  @param  [in] SharedBufAddress   The pointer memory shared with privileged
-                                  firmware.
+  This function prints the boot information, which is passed by privileged
+  secure firmware through shared buffer or other mechanism.
 
+  @param  [in] PayloadBootInfo   Pointer to StandaloneMM Boot Info structure.
 **/
-EFI_SECURE_PARTITION_BOOT_INFO *
-GetAndPrintBootinformation (
-  IN VOID                      *SharedBufAddress
+VOID
+PrintBootinformation (
+  IN EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo
 )
 {
-  EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
   EFI_SECURE_PARTITION_CPU_INFO  *PayloadCpuInfo;
   UINTN                          Index;
 
-  PayloadBootInfo = (EFI_SECURE_PARTITION_BOOT_INFO *) SharedBufAddress;
 
   if (PayloadBootInfo == NULL) {
     DEBUG ((DEBUG_ERROR, "PayloadBootInfo NULL\n"));
-    return NULL;
+    return;
   }
 
   if (PayloadBootInfo->Header.Version != BOOT_PAYLOAD_VERSION) {
     DEBUG ((DEBUG_ERROR, "Boot Information Version Mismatch. Current=0x%x, Expected=0x%x.\n",
             PayloadBootInfo->Header.Version, BOOT_PAYLOAD_VERSION));
-    return NULL;
+    return;
   }
 
   DEBUG ((DEBUG_INFO, "NumSpMemRegions - 0x%x\n", PayloadBootInfo->NumSpMemRegions));
@@ -96,7 +95,7 @@ GetAndPrintBootinformation (
 
   if (PayloadCpuInfo == NULL) {
     DEBUG ((DEBUG_ERROR, "PayloadCpuInfo NULL\n"));
-    return NULL;
+    return;
   }
 
   for (Index = 0; Index < PayloadBootInfo->NumCpus; Index++) {
@@ -105,7 +104,7 @@ GetAndPrintBootinformation (
     DEBUG ((DEBUG_INFO, "Flags           - 0x%x\n", PayloadCpuInfo[Index].Flags));
   }
 
-  return PayloadBootInfo;
+  return;
 }
 
 /**
@@ -194,6 +193,119 @@ DelegatedEventLoop (
   }
 }
 
+/**
+  Populates StandAloneMM boot information structure.
+
+  This function receives dtb Address, where StMM Boot information specific
+  properties will be looked out to form the booting structure of type
+  EFI_SECURE_PARTITION_BOOT_INFO. At first, the properties for StandAloneMM
+  ConfigSize and  Memory limit will be checked out. Boot information will
+  be stored at address (Memory Limit - ConfigSize). Thereafter all boot
+  information specific properties will be parsed and corresponding values
+  will be obtained.
+
+  @param  [out] BootInfo   Pointer, where Boot Info structure will be populated.
+  @param  [in] DtbAddress  Address of the Device tree from where Boot
+                           information will be fetched.
+**/
+VOID
+PopulateBootinformation (
+  OUT EFI_SECURE_PARTITION_BOOT_INFO **BootInfo,
+  IN VOID   *DtbAddress
+)
+{
+  INT32           Offset;
+  CONST UINT32    *Property;
+  CONST UINT64    *Property64;
+  UINT32          ConfigSize;
+  UINT64          SpMemLimit;
+  EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
+
+  Offset = fdt_node_offset_by_compatible (DtbAddress, -1, "config-size");
+  if (Offset < 0) {
+    DEBUG ((DEBUG_WARN, "Total Config Size is not  defined\n"));
+  } else {
+    Property = fdt_getprop (DtbAddress, Offset, "size", NULL);
+    if (Property) {
+      ConfigSize = fdt32_to_cpu (*Property);
+      DEBUG ((DEBUG_INFO, "stmm dtb config-size  = 0x%x \n", ConfigSize));
+    }
+  }
+
+  Offset = fdt_node_offset_by_compatible (DtbAddress, -1, "bootargs");
+  if (Offset >= 0) {
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_mem_limit", NULL);
+    SpMemLimit = fdt64_to_cpu (*Property64);
+  }
+
+  if (SpMemLimit && ConfigSize)
+    PayloadBootInfo =
+      (EFI_SECURE_PARTITION_BOOT_INFO *)(SpMemLimit - ConfigSize);
+
+  if (PayloadBootInfo) {
+    PayloadBootInfo->SpMemLimit = SpMemLimit;
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_type", NULL);
+    PayloadBootInfo->Header.Type = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_version", NULL);
+    PayloadBootInfo->Header.Version = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_size", NULL);
+    PayloadBootInfo->Header.Size = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_attr", NULL);
+    PayloadBootInfo->Header.Attr = fdt32_to_cpu(*Property);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_mem_base", NULL);
+    PayloadBootInfo->SpMemBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_image_base", NULL);
+    PayloadBootInfo->SpImageBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_stack_base", NULL);
+    PayloadBootInfo->SpStackBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_heap_base", NULL);
+    PayloadBootInfo->SpHeapBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_base", NULL);
+    PayloadBootInfo->SpNsCommBufBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_shared_buf_base", NULL);
+    PayloadBootInfo->SpSharedBufBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_image_size", NULL);
+    PayloadBootInfo->SpImageSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_pcpu_stack_size", NULL);
+    PayloadBootInfo->SpPcpuStackSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_heap_size", NULL);
+    PayloadBootInfo->SpHeapSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_size", NULL);
+    PayloadBootInfo->SpNsCommBufSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_shared_buf_size", NULL);
+    PayloadBootInfo->SpPcpuSharedBufSize = fdt64_to_cpu(*Property64);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "num_sp_mem_regions", NULL);
+    PayloadBootInfo->NumSpMemRegions = fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "num_cpus", NULL);
+    PayloadBootInfo->NumCpus = fdt32_to_cpu(*Property);
+
+    PayloadBootInfo->CpuInfo =
+      (EFI_SECURE_PARTITION_CPU_INFO *)((UINT64)PayloadBootInfo +
+                                        sizeof(EFI_SECURE_PARTITION_BOOT_INFO));
+  }
+
+  *BootInfo = PayloadBootInfo;
+
+  return;
+}
+
 /**
   Query the SPM version, check compatibility and return success if compatible.
 
@@ -313,6 +425,7 @@ _ModuleEntryPoint (
   VOID                                    *TeData;
   UINTN                                   TeDataSize;
   EFI_PHYSICAL_ADDRESS                    ImageBase;
+  VOID                                    *DtbAddress;
 
   // Get Secure Partition Manager Version Information
   Status = GetSpmVersion ();
@@ -320,12 +433,24 @@ _ModuleEntryPoint (
     goto finish;
   }
 
-  PayloadBootInfo = GetAndPrintBootinformation (SharedBufAddress);
+  // In cookie1 the DTB address is passed. With reference to DTB, Boot
+  // info structure can be populated.
+  // If cookie1 doesn't have any value, then Boot info is copied from
+  // Sharedbuffer.
+  if (cookie1) {
+    DtbAddress = (void *)cookie1;
+    PopulateBootinformation (&PayloadBootInfo, DtbAddress);
+  } else {
+    PayloadBootInfo = (EFI_SECURE_PARTITION_BOOT_INFO *)SharedBufAddress;
+  }
+
   if (PayloadBootInfo == NULL) {
     Status = EFI_UNSUPPORTED;
     goto finish;
   }
 
+  PrintBootinformation (PayloadBootInfo);
+
   // Locate PE/COFF File information for the Standalone MM core module
   Status = LocateStandaloneMmCorePeCoffData (
              (EFI_FIRMWARE_VOLUME_HEADER *) PayloadBootInfo->SpImageBase,
-- 
2.17.1



-=-=-=-=-=-=
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#78449): https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Fmessage%2F78449&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=0DbkyBJe0prgx2vSCVVsrI0GoVbCipcfmbZp83LREjo%3D&amp;reserved=0
Mute This Topic: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgroups.io%2Fmt%2F84555304%2F1852292&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=1u3cr0MmJmjrkxtGgfOe1mb8%2F7vUXaoyUr5%2FmC1Uq%2F8%3D&amp;reserved=0
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Funsub&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786142135%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=Yr%2BSNqWHllfxHkFqBhCyP2TE4IGRLFLm%2FCCWexjB0AM%3D&amp;reserved=0 [bret.barkelew@...]
-=-=-=-=-=-=


 

 


Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Yao, Jiewen
 

Hi Sean
That is excellent topic.

I agree with you that, one of most frequent asked question in the design meeting is: Should I use protocol or library? Sigh...

I added my personal thought below. I would like other people to comment, Mike Kinney, Andrew Fish, Liming Gao, who knows the history and EDKII design philosophy very well.

==========
Besides the specification difference you mentioned in previous email,

1) *Protocol* is between 2 modules in UEFI/SMM phase. It is used to pass data or abstract function.
2) *PPI* is similar to *Protocol*. But it is between 2 modules in PEI phase.

3) *HOB* is between 2 modules in different phase, to pass the data from one phase to the other. Such as PEI passing HOB to DXE, PEI passing HOB to SMM.
4) *Variable* is also between 2 modules in different phase. In addition, the data can be *NON_VOLATILE* and always be there across boot.
5) *GUID configuration table* is also between 2 modules in different phase, but it is limited from UEFI to OS. From function perspective, GUID HOB is same as GUID Config Table. The difference is that HOB is PI/OEM Firmware scope, while the GUID Config Table is UEFI/OS interface scope.

So far, above is the EDK-I design. However, two big issues existed in EDK-I.
A) The policy protocol. Everybody hated the policy protocol in EDKII, but everybody has to use it because there is no other choice. Everyone keeps increase the policy version and adds their own stuff, which make code hard to unify.
B) The library. In EDK-I, we had crazy library design - EfiDriverLib, EfiCommonLib, EfiRuntimeLib, EfiShellLib. It looked good at first glance. But when we link them together, it fails. 2 libraries may have same function name, but implement in different way. 2 libraries may have different function name, but implement same function.

Those issues drove the two most important feature - PCD and Library design in EDK-II.

6) *PCD* is designed to abstract the OEM configuration data.
Static PCD means OEM can determine the value at build time.
Dynamic PCD means OEM need determine the value at runtime. (The implementation of PCD is still based upon PPI, Hob, and Protocol.)

7) A generic *library* is design to share common code in one place.
In addition, EDKII *library*serves another purpose: to abstract a set of common interface (as known as Library Class). That allows the OEM can replace the library implementation (as known as Library Instance) at build time without changing any code.

==========

Usually, we expose functions in a library instead of global data. (Well, there are exceptions such as gBS, gST, gRT, gSmst, which is well known pointers in history.)
I did not see too many cases to expose global data directly.

To compare UEFI protocol or C structure/class, I feel option 1 is similar to :
typedef struct {
Public:
UINTN Data;
} CLASS_XXX.

While, option 2 is similar to :
typedef struct {
Private:
UINTN Data;
Public:
UINTN GetData ();
VOID SetData (UINTN Data);
} CLASS_XXX.


I also see the similarity with https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DebugLib.h, such as DebugAssertEnabled(), DebugPrintEnabled(), DebugCodeEnabled(), DebugClearMemoryEnabled(), DebugPrintLevelEnabled().
That is the reason I vote option 2.


Thank you
Yao Jiewen

-----Original Message-----
From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Sean
Sent: Saturday, July 31, 2021 2:42 AM
To: devel@edk2.groups.io; Yao, Jiewen <jiewen.yao@intel.com>; Taylor Beebe
<t@taylorbeebe.com>; Wang, Jian J <jian.j.wang@intel.com>
Cc: Dong, Eric <eric.dong@intel.com>; Ni, Ray <ray.ni@intel.com>; Kumar,
Rahul1 <rahul1.kumar@intel.com>; mikuback@linux.microsoft.com; Wu, Hao A
<hao.a.wu@intel.com>; Bi, Dandan <dandan.bi@intel.com>;
gaoliming@byosoft.com.cn; Dong, Guo <guo.dong@intel.com>; Ma, Maurice
<maurice.ma@intel.com>; You, Benjamin <benjamin.you@intel.com>
Subject: Re: [edk2-devel] [RFC] MemoryProtectionLib for Dynamic Memory
Guard Settings

Jiewen,

**Slight rant**

I agree with libraries as an effective abstraction method. But I think
there needs to be a broad discussion about the order of preference for
methods of abstraction. Today the edk2 code base is a mix and often
there are numerous methods abstracting the same thing which leads to
confusion, misconfiguration, and error.

In the UEFI specification we have PPIs/Protocols/Events for functional
abstraction. We have variables, guided config tables, and HII for data
abstraction.

In the PI specification we add HOBs and PCDs for data abstractions.

Finally, in EDKII we add the library class concept and leverage it
heavily for arch, phase, and platform/behavioral abstractions.

Without clear guidance for how and when to use the above it is hard to
keep code being developed by the larger community consistent.

**End**

I was leaning towards something closer to

>> Option 1:
https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib_2

the HOB method and internally as we develop more code we are preferring
HOB and data abstractions more than functional abstraction. Data
abstractions can be used to control functional differences as well if
needed. Data abstractions allow for easier validation and support
diverse code environments. For example standalone MM and
payloadpkg/payload concepts. Finally, data abstractions break the need
for a monolithic code base. But as you can see in option 1 it actually
uses a library class abstraction as well because no one wants to write
the same code over and over again to get the HOB. The contract of the
library is just data but it still requires library mappings. Maybe
these types of libraries need to be treated differently.

Anyway it would be great to hear from other members of the community
around not just the memory protections RFC (this RFC) but around
preferences for abstraction techniques (pro/con). If an actual
discussion starts it could move to design meeting.

Thanks
Sean







On 7/29/2021 7:34 PM, Yao, Jiewen wrote:
Thanks. Code talks better.

I prefer option 2, which is a generic way for abstraction.

And you may enable option 1 under the cover of option 2, just create a lib
instance to get config from Hob.

Thank you
Yao Jiewen

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 30, 2021 10:07 AM
To: Yao, Jiewen <jiewen.yao@intel.com>; Wang, Jian J
<jian.j.wang@intel.com>;
devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <rahul1.kumar@intel.com>;
mikuback@linux.microsoft.com; Wu, Hao A <hao.a.wu@intel.com>; Bi,
Dandan
<dandan.bi@intel.com>; gaoliming@byosoft.com.cn; Dong, Guo
<guo.dong@intel.com>; Ma, Maurice <maurice.ma@intel.com>; You,
Benjamin
<benjamin.you@intel.com>
Subject: Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Of course - here are a couple of rough drafts:

Option 1:
https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib_2
Option 2:
https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib

On 7/29/2021 6:57 PM, Yao, Jiewen wrote:
Hi
Sorry, I am not able to follow the discussion.

Is there any sample or POC code to show the concept?

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 30, 2021 9:55 AM
To: Wang, Jian J <jian.j.wang@intel.com>; devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <rahul1.kumar@intel.com>;
mikuback@linux.microsoft.com; Wu, Hao A <hao.a.wu@intel.com>; Bi,
Dandan
<dandan.bi@intel.com>; gaoliming@byosoft.com.cn; Dong, Guo
<guo.dong@intel.com>; Ma, Maurice <maurice.ma@intel.com>; You,
Benjamin
<benjamin.you@intel.com>; Yao, Jiewen <jiewen.yao@intel.com>
Subject: Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard
Settings

Thanks for your feedback, Jian.

In option 2, a most basic implementation would returning the current
FixedAtBuild PCDs assuming they are kept. If they aren't, the library
implementer could simply hard-code the return value for each memory
protection setting.

In option 1, the HOB would be published in pre-mem and I'm not an expert
on exploiting the pre-mem environment. Jiewen may have more to say on
this.

-Taylor

On 7/28/2021 7:18 PM, Wang, Jian J wrote:
Thanks for the RFC. I'm not object to this idea. The only concern from me
is the potential security holes introduced by the changes. According to
your
description, it allows 3rd party software to violate memory protection
policy.
I'd like to see more explanations on how to avoid it to be exploited.

+Jiewen, what's current process to evaluate the security threat?

Regards,
Jian

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 23, 2021 8:33 AM
To: devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <Rahul1.Kumar@intel.com>;
mikuback@linux.microsoft.com; Wang, Jian J <jian.j.wang@intel.com>;
Wu,
Hao A <hao.a.wu@intel.com>; Bi, Dandan <dandan.bi@intel.com>;
gaoliming@byosoft.com.cn; Dong, Guo <guo.dong@intel.com>; Ma,
Maurice
<maurice.ma@intel.com>; You, Benjamin <benjamin.you@intel.com>
Subject: [RFC] MemoryProtectionLib for Dynamic Memory Guard
Settings

Current memory protection settings rely on FixedAtBuild PCD values
(minus PcdSetNxForStack). Because of this, the memory protection
configuration interface is fixed in nature. Cases arise in which memory
protections might need to be adjusted between boots (if platform design
allows) to avoid disabling a system. For example, platforms might
choose
to allow the user to control their protection policies such as allow
execution of critical 3rd party software that might violate memory
protections.

This RFC seeks your feedback regarding introducing an interface that
allows dynamic configuration of memory protection settings.

I would like to propose two options:
1. Describing the memory protection setting configuration in a HOB that
is produced by the platform.
2. Introducing a library class (e.g. MemoryProtectionLib) that allows
abstraction of the memory protection setting configuration data source.

In addition, I would like to know if the memory protection FixedAtBuild
PCDs currently in MdeModulePkg can be removed so we can move the
configuration interface entirely to an option above.

In any case, I would like the settings to be visible to environments
such as Standalone MM where dynamic PCDs are not accessible.

I am seeking your feedback on this proposal in preparation for sending
an edk2 patch series.

--
Taylor Beebe
Software Engineer @ Microsoft
--
Taylor Beebe
Software Engineer @ Microsoft
--
Taylor Beebe
Software Engineer @ Microsoft






Re: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

Andrew Fish
 



On Jul 30, 2021, at 12:34 PM, Bret Barkelew via groups.io <bret.barkelew@...> wrote:

I don’t think this is a good dependency. StandaloneMmCore shouldn’t require anything from EmbeddedPkg, and if it does there’s probably some more generalization necessary.
 
Further, we can’t require the MmCore (which should be considered generic at the level of PEI Core or DXE Core) include some external library like Libfdt without significant justification.
 

I think we should understand if this is being driven by some kind of standard? Maybe the Flat Device Tree lib should be part of the MdePkg? Or maybe MdeModulePkg?

Thanks,

Andrew Fish

- Bret 
 
From: Sayanta Pattanayak via groups.io
Sent: Friday, July 30, 2021 10:36 AM
To: devel@edk2.groups.io
Cc: Ard Biesheuvel; Sami Mujawar; Achin Gupta
Subject: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree
 

Introduce support to populate StMM boot data via DTS parsing. The DTB is
passed as a boot argument by a binary of higer exception level.
Previously it was achieved by placing the boot data structure in a
shared buffer and the address of this shared buffer was passed by the
binary of higher exception level. Now either of the option can be used
for populating StMM boot info.

StMM boot information structure binding in device tree can be of following
prototype. Property values are not mentioned here.

bootarg {
  compatible = "bootargs";
  h_type  = <..>;
  h_version = <..>;
  h_size    = <..>;
  h_attr    = <..>;
  sp_mem_base         = <..>;
  sp_mem_limit        = <..>;
  sp_image_base       = <..>;
  sp_stack_base       = <..>;
  sp_heap_base        = <..>;
  sp_ns_comm_buf_base = <..>;fc
  sp_shared_buf_base  = <..>;
  sp_image_size       = <..>;
  sp_pcpu_stack_size  = <..>;
  sp_heap_size        = <..>;
  sp_ns_comm_buf_size = <..>;
  sp_shared_buf_size  = <..>;
  num_sp_mem_regions  = <..>;
  num_cpus            = <..>;
};

Addition of DTS supoort involves a dependency on FdtLib from EmbeddedPkg.

Signed-off-by: Sayanta Pattanayak <sayanta.pattanayak@...>
---
 Link to github branch with this patch -
 https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FSayantaP-arm%2Fedk2%2Ftree%2Fstmm-dts&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=gJw%2BH8Dj9lih1DNrIzbNPToJn9nH%2BHqpvpSr2JJrS4I%3D&amp;reserved=0

 StandaloneMmPkg/StandaloneMmPkg.dsc                                                     |   1 +
 StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf       |   3 +
 StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c | 153 ++++++++++++++++++--
 3 files changed, 143 insertions(+), 14 deletions(-)

diff --git a/StandaloneMmPkg/StandaloneMmPkg.dsc b/StandaloneMmPkg/StandaloneMmPkg.dsc
index 0c45df95e2dd..e3a3a6ee3ba1 100644
--- a/StandaloneMmPkg/StandaloneMmPkg.dsc
+++ b/StandaloneMmPkg/StandaloneMmPkg.dsc
@@ -49,6 +49,7 @@
   HobLib|StandaloneMmPkg/Library/StandaloneMmHobLib/StandaloneMmHobLib.inf
   IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
   MemLib|StandaloneMmPkg/Library/StandaloneMmMemLib/StandaloneMmMemLib.inf
+  FdtLib|EmbeddedPkg/Library/FdtLib/FdtLib.inf
   MemoryAllocationLib|StandaloneMmPkg/Library/StandaloneMmCoreMemoryAllocationLib/StandaloneMmCoreMemoryAllocationLib.inf
   MmServicesTableLib|MdePkg/Library/StandaloneMmServicesTableLib/StandaloneMmServicesTableLib.inf
   PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
index 4fa426f58ef4..0a2e519dd664 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
@@ -30,6 +30,7 @@
   X64/StandaloneMmCoreEntryPoint.c
 
 [Packages]
+  EmbeddedPkg/EmbeddedPkg.dec
   MdePkg/MdePkg.dec
   MdeModulePkg/MdeModulePkg.dec
   StandaloneMmPkg/StandaloneMmPkg.dec
@@ -40,10 +41,12 @@
 [LibraryClasses]
   BaseLib
   DebugLib
+  FdtLib
 
 [LibraryClasses.AARCH64]
   StandaloneMmMmuLib
   ArmSvcLib
+  FdtLib
 
 [Guids]
   gMpInformationHobGuid
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
index 6c50f470aa35..cc09d75dac36 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
@@ -16,6 +16,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
 #include <Guid/MmramMemoryReserve.h>
 #include <Guid/MpInformation.h>
 
+#include <libfdt.h>
 #include <Library/ArmMmuLib.h>
 #include <Library/ArmSvcLib.h>
 #include <Library/DebugLib.h>
@@ -45,33 +46,31 @@ STATIC CONST UINT32 mSpmMinorVerFfa = SPM_MINOR_VERSION_FFA;
 PI_MM_ARM_TF_CPU_DRIVER_ENTRYPOINT      CpuDriverEntryPoint = NULL;
 
 /**
-  Retrieve a pointer to and print the boot information passed by privileged
-  secure firmware.
+  Prints boot information.
 
-  @param  [in] SharedBufAddress   The pointer memory shared with privileged
-                                  firmware.
+  This function prints the boot information, which is passed by privileged
+  secure firmware through shared buffer or other mechanism.
 
+  @param  [in] PayloadBootInfo   Pointer to StandaloneMM Boot Info structure.
 **/
-EFI_SECURE_PARTITION_BOOT_INFO *
-GetAndPrintBootinformation (
-  IN VOID                      *SharedBufAddress
+VOID
+PrintBootinformation (
+  IN EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo
 )
 {
-  EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
   EFI_SECURE_PARTITION_CPU_INFO  *PayloadCpuInfo;
   UINTN                          Index;
 
-  PayloadBootInfo = (EFI_SECURE_PARTITION_BOOT_INFO *) SharedBufAddress;
 
   if (PayloadBootInfo == NULL) {
     DEBUG ((DEBUG_ERROR, "PayloadBootInfo NULL\n"));
-    return NULL;
+    return;
   }
 
   if (PayloadBootInfo->Header.Version != BOOT_PAYLOAD_VERSION) {
     DEBUG ((DEBUG_ERROR, "Boot Information Version Mismatch. Current=0x%x, Expected=0x%x.\n",
             PayloadBootInfo->Header.Version, BOOT_PAYLOAD_VERSION));
-    return NULL;
+    return;
   }
 
   DEBUG ((DEBUG_INFO, "NumSpMemRegions - 0x%x\n", PayloadBootInfo->NumSpMemRegions));
@@ -96,7 +95,7 @@ GetAndPrintBootinformation (
 
   if (PayloadCpuInfo == NULL) {
     DEBUG ((DEBUG_ERROR, "PayloadCpuInfo NULL\n"));
-    return NULL;
+    return;
   }
 
   for (Index = 0; Index < PayloadBootInfo->NumCpus; Index++) {
@@ -105,7 +104,7 @@ GetAndPrintBootinformation (
     DEBUG ((DEBUG_INFO, "Flags           - 0x%x\n", PayloadCpuInfo[Index].Flags));
   }
 
-  return PayloadBootInfo;
+  return;
 }
 
 /**
@@ -194,6 +193,119 @@ DelegatedEventLoop (
   }
 }
 
+/**
+  Populates StandAloneMM boot information structure.
+
+  This function receives dtb Address, where StMM Boot information specific
+  properties will be looked out to form the booting structure of type
+  EFI_SECURE_PARTITION_BOOT_INFO. At first, the properties for StandAloneMM
+  ConfigSize and  Memory limit will be checked out. Boot information will
+  be stored at address (Memory Limit - ConfigSize). Thereafter all boot
+  information specific properties will be parsed and corresponding values
+  will be obtained.
+
+  @param  [out] BootInfo   Pointer, where Boot Info structure will be populated.
+  @param  [in] DtbAddress  Address of the Device tree from where Boot
+                           information will be fetched.
+**/
+VOID
+PopulateBootinformation (
+  OUT EFI_SECURE_PARTITION_BOOT_INFO **BootInfo,
+  IN VOID   *DtbAddress
+)
+{
+  INT32           Offset;
+  CONST UINT32    *Property;
+  CONST UINT64    *Property64;
+  UINT32          ConfigSize;
+  UINT64          SpMemLimit;
+  EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
+
+  Offset = fdt_node_offset_by_compatible (DtbAddress, -1, "config-size");
+  if (Offset < 0) {
+    DEBUG ((DEBUG_WARN, "Total Config Size is not  defined\n"));
+  } else {
+    Property = fdt_getprop (DtbAddress, Offset, "size", NULL);
+    if (Property) {
+      ConfigSize = fdt32_to_cpu (*Property);
+      DEBUG ((DEBUG_INFO, "stmm dtb config-size  = 0x%x \n", ConfigSize));
+    }
+  }
+
+  Offset = fdt_node_offset_by_compatible (DtbAddress, -1, "bootargs");
+  if (Offset >= 0) {
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_mem_limit", NULL);
+    SpMemLimit = fdt64_to_cpu (*Property64);
+  }
+
+  if (SpMemLimit && ConfigSize)
+    PayloadBootInfo =
+      (EFI_SECURE_PARTITION_BOOT_INFO *)(SpMemLimit - ConfigSize);
+
+  if (PayloadBootInfo) {
+    PayloadBootInfo->SpMemLimit = SpMemLimit;
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_type", NULL);
+    PayloadBootInfo->Header.Type = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_version", NULL);
+    PayloadBootInfo->Header.Version = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_size", NULL);
+    PayloadBootInfo->Header.Size = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_attr", NULL);
+    PayloadBootInfo->Header.Attr = fdt32_to_cpu(*Property);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_mem_base", NULL);
+    PayloadBootInfo->SpMemBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_image_base", NULL);
+    PayloadBootInfo->SpImageBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_stack_base", NULL);
+    PayloadBootInfo->SpStackBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_heap_base", NULL);
+    PayloadBootInfo->SpHeapBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_base", NULL);
+    PayloadBootInfo->SpNsCommBufBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_shared_buf_base", NULL);
+    PayloadBootInfo->SpSharedBufBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_image_size", NULL);
+    PayloadBootInfo->SpImageSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_pcpu_stack_size", NULL);
+    PayloadBootInfo->SpPcpuStackSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_heap_size", NULL);
+    PayloadBootInfo->SpHeapSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_size", NULL);
+    PayloadBootInfo->SpNsCommBufSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_shared_buf_size", NULL);
+    PayloadBootInfo->SpPcpuSharedBufSize = fdt64_to_cpu(*Property64);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "num_sp_mem_regions", NULL);
+    PayloadBootInfo->NumSpMemRegions = fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "num_cpus", NULL);
+    PayloadBootInfo->NumCpus = fdt32_to_cpu(*Property);
+
+    PayloadBootInfo->CpuInfo =
+      (EFI_SECURE_PARTITION_CPU_INFO *)((UINT64)PayloadBootInfo +
+                                        sizeof(EFI_SECURE_PARTITION_BOOT_INFO));
+  }
+
+  *BootInfo = PayloadBootInfo;
+
+  return;
+}
+
 /**
   Query the SPM version, check compatibility and return success if compatible.
 
@@ -313,6 +425,7 @@ _ModuleEntryPoint (
   VOID                                    *TeData;
   UINTN                                   TeDataSize;
   EFI_PHYSICAL_ADDRESS                    ImageBase;
+  VOID                                    *DtbAddress;
 
   // Get Secure Partition Manager Version Information
   Status = GetSpmVersion ();
@@ -320,12 +433,24 @@ _ModuleEntryPoint (
     goto finish;
   }
 
-  PayloadBootInfo = GetAndPrintBootinformation (SharedBufAddress);
+  // In cookie1 the DTB address is passed. With reference to DTB, Boot
+  // info structure can be populated.
+  // If cookie1 doesn't have any value, then Boot info is copied from
+  // Sharedbuffer.
+  if (cookie1) {
+    DtbAddress = (void *)cookie1;
+    PopulateBootinformation (&PayloadBootInfo, DtbAddress);
+  } else {
+    PayloadBootInfo = (EFI_SECURE_PARTITION_BOOT_INFO *)SharedBufAddress;
+  }
+
   if (PayloadBootInfo == NULL) {
     Status = EFI_UNSUPPORTED;
     goto finish;
   }
 
+  PrintBootinformation (PayloadBootInfo);
+
   // Locate PE/COFF File information for the Standalone MM core module
   Status = LocateStandaloneMmCorePeCoffData (
              (EFI_FIRMWARE_VOLUME_HEADER *) PayloadBootInfo->SpImageBase,
-- 
2.17.1



-=-=-=-=-=-=
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#78449): https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Fmessage%2F78449&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=0DbkyBJe0prgx2vSCVVsrI0GoVbCipcfmbZp83LREjo%3D&amp;reserved=0
Mute This Topic: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgroups.io%2Fmt%2F84555304%2F1852292&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=1u3cr0MmJmjrkxtGgfOe1mb8%2F7vUXaoyUr5%2FmC1Uq%2F8%3D&amp;reserved=0
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Funsub&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786142135%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=Yr%2BSNqWHllfxHkFqBhCyP2TE4IGRLFLm%2FCCWexjB0AM%3D&amp;reserved=0 [bret.barkelew@...]
-=-=-=-=-=-=

 


Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Taylor Beebe
 

I am going to extend the opportunity for feedback to the end of Tuesday. I would very much like more community input on this proposal.

On 7/30/2021 11:42 AM, Sean Brogan wrote:
Jiewen,
**Slight rant**
I agree with libraries as an effective abstraction method.  But I think there needs to be a broad discussion about the order of preference for methods of abstraction.  Today the edk2 code base is a mix and often there are numerous methods abstracting the same thing which leads to confusion, misconfiguration, and error.
In the UEFI specification we have PPIs/Protocols/Events for functional abstraction.  We have variables, guided config tables, and HII for data abstraction.
In the PI specification we add HOBs and PCDs for data abstractions.
Finally, in EDKII we add the library class concept and leverage it heavily for arch, phase, and platform/behavioral abstractions.
Without clear guidance for how and when to use the above it is hard to keep code being developed by the larger community consistent.
**End**
I was leaning towards something closer to

>> Option 1:
https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib_2
the HOB method and internally as we develop more code we are preferring HOB and data abstractions more than functional abstraction.  Data abstractions can be used to control functional differences as well if needed.  Data abstractions allow for easier validation and support diverse code environments.  For example standalone MM and payloadpkg/payload concepts.  Finally, data abstractions break the need for a monolithic code base.   But as you can see in option 1 it actually uses a library class abstraction as well because no one wants to write the same code over and over again to get the HOB.  The contract of the library is just data but it still requires library mappings.  Maybe these types of libraries need to be treated differently.
Anyway it would be great to hear from other members of the community around not just the memory protections RFC (this RFC) but around preferences for abstraction techniques (pro/con).  If an actual discussion starts it could move to design meeting.
Thanks
Sean
On 7/29/2021 7:34 PM, Yao, Jiewen wrote:
Thanks. Code talks better.

I prefer option 2, which is a generic way for abstraction.

And you may enable option 1 under the cover of option 2, just create a lib instance to get config from Hob.

Thank you
Yao Jiewen

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 30, 2021 10:07 AM
To: Yao, Jiewen <jiewen.yao@intel.com>; Wang, Jian J <jian.j.wang@intel.com>;
devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <rahul1.kumar@intel.com>;
mikuback@linux.microsoft.com; Wu, Hao A <hao.a.wu@intel.com>; Bi, Dandan
<dandan.bi@intel.com>; gaoliming@byosoft.com.cn; Dong, Guo
<guo.dong@intel.com>; Ma, Maurice <maurice.ma@intel.com>; You, Benjamin
<benjamin.you@intel.com>
Subject: Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Of course - here are a couple of rough drafts:

Option 1: https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib_2
Option 2: https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib

On 7/29/2021 6:57 PM, Yao, Jiewen wrote:
Hi
Sorry, I am not able to follow the discussion.

Is there any sample or POC code to show the concept?

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 30, 2021 9:55 AM
To: Wang, Jian J <jian.j.wang@intel.com>; devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <rahul1.kumar@intel.com>;
mikuback@linux.microsoft.com; Wu, Hao A <hao.a.wu@intel.com>; Bi,
Dandan
<dandan.bi@intel.com>; gaoliming@byosoft.com.cn; Dong, Guo
<guo.dong@intel.com>; Ma, Maurice <maurice.ma@intel.com>; You,
Benjamin
<benjamin.you@intel.com>; Yao, Jiewen <jiewen.yao@intel.com>
Subject: Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Thanks for your feedback, Jian.

In option 2, a most basic implementation would returning the current
FixedAtBuild PCDs assuming they are kept. If they aren't, the library
implementer could simply hard-code the return value for each memory
protection setting.

In option 1, the HOB would be published in pre-mem and I'm not an expert
on exploiting the pre-mem environment. Jiewen may have more to say on
this.

-Taylor

On 7/28/2021 7:18 PM, Wang, Jian J wrote:
Thanks for the RFC. I'm not object to this idea. The only concern from me
is the potential security holes introduced by the changes. According to your
description, it allows 3rd party software to violate memory protection
policy.
I'd like to see more explanations on how to avoid it to be exploited.

+Jiewen, what's current process to evaluate the security threat?

Regards,
Jian

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 23, 2021 8:33 AM
To: devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <Rahul1.Kumar@intel.com>;
mikuback@linux.microsoft.com; Wang, Jian J <jian.j.wang@intel.com>;
Wu,
Hao A <hao.a.wu@intel.com>; Bi, Dandan <dandan.bi@intel.com>;
gaoliming@byosoft.com.cn; Dong, Guo <guo.dong@intel.com>; Ma,
Maurice
<maurice.ma@intel.com>; You, Benjamin <benjamin.you@intel.com>
Subject: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Current memory protection settings rely on FixedAtBuild PCD values
(minus PcdSetNxForStack). Because of this, the memory protection
configuration interface is fixed in nature. Cases arise in which memory
protections might need to be adjusted between boots (if platform design
allows) to avoid disabling a system. For example, platforms might choose
to allow the user to control their protection policies such as allow
execution of critical 3rd party software that might violate memory
protections.

This RFC seeks your feedback regarding introducing an interface that
allows dynamic configuration of memory protection settings.

I would like to propose two options:
1. Describing the memory protection setting configuration in a HOB that
is produced by the platform.
2. Introducing a library class (e.g. MemoryProtectionLib) that allows
abstraction of the memory protection setting configuration data source.

In addition, I would like to know if the memory protection FixedAtBuild
PCDs currently in MdeModulePkg can be removed so we can move the
configuration interface entirely to an option above.

In any case, I would like the settings to be visible to environments
such as Standalone MM where dynamic PCDs are not accessible.

I am seeking your feedback on this proposal in preparation for sending
an edk2 patch series.

--
Taylor Beebe
Software Engineer @ Microsoft
--
Taylor Beebe
Software Engineer @ Microsoft
--
Taylor Beebe
Software Engineer @ Microsoft


--
Taylor Beebe
Software Engineer @ Microsoft


Re: edk2 memory map on QEMU

Stuart Yoder
 

On 7/30/21 3:36 PM, Andrew Fish wrote:

On Jul 30, 2021, at 12:32 PM, Stuart Yoder <stuart.yoder@arm.com <mailto:stuart.yoder@arm.com>> wrote:

I am playing around with EDK2 on QEMU with a UEFI shell application and in the app I allocate some memory using gBS->AllocatePool(EfiBootServicesData, ...)

Programmatically accessing the pointer returned works fine, but when I print it, it does not seem to be what I would expect is a valid address.

I've allocated 4GB to the QEMU machine, which I believe starts at 0x40000000.
You can run the `memmap` command at the EFI Shell to see the layout.

But, when I print the address returned by AllocatePool the value is "0x39177018".
Print != printf on some of the format string so be careful about that….
I figured it out. It was a dumb Print() formatting error that resulted in
the address getting truncated to 8 bytes.

Thanks,
Stuart


Re: EmulatorPkg and the state of DlLoadImage()

Andrew Fish
 



On Jul 30, 2021, at 1:34 PM, Marvin Häuser <mhaeuser@...> wrote:

30.07.2021 17:58:05 Andrew Fish via groups.io <afish@...>:



On Jul 30, 2021, at 3:37 AM, Marvin Häuser <mhaeuser@...> wrote:

Good day everyone,

I'm currently refining the port of EmulatorPkg to my new PE/COFF loader library instance.
In the process, I found the function DlOpenImage() [1], which loads UEFI Images via the OS loader to utilise its symbol loading capability. Theoretically, this should e.g. allow arbitrary debuggers using the OS APIs to symbolise the backtrace.

macOS: The function seems to be unused entirely. [2]

Linux: On my system running Fedora 34, the function neither works out-of-the-box, nor after significant time of trying to fix it. The first issue is that it only proceeds if the Image has a PDB path with ".pdb" extension [3], while the GCC5 toolchain generates Images with ".dll" files for PDB paths (see errors below). Once this is resolved, there is an error message indicating insufficient Image section alignment:

[...]/Build/EmulatorX64/DEBUG_GCC5/X64/MdeModulePkg/Universal/EbcDxe/EbcDxe/DEBUG/EbcDxe.dll: ELF load command alignment not page-aligned


The requiring *.pdb seems like something that rotted out and could be fixed. 

OK, yes.

Resolving this yields an error that executable files cannot be loaded dynamically:

[...]/Build/EmulatorX64/DEBUG_GCC5/X64/MdeModulePkg/Core/Pei/PeiMain/DEBUG/PeiCore.dll: cannot dynamically load executable

With my very limited knowledge about Linux and ELF I tried the naive approach of building the Images as shared (hoping it would be similar to DLLs, which are built on Windows), but this just silently crashes.


This code is very very old. Notice the comment about gdb predates gdb Python support [1].

Right, that is why I wondered, whether it is still needed.
What happens if you comment out the DlLoadImage path? There seems to be some gdb scripts?

It already is pretty much no-op on my system. On break, the debug code writes the symbol file paths into a file (Host.gdb) and a gdb script loads them. With gdb, this code seems to not be needed at all.

The macOS path sets breakpoints on SecGdbScriptBreak() in an lldb script and loads symbols via that path. That his probably the best path forward for gdb too? 
It looks like if you `build.sh run` you should launch the emulator under gdb and source the symbol loading file.
EmulatorPkg/build.sh:221:  /usr/bin/gdb $BUILD_ROOT_ARCH/Host -q -cd=$BUILD_ROOT_ARCH -x $WORKSPACE/EmulatorPkg/Unix/*GdbRun.sh*

If you comment out the dlopen() path does it start working? Looks like breaking in with gdb should get symbols loaded? 

Yes, it works, even without uncommenting. I'm not experienced with gdb enough to know how it should work, but I could help with a better solution if such is needed later. For now, I'd like to figure out whether dlopen() is legacy code nobody uses or needs first.


Marvin,

Sorry I started looking at the code and forgot to give the history. The dlopen() was a trick copied from the Windows port. Basically an EFI image is loaded into EFI memory and it is also loaded into the application memory outside of EFI vis the dlopen() and the entry point is returned to be the dlopen() one. This fakes EFI out enough to be happy, and since the EFI code was loaded via dlopen() symbols get loaded automatically. So the only purpose of the dlopen is automatic source level debugging without requiring any debugger scripting. 

The gdb/lldb scripts have magic to tell the debugger the location things EFI got loaded, so you don’t need the dlopen() to load symbols. Basically not using the dlopen is a more realistic simulation of EFI. 


So my questions are:
1) Does this code currently work for anyone?
2) Does anyone use a debugging setup that is incompatible with Images loaded by EDK II rather than the OS?

Not a 100% sure what you are asking?

Sorry if it was unclear, "loaded by EDK II" refers to Images loaded without dlopen() and "loaded by the OS" to Images loaded with dlopen().


Hopefully the above history clears that up.

In a lot of cases you are debugging what is compatible with the OS? For example on macOS we build a mach-O and convert that to PE/COFF. We point the PDB entry at the mach-O file and that is what the debugger sees. As long as the PE/COFF lines up with the mach-O it does not really matter, as at the end of the day the debugger is just processing the dwarf debug info associated with addresses in system memory. 
3) Are the issues above known and planned to be fixed?


Not likely please file a BZ. 

OK, will do soon.


Note I’m working on getting a generic gdb debugging script into the edk2 [2] and that should also work with the Emulator. I think you could replace the ` -x $WORKSPACE/EmulatorPkg/Unix/GdbRun.sh` with `-ex efi_gdb.py’. There is not a break hook in those scripts so you would have to run the `efi` command the 1st time you attach to load symbols. The efi_gdb.py script works on stock EFI so it does not depend on any of the hooks in the EmulatorPkg to work. 

I'm not good with Python, so will have to check. :)

I was forced to learn Python to figure about debugging scripts :)

Thanks,

Andrew Fish


Best regards,
Marvin


Thank you for your time!

Best regards,
Marvin


[1]
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1065-L1113

[2]
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1071-L1073

[3]
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1084-L1086
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1003-L1026


[1] https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1179
[2] https://github.com/ajfish/edk2/blob/BZ3500-gdb/efi_gdb.py

Thanks,

Andrew Fish





Re: edk2 memory map on QEMU

Andrew Fish
 



On Jul 30, 2021, at 12:32 PM, Stuart Yoder <stuart.yoder@...> wrote:

I am playing around with EDK2 on QEMU with a UEFI shell application and in the app I allocate some memory using gBS->AllocatePool(EfiBootServicesData, ...)

Programmatically accessing the pointer returned works fine, but when I print it, it does not seem to be what I would expect is a valid address.

I've allocated 4GB to the QEMU machine, which I believe starts at 0x40000000.


You can run the `memmap` command at the EFI Shell to see the layout. 

But, when I print the address returned by AllocatePool the value is "0x39177018".


Print != printf on some of the format string so be careful about that….

Thanks,

Andrew Fish

I thought that all memory was identity mapped where phys=virt, so not sure where the 0x39177018 is coming from.  Trying to dump 0x39177018 from the QEMU console or GDB results in a bad address error.
Thanks,
Stuart


Re: EmulatorPkg and the state of DlLoadImage()

Marvin Häuser
 

30.07.2021 17:58:05 Andrew Fish via groups.io <afish=apple.com@groups.io>:



On Jul 30, 2021, at 3:37 AM, Marvin Häuser <mhaeuser@posteo.de> wrote:

Good day everyone,

I'm currently refining the port of EmulatorPkg to my new PE/COFF loader library instance.
In the process, I found the function DlOpenImage() [1], which loads UEFI Images via the OS loader to utilise its symbol loading capability. Theoretically, this should e.g. allow arbitrary debuggers using the OS APIs to symbolise the backtrace.

macOS: The function seems to be unused entirely. [2]

Linux: On my system running Fedora 34, the function neither works out-of-the-box, nor after significant time of trying to fix it. The first issue is that it only proceeds if the Image has a PDB path with ".pdb" extension [3], while the GCC5 toolchain generates Images with ".dll" files for PDB paths (see errors below). Once this is resolved, there is an error message indicating insufficient Image section alignment:

[...]/Build/EmulatorX64/DEBUG_GCC5/X64/MdeModulePkg/Universal/EbcDxe/EbcDxe/DEBUG/EbcDxe.dll: ELF load command alignment not page-aligned
The requiring *.pdb seems like something that rotted out and could be fixed.
OK, yes.

Resolving this yields an error that executable files cannot be loaded dynamically:

[...]/Build/EmulatorX64/DEBUG_GCC5/X64/MdeModulePkg/Core/Pei/PeiMain/DEBUG/PeiCore.dll: cannot dynamically load executable

With my very limited knowledge about Linux and ELF I tried the naive approach of building the Images as shared (hoping it would be similar to DLLs, which are built on Windows), but this just silently crashes.
This code is very very old. Notice the comment about gdb predates gdb Python support [1].
Right, that is why I wondered, whether it is still needed.
What happens if you comment out the DlLoadImage path? There seems to be some gdb scripts?
It already is pretty much no-op on my system. On break, the debug code writes the symbol file paths into a file (Host.gdb) and a gdb script loads them. With gdb, this code seems to not be needed at all.

The macOS path sets breakpoints on SecGdbScriptBreak() in an lldb script and loads symbols via that path. That his probably the best path forward for gdb too?
It looks like if you `build.sh run` you should launch the emulator under gdb and source the symbol loading file.
EmulatorPkg/build.sh:221:  /usr/bin/gdb $BUILD_ROOT_ARCH/Host -q -cd=$BUILD_ROOT_ARCH -x $WORKSPACE/EmulatorPkg/Unix/*GdbRun.sh*

If you comment out the dlopen() path does it start working? Looks like breaking in with gdb should get symbols loaded?
Yes, it works, even without uncommenting. I'm not experienced with gdb enough to know how it should work, but I could help with a better solution if such is needed later. For now, I'd like to figure out whether dlopen() is legacy code nobody uses or needs first.


So my questions are:
1) Does this code currently work for anyone?
2) Does anyone use a debugging setup that is incompatible with Images loaded by EDK II rather than the OS?
Not a 100% sure what you are asking?
Sorry if it was unclear, "loaded by EDK II" refers to Images loaded without dlopen() and "loaded by the OS" to Images loaded with dlopen().

In a lot of cases you are debugging what is compatible with the OS? For example on macOS we build a mach-O and convert that to PE/COFF. We point the PDB entry at the mach-O file and that is what the debugger sees. As long as the PE/COFF lines up with the mach-O it does not really matter, as at the end of the day the debugger is just processing the dwarf debug info associated with addresses in system memory.
3) Are the issues above known and planned to be fixed?
Not likely please file a BZ.
OK, will do soon.


Note I’m working on getting a generic gdb debugging script into the edk2 [2] and that should also work with the Emulator. I think you could replace the ` -x $WORKSPACE/EmulatorPkg/Unix/GdbRun.sh` with `-ex efi_gdb.py’. There is not a break hook in those scripts so you would have to run the `efi` command the 1st time you attach to load symbols. The efi_gdb.py script works on stock EFI so it does not depend on any of the hooks in the EmulatorPkg to work.
I'm not good with Python, so will have to check. :)

Best regards,
Marvin


Thank you for your time!

Best regards,
Marvin


[1]
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1065-L1113

[2]
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1071-L1073

[3]
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1084-L1086
https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1003-L1026
[1] https://github.com/tianocore/edk2/blob/be282b14938846960cce30825a9fe762e14ca8c9/EmulatorPkg/Unix/Host/Host.c#L1179
[2] https://github.com/ajfish/edk2/blob/BZ3500-gdb/efi_gdb.py

Thanks,

Andrew Fish


edk2 memory map on QEMU

Stuart Yoder
 

I am playing around with EDK2 on QEMU with a UEFI shell application and in the app I allocate some memory using gBS->AllocatePool(EfiBootServicesData, ...)

Programmatically accessing the pointer returned works fine, but when I print it, it does not seem to be what I would expect is a valid address.

I've allocated 4GB to the QEMU machine, which I believe starts at 0x40000000.

But, when I print the address returned by AllocatePool the value is "0x39177018".

I thought that all memory was identity mapped where phys=virt, so not sure where the 0x39177018 is coming from.  Trying to dump 0x39177018 from the QEMU console or GDB results in a bad address error.

Thanks,
Stuart


Re: [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver.

Marvin Häuser
 

Hey Pedro,

Sorry, I went through this in a great rush, but I wanted to get some feedback out asap to have enough time for discussion till end of GSoC.
Some comments are inline.

Best regards,
Marvin

On 30.07.21 18:16, Pedro Falcato wrote:
Adds a UEFI EXT4 filesystem driver that implements the EFI_FILE_PROTOCOL
and EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.

Cc: Leif Lindholm <leif@nuviainc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>

Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com>
---
Features/Ext4Pkg/Ext4Dxe/BlockGroup.c | 208 ++++++
Features/Ext4Pkg/Ext4Dxe/Collation.c | 157 +++++
Features/Ext4Pkg/Ext4Dxe/Crc16.c | 75 ++
Features/Ext4Pkg/Ext4Dxe/Crc32c.c | 84 +++
Features/Ext4Pkg/Ext4Dxe/Directory.c | 492 ++++++++++++++
Features/Ext4Pkg/Ext4Dxe/DiskUtil.c | 83 +++
Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h | 450 ++++++++++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c | 454 +++++++++++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h | 942 ++++++++++++++++++++++++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf | 147 ++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni | 15 +
Features/Ext4Pkg/Ext4Dxe/Extents.c | 616 +++++++++++++++++
Features/Ext4Pkg/Ext4Dxe/File.c | 583 ++++++++++++++++
Features/Ext4Pkg/Ext4Dxe/Inode.c | 468 +++++++++++++
Features/Ext4Pkg/Ext4Dxe/Partition.c | 120 ++++
Features/Ext4Pkg/Ext4Dxe/Superblock.c | 257 +++++++
16 files changed, 5151 insertions(+)
create mode 100644 Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Collation.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Crc16.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Crc32c.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Directory.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Extents.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/File.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Inode.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Partition.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Superblock.c

diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
new file mode 100644
index 0000000000..10a82d40a0
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
@@ -0,0 +1,208 @@
+/**
+ @file Block group related routines
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+/**
+ Reads an inode from disk.
+
+ @param[in] Partition Pointer to the opened partition.
+ @param[in] InodeNum Number of the desired Inode
+ @param[out] OutIno Pointer to where it will be stored a pointer to the read inode.
+
+ @return Status of the inode read.
+ */
+EFI_STATUS
+Ext4ReadInode (
+ IN EXT4_PARTITION *Partition, IN EXT4_INO_NR InodeNum, OUT EXT4_INODE **OutIno
+ )
+{
+ UINT64 InodeOffset;
+ UINT32 BlockGroupNumber;
+ EXT4_INODE *Inode;
+ EXT4_BLOCK_GROUP_DESC *BlockGroup;
+ EXT4_BLOCK_NR InodeTableStart;
+ EFI_STATUS Status;
+
+ BlockGroupNumber = (UINT32)DivU64x64Remainder (
+ InodeNum - 1,
+ Partition->SuperBlock.s_inodes_per_group,
+ &InodeOffset
+ );
+
+ // Check for the block group number's correctness
+ if (BlockGroupNumber >= Partition->NumberBlockGroups) {
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ Inode = Ext4AllocateInode (Partition);
+
+ if (Inode == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ BlockGroup = Ext4GetBlockGroupDesc (Partition, BlockGroupNumber);
+
+ // Note: We'll need to check INODE_UNINIT and friends when we add write support
+
+ InodeTableStart = Ext4MakeBlockNumberFromHalfs (
+ Partition,
+ BlockGroup->bg_inode_table_lo,
+ BlockGroup->bg_inode_table_hi
+ );
+
+ Status = Ext4ReadDiskIo (
+ Partition,
+ Inode,
+ Partition->InodeSize,
+ Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOffset * Partition->InodeSize
+ );
+
+ if (EFI_ERROR (Status)) {
+ DEBUG ((
+ EFI_D_ERROR,
+ "[ext4] Error reading inode: status %x; inode offset %lx"
+ " inode table start %lu block group %lu\n",
+ Status,
+ InodeOffset,
+ InodeTableStart,
+ BlockGroupNumber
+ ));
+ FreePool (Inode);
+ return Status;
+ }
+
+ if (!Ext4CheckInodeChecksum (Partition, Inode, InodeNum)) {
+ DEBUG ((
+ EFI_D_ERROR,
+ "[ext4] Inode %llu has invalid checksum (calculated %x)\n",
+ InodeNum,
+ Ext4CalculateInodeChecksum (Partition, Inode, InodeNum)
+ ));
+ FreePool (Inode);
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ *OutIno = Inode;
+ return EFI_SUCCESS;
+}
+
+/**
+ Calculates the checksum of the block group descriptor for METADATA_CSUM enabled filesystems.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.
+ @param[in] BlockGroupNum Number of the block group.
+
+ @return The checksum.
+*/
+STATIC
+UINT16
+Ext4CalculateBlockGroupDescChecksumMetadataCsum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
+ IN UINT32 BlockGroupNum
+ )
+{
+ UINT32 Csum;
+ UINT16 Dummy;
+
+ Dummy = 0;
+
+ Csum = Ext4CalculateChecksum (Partition, &BlockGroupNum, sizeof (BlockGroupNum), Partition->InitialSeed);
+ Csum = Ext4CalculateChecksum (Partition, BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
+ Csum = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Csum);
+ Csum =
+ Ext4CalculateChecksum (
+ Partition,
+ &BlockGroupDesc->bg_block_bitmap_hi,
+ Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bitmap_hi),
+ Csum
+ );
+ return (UINT16)Csum;
+}
+
+/**
+ Calculates the checksum of the block group descriptor for GDT_CSUM enabled filesystems.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.
+ @param[in] BlockGroupNum Number of the block group.
+
+ @return The checksum.
+*/
+STATIC
+UINT16
+Ext4CalculateBlockGroupDescChecksumGdtCsum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
+ IN UINT32 BlockGroupNum
+ )
+{
+ UINT16 Csum;
+ UINT16 Dummy;
+
+ Dummy = 0;
+
+ Csum = CalculateCrc16 (Partition->SuperBlock.s_uuid, 16, 0);
+ Csum = CalculateCrc16 (&BlockGroupNum, sizeof (BlockGroupNum), Csum);
+ Csum = CalculateCrc16 (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
+ Csum = CalculateCrc16 (&Dummy, sizeof (Dummy), Csum);
+ Csum =
+ CalculateCrc16 (
+ &BlockGroupDesc->bg_block_bitmap_hi,
+ Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bitmap_hi),
+ Csum
+ );
+ return Csum;
+}
+
+/**
+ Checks if the checksum of the block group descriptor is correct.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.
+ @param[in] BlockGroupNum Number of the block group.
+
+ @return TRUE if checksum is correct, FALSE if there is corruption.
+*/
+BOOLEAN
+Ext4VerifyBlockGroupDescChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
+ IN UINT32 BlockGroupNum
+ )
+{
+ if(!Ext4HasMetadataCsum (Partition) && !Ext4HasGdtCsum (Partition)) {
+ return TRUE;
+ }
+
+ return Ext4CalculateBlockGroupDescChecksum (Partition, BlockGroupDesc, BlockGroupNum) == BlockGroupDesc->bg_checksum;
+}
+
+/**
+ Calculates the checksum of the block group descriptor.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.
+ @param[in] BlockGroupNum Number of the block group.
+
+ @return The checksum.
+*/
+UINT16
+Ext4CalculateBlockGroupDescChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
+ IN UINT32 BlockGroupNum
+ )
+{
+ if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) {
+ return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, BlockGroupDesc, BlockGroupNum);
+ } else if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_GDT_CSUM) {
+ return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGroupDesc, BlockGroupNum);
+ }
+
+ return 0;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Collation.c b/Features/Ext4Pkg/Ext4Dxe/Collation.c
new file mode 100644
index 0000000000..92d6a9184b
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Collation.c
@@ -0,0 +1,157 @@
+/**
+ @file Unicode collation routines
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ Copyright (c) 2005 - 2017, Intel Corporation. All rights reserved.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include <Uefi.h>
+
+#include <Library/UefiLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/MemoryAllocationLib.h>
+
+#include <Protocol/UnicodeCollation.h>
+
+STATIC EFI_UNICODE_COLLATION_PROTOCOL *gUnicodeCollationInterface = NULL;
+
+/*
+ * Note: This code is heavily based on FatPkg's Unicode collation, since they seem to know what
+ * they're doing.
+ * PS: Maybe all this code could be put in a library? It looks heavily shareable.
+ */
+STATIC
+EFI_STATUS
+Ext4InitialiseUnicodeCollationInternal (
+ IN EFI_HANDLE DriverHandle,
+ IN EFI_GUID *ProtocolGuid,
+ IN CONST CHAR16 *VariableName,
+ IN CONST CHAR8 *DefaultLanguage
+ )
+{
+ UINTN NumHandles;
+ EFI_HANDLE *Handles;
+ EFI_UNICODE_COLLATION_PROTOCOL *Uci;
+ BOOLEAN Iso639Language;
+ CHAR8 *Language;
+ EFI_STATUS RetStatus;
+ EFI_STATUS Status;
+
+ Iso639Language = (BOOLEAN)(ProtocolGuid == &gEfiUnicodeCollationProtocolGuid);
+ RetStatus = EFI_UNSUPPORTED;
+ GetEfiGlobalVariable2 (VariableName, (VOID **)&Language, NULL);
I know that this is done across EDK II drivers, however please do not discard return results and rely on the function initialising all parameters. C code traditionally is driven by returned results, i.e. the function succeeded if and only if the returned status indicates success. There are many reasons not to rely on this behaviour, most importantly:
1) A future library class instance may not unconditionally initialise the argument pointers. The function documentation has no mention of this behaviour, and it is awkward.
2) The callee may pass the parameters down to other functions and accidentally initialise them with non-trivial values, making it look like the function has succeeded when not considering the returned status.
3) This is basically 2.1, the functions must all follow this pattern down the call tree (or workarounds need to be implemented), which wastes resources.

+
+ Status = gBS->LocateHandleBuffer (
+ ByProtocol,
+ ProtocolGuid,
+ NULL,
+ &NumHandles,
+ &Handles
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ // Note: FatPkg also doesn't close unneeded protocols.
+ // This looks like a leak but I'm likely wrong.
I'm not 100 % sure what you mean here, but in case you refer to below's code, protocols retrieved by EFI_OPEN_PROTOCOL_GET_PROTOCOL do not need to be closed as per UEFI 2.9, page 194.
+ for(UINTN i = 0; i < NumHandles; i++) {
+ Status = gBS->OpenProtocol (
+ Handles[i],
+ ProtocolGuid,
+ (VOID **)&Uci,
+ DriverHandle,
+ NULL,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL
+ );
+
+ if(EFI_ERROR (Status)) {
+ continue;
+ }
+
+ CHAR8 *BestLanguage = GetBestLanguage (
+ Uci->SupportedLanguages,
+ Iso639Language,
+ (Language == NULL) ? "" : Language,
+ DefaultLanguage,
+ NULL
+ );
+ if (BestLanguage != NULL) {
+ FreePool (BestLanguage);
+ gUnicodeCollationInterface = Uci;
+ RetStatus = EFI_SUCCESS;
+ break;
+ }
+ }
+
+ if (Language != NULL) {
+ FreePool (Language);
+ }
+
+ FreePool (Handles);
+ return RetStatus;
+}
+
+/**
+ Initialises Unicode collation, which is needed for case-insensitive string comparisons
+ within the driver (a good example of an application of this is filename comparison).
+
+ @param[in] DriverHandle Handle to the driver image.
+
+ @retval EFI_SUCCESS Unicode collation was successfully initialised.
+ @retval !EFI_SUCCESS Failure.
+*/
+EFI_STATUS
+Ext4InitialiseUnicodeCollation (
+ EFI_HANDLE DriverHandle
+ )
+{
+ EFI_STATUS Status;
+
+ Status = EFI_UNSUPPORTED;
+
+ //
+ // First try to use RFC 4646 Unicode Collation 2 Protocol.
+ //
+ Status = Ext4InitialiseUnicodeCollationInternal (
+ DriverHandle,
+ &gEfiUnicodeCollation2ProtocolGuid,
+ L"PlatformLang",
+ (CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultPlatformLang)
+ );
+ //
+ // If the attempt to use Unicode Collation 2 Protocol fails, then we fall back
+ // on the ISO 639-2 Unicode Collation Protocol.
+ //
+ if (EFI_ERROR (Status)) {
+ Status = Ext4InitialiseUnicodeCollationInternal (
+ DriverHandle,
+ &gEfiUnicodeCollationProtocolGuid,
+ L"Lang",
+ (CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultLang)
+ );
+ }
+
+ return Status;
+}
+
+/**
+ Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATION_PROTOCOL's StriColl
+ for more details.
+
+ @param[in] Str1 Pointer to a null terminated string.
+ @param[in] Str2 Pointer to a null terminated string.
+
+ @retval 0 Str1 is equivalent to Str2.
+ @retval >0 Str1 is lexically greater than Str2.
+ @retval <0 Str1 is lexically less than Str2.
+*/
+INTN
+Ext4StrCmpInsensitive (
+ IN CHAR16 *Str1,
+ IN CHAR16 *Str2
+ )
+{
+ return gUnicodeCollationInterface->StriColl (gUnicodeCollationInterface, Str1, Str2);
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc16.c b/Features/Ext4Pkg/Ext4Dxe/Crc16.c
new file mode 100644
index 0000000000..25a11cfde3
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Crc16.c
@@ -0,0 +1,75 @@
+/**
+ @file CRC16 calculation routines.
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include <Uefi.h>
+
+STATIC CONST UINT16 gCrc16LookupTable[256] =
+{
+ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+ 0x0919, 0x1890, 0x2a0b, 0x3b82, 0x4f3d, 0x5eb4, 0x6c2f, 0x7da6,
+ 0x8551, 0x94d8, 0xa643, 0xb7ca, 0xc375, 0xd2fc, 0xe067, 0xf1ee,
+ 0x1232, 0x03bb, 0x3120, 0x20a9, 0x5416, 0x459f, 0x7704, 0x668d,
+ 0x9e7a, 0x8ff3, 0xbd68, 0xace1, 0xd85e, 0xc9d7, 0xfb4c, 0xeac5,
+ 0x1b2b, 0x0aa2, 0x3839, 0x29b0, 0x5d0f, 0x4c86, 0x7e1d, 0x6f94,
+ 0x9763, 0x86ea, 0xb471, 0xa5f8, 0xd147, 0xc0ce, 0xf255, 0xe3dc,
+ 0x2464, 0x35ed, 0x0776, 0x16ff, 0x6240, 0x73c9, 0x4152, 0x50db,
+ 0xa82c, 0xb9a5, 0x8b3e, 0x9ab7, 0xee08, 0xff81, 0xcd1a, 0xdc93,
+ 0x2d7d, 0x3cf4, 0x0e6f, 0x1fe6, 0x6b59, 0x7ad0, 0x484b, 0x59c2,
+ 0xa135, 0xb0bc, 0x8227, 0x93ae, 0xe711, 0xf698, 0xc403, 0xd58a,
+ 0x3656, 0x27df, 0x1544, 0x04cd, 0x7072, 0x61fb, 0x5360, 0x42e9,
+ 0xba1e, 0xab97, 0x990c, 0x8885, 0xfc3a, 0xedb3, 0xdf28, 0xcea1,
+ 0x3f4f, 0x2ec6, 0x1c5d, 0x0dd4, 0x796b, 0x68e2, 0x5a79, 0x4bf0,
+ 0xb307, 0xa28e, 0x9015, 0x819c, 0xf523, 0xe4aa, 0xd631, 0xc7b8,
+ 0x48c8, 0x5941, 0x6bda, 0x7a53, 0x0eec, 0x1f65, 0x2dfe, 0x3c77,
+ 0xc480, 0xd509, 0xe792, 0xf61b, 0x82a4, 0x932d, 0xa1b6, 0xb03f,
+ 0x41d1, 0x5058, 0x62c3, 0x734a, 0x07f5, 0x167c, 0x24e7, 0x356e,
+ 0xcd99, 0xdc10, 0xee8b, 0xff02, 0x8bbd, 0x9a34, 0xa8af, 0xb926,
+ 0x5afa, 0x4b73, 0x79e8, 0x6861, 0x1cde, 0x0d57, 0x3fcc, 0x2e45,
+ 0xd6b2, 0xc73b, 0xf5a0, 0xe429, 0x9096, 0x811f, 0xb384, 0xa20d,
+ 0x53e3, 0x426a, 0x70f1, 0x6178, 0x15c7, 0x044e, 0x36d5, 0x275c,
+ 0xdfab, 0xce22, 0xfcb9, 0xed30, 0x998f, 0x8806, 0xba9d, 0xab14,
+ 0x6cac, 0x7d25, 0x4fbe, 0x5e37, 0x2a88, 0x3b01, 0x099a, 0x1813,
+ 0xe0e4, 0xf16d, 0xc3f6, 0xd27f, 0xa6c0, 0xb749, 0x85d2, 0x945b,
+ 0x65b5, 0x743c, 0x46a7, 0x572e, 0x2391, 0x3218, 0x0083, 0x110a,
+ 0xe9fd, 0xf874, 0xcaef, 0xdb66, 0xafd9, 0xbe50, 0x8ccb, 0x9d42,
+ 0x7e9e, 0x6f17, 0x5d8c, 0x4c05, 0x38ba, 0x2933, 0x1ba8, 0x0a21,
+ 0xf2d6, 0xe35f, 0xd1c4, 0xc04d, 0xb4f2, 0xa57b, 0x97e0, 0x8669,
+ 0x7787, 0x660e, 0x5495, 0x451c, 0x31a3, 0x202a, 0x12b1, 0x0338,
+ 0xfbcf, 0xea46, 0xd8dd, 0xc954, 0xbdeb, 0xac62, 0x9ef9, 0x8f70
+};
+
+/**
+ Calculates the CRC16 checksum of the given buffer.
+
+ @param[in] Buffer Pointer to the buffer.
+ @param[in] Length Length of the buffer, in bytes.
+ @param[in] InitialValue Initial value of the CRC.
+
+ @return The CRC16 checksum.
+*/
+UINT16
+CalculateCrc16 (
+ IN CONST VOID *Buffer,
+ IN UINTN Length,
+ IN UINT16 InitialValue
+ )
+{
+ CONST UINT8 *Buf;
+ UINT16 Crc;
+
+ Buf = Buffer;
+
+ Crc = ~InitialValue;
+
+ while(Length-- != 0) {
+ Crc = gCrc16LookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);
+ }
+
+ return ~Crc;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc32c.c b/Features/Ext4Pkg/Ext4Dxe/Crc32c.c
new file mode 100644
index 0000000000..3986c9b10f
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Crc32c.c
@@ -0,0 +1,84 @@
+/**
+ @file CRC32c calculation routines.
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include <Uefi.h>
+
+STATIC CONST UINT32 gCrc32cLookupTable[256] = {
+ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,
+ 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
+ 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,
+ 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
+ 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,
+ 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
+ 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,
+ 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
+ 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,
+ 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
+ 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,
+ 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
+ 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,
+ 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
+ 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,
+ 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
+ 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,
+ 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
+ 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,
+ 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
+ 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,
+ 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
+ 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,
+ 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
+ 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,
+ 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
+ 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,
+ 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
+ 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,
+ 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
+ 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,
+ 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
+ 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,
+ 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
+ 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,
+ 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
+ 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,
+ 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
+ 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,
+ 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
+ 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,
+ 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
+ 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
+};
+
+/**
+ Calculates the CRC32c checksum of the given buffer.
+
+ @param[in] Buffer Pointer to the buffer.
+ @param[in] Length Length of the buffer, in bytes.
+ @param[in] InitialValue Initial value of the CRC.
+
+ @return The CRC32c checksum.
+*/
+UINT32
+CalculateCrc32c (
+ IN CONST VOID *Buffer,
+ IN UINTN Length,
+ IN UINT32 InitialValue
+ )
+{
+ CONST UINT8 *Buf;
+ UINT32 Crc;
+
+ Buf = Buffer;
+ Crc = ~InitialValue;
+
+ while(Length-- != 0) {
+ Crc = gCrc32cLookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);
+ }
+
+ return ~Crc;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Directory.c b/Features/Ext4Pkg/Ext4Dxe/Directory.c
new file mode 100644
index 0000000000..caa97cf9f1
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Directory.c
@@ -0,0 +1,492 @@
+/**
+ @file Directory related routines
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+#include <Library/BaseUcs2Utf8Lib.h>
+
+/**
+ Retrieves the filename of the directory entry and converts it to UTF-16/UCS-2
+
+ @param[in] Entry Pointer to a EXT4_DIR_ENTRY.
+ @param[out] Ucs2FileName Pointer to an array of CHAR16's, of size EXT4_NAME_MAX + 1.
+
+ @retval EFI_SUCCESS The filename was succesfully retrieved and converted to UCS2.
+ @retval !EFI_SUCCESS Failure.


+*/
+EFI_STATUS
+Ext4GetUcs2DirentName (
+ IN EXT4_DIR_ENTRY *Entry,
+ OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]
+ )
+{
+ CHAR8 Utf8NameBuf[EXT4_NAME_MAX + 1];
+ UINT16 *Str;
+ EFI_STATUS Status;
+
+ CopyMem (Utf8NameBuf, Entry->name, Entry->name_len);
+
+ Utf8NameBuf[Entry->name_len] = '\0';
+
+ // Unfortunately, BaseUcs2Utf8Lib doesn't have a convert-buffer-to-buffer-like
+ // function. Therefore, we need to allocate from the pool (inside UTF8StrToUCS2),
+ // copy it to our out buffer (Ucs2FileName) and free.
Maybe that's a good idea to propose then? Dynamic memory allocations are never nice. :)

+ Status = UTF8StrToUCS2 (Utf8NameBuf, &Str);
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = StrCpyS (Ucs2FileName, EXT4_NAME_MAX + 1, Str);
+
+ FreePool (Str);
+
+ return Status;
+}
+
+/**
+ Retrieves a directory entry.
+
+ @param[in] Directory Pointer to the opened directory.
+ @param[in] NameUnicode Pointer to the UCS-2 formatted filename.
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[out] Result Pointer to the destination directory entry.
+
+ @return The result of the operation.
+*/
+EFI_STATUS
+Ext4RetrieveDirent (
+ IN EXT4_FILE *File,
+ IN CONST CHAR16 *Name,
+ IN EXT4_PARTITION *Partition,
+ OUT EXT4_DIR_ENTRY *res
+ )
+{
+ EFI_STATUS Status;
+ CHAR8 *Buf;
+ UINT64 Off;
+ EXT4_INODE *Inode;
+ UINT64 DirInoSize;
+ UINT32 BlockRemainder;
+
+ Status = EFI_NOT_FOUND;
+ Buf = AllocatePool (Partition->BlockSize);
+
+ if(Buf == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ Off = 0;
+
+ Inode = File->Inode;
+ DirInoSize = EXT4_INODE_SIZE (Inode);
+
+ DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
+ if(BlockRemainder != 0) {
+ // Directory inodes need to have block aligned sizes
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ while(Off < DirInoSize) {
+ UINTN Length;
Variable not declared at function entry.

+
+ Length = Partition->BlockSize;
+
+ Status = Ext4Read (Partition, File, Buf, Off, &Length);
+
+ if (Status != EFI_SUCCESS) {
+ FreePool (Buf);
+ return Status;
+ }
+
+ for(CHAR8 *b = Buf; b < Buf + Partition->BlockSize; ) {
+ EXT4_DIR_ENTRY *Entry;
+ UINTN RemainingBlock;
+
+ Entry = (EXT4_DIR_ENTRY *)b;
+ ASSERT (Entry->rec_len != 0);
+
+ RemainingBlock = Partition->BlockSize - (b - Buf);
I think this would be easier to read and verify if the loop variant as RemainingBlock.

+
+ if(Entry->name_len > RemainingBlock || Entry->rec_len > RemainingBlock) {
+ // Corrupted filesystem
+ // TODO: Do the proper ext4 corruption detection thing and dirty the filesystem.
+ FreePool (Buf);
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ // Ignore names bigger than our limit.
+
+ /* Note: I think having a limit is sane because:
+ 1) It's nicer to work with.
When in doubt, say it's for security. :) An upper limit gives you a definite guarantee you need to (and can) test your code against, you can avoid dynamic memory allocations and their classes of potential exploits, etc...
Also, I'm not sure C++ comments are permitted in function bodies, at least I have not seen them.

+ 2) Linux and a number of BSDs also have a filename limit of 255.
+ */
+ if(Entry->name_len > EXT4_NAME_MAX) {
+ continue;
+ }
+
+ // Unused entry
+ if(Entry->inode == 0) {
+ b += Entry->rec_len;
+ continue;
+ }
+
+ CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1];
I have seen this type a lot now, maybe it'd be a good idea to typedef it?

+
+ Status = Ext4GetUcs2DirentName (Entry, Ucs2FileName);
+
+ /* In theory, this should never fail.
+ * In reality, it's quite possible that it can fail, considering filenames in
+ * Linux (and probably other nixes) are just null-terminated bags of bytes, and don't
+ * need to form valid ASCII/UTF-8 sequences.
+ */
+ if (EFI_ERROR (Status)) {
+ // If we error out, skip this entry
+ // I'm not sure if this is correct behaviour, but I don't think there's a precedent here.
+ b += Entry->rec_len;
+ continue;
+ }
+
+ if (Entry->name_len == StrLen (Name) &&
+ !Ext4StrCmpInsensitive (Ucs2FileName, (CHAR16 *)Name)) {
+ UINTN ToCopy;
+
+ ToCopy = Entry->rec_len > sizeof (EXT4_DIR_ENTRY) ?
+ sizeof (EXT4_DIR_ENTRY) :
+ Entry->rec_len;
Please use MIN().

+
+ CopyMem (res, Entry, ToCopy);
+ FreePool (Buf);
+ return EFI_SUCCESS;
+ }
+
+ b += Entry->rec_len;
+ }
+
+ Off += Partition->BlockSize;
+ }
+
+ FreePool (Buf);
+ return EFI_NOT_FOUND;
+}
+
+/**
+ Opens a file using a directory entry.
+
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[in] OpenMode Mode in which the file is supposed to be open.
+ @param[out] OutFile Pointer to the newly opened file.
+ @param[in] Entry Directory entry to be used.
+
+ @retval EFI_STATUS Result of the operation
+*/
+EFI_STATUS
+Ext4OpenDirent (
+ IN EXT4_PARTITION *Partition,
+ IN UINT64 OpenMode,
+ OUT EXT4_FILE **OutFile,
+ IN EXT4_DIR_ENTRY *Entry
+ )
+{
+ EFI_STATUS Status;
+ CHAR16 FileName[EXT4_NAME_MAX + 1];
+ EXT4_FILE *File;
+
+ File = AllocateZeroPool (sizeof (EXT4_FILE));
+
+ if (File == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto Error;
+ }
+
+ Status = Ext4GetUcs2DirentName (Entry, FileName);
+
+ if (EFI_ERROR (Status)) {
+ goto Error;
+ }
+
+ File->FileName = AllocateZeroPool (StrSize (FileName));
+
+ if (!File->FileName) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto Error;
+ }
+
+ Status = Ext4InitExtentsMap (File);
+
+ if (EFI_ERROR (Status)) {
+ goto Error;
+ }
+
+ // This should not fail.
+ StrCpyS (File->FileName, EXT4_NAME_MAX + 1, FileName);
Maybe use ARRAY_SIZE to avoid size constants?

+
+ File->InodeNum = Entry->inode;
+
+ Ext4SetupFile (File, (EXT4_PARTITION *)Partition);
Is Partition not already this type?

+
+ Status = Ext4ReadInode (Partition, Entry->inode, &File->Inode);
+
+ if (EFI_ERROR (Status)) {
+ goto Error;
+ }
+
+ *OutFile = File;
+
+ InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
+
+ return EFI_SUCCESS;
+
+Error:
+ if (File != NULL) {
+ if (File->FileName != NULL) {
+ FreePool (File->FileName);
+ }
+
+ if (File->ExtentsMap != NULL) {
+ OrderedCollectionUninit (File->ExtentsMap);
+ }
+
+ FreePool (File);
+ }
+
+ return Status;
+}
+
+/**
+ Opens a file.
+
+ @param[in] Directory Pointer to the opened directory.
+ @param[in] Name Pointer to the UCS-2 formatted filename.
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[in] OpenMode Mode in which the file is supposed to be open.
+ @param[out] OutFile Pointer to the newly opened file.
+
+ @return Result of the operation.
+*/
+EFI_STATUS
+Ext4OpenFile (
+ IN EXT4_FILE *Directory,
+ IN CONST CHAR16 *Name,
+ IN EXT4_PARTITION *Partition,
+ IN UINT64 OpenMode,
+ OUT EXT4_FILE **OutFile
+ )
+{
+ EXT4_DIR_ENTRY Entry;
+ EFI_STATUS Status;
+
+ Status = Ext4RetrieveDirent (Directory, Name, Partition, &Entry);
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ // EFI requires us to error out on ".." opens for the root directory
+ if (Entry.inode == Directory->InodeNum) {
+ return EFI_NOT_FOUND;
+ }
+
+ return Ext4OpenDirent (Partition, OpenMode, OutFile, &Entry);
+}
+
+EFI_STATUS EFIAPI
+Ext4OpenVolume (
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition, EFI_FILE_PROTOCOL **Root
+ )
+{
+ EXT4_INODE *RootInode;
+ EFI_STATUS Status;
+
+ Status = Ext4ReadInode ((EXT4_PARTITION *)Partition, 2, &RootInode);
+
+ if(EFI_ERROR (Status)) {
+ DEBUG ((EFI_D_ERROR, "[ext4] Could not open root inode - status %x\n", Status));
+ return Status;
+ }
+
+ EXT4_FILE *RootDir = AllocateZeroPool (sizeof (EXT4_FILE));
+
+ if(!RootDir) {
+ FreePool (RootInode);
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ // The filename will be "\"(null terminated of course)
+ RootDir->FileName = AllocateZeroPool (2 * sizeof (CHAR16));
+
+ if (!RootDir->FileName) {
+ FreePool (RootDir);
+ FreePool (RootInode);
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ RootDir->FileName[0] = L'\\';
+
+ RootDir->Inode = RootInode;
+ RootDir->InodeNum = 2;
+
+ if (EFI_ERROR (Ext4InitExtentsMap (RootDir))) {
I'm not sure it's allowed to call functions in such expressions, but if-clauses with side-effects are generally error-prone and hard to read in my opinion.

+ FreePool (RootDir->FileName);
+ FreePool (RootInode);
+ FreePool (RootDir);
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ Ext4SetupFile (RootDir, (EXT4_PARTITION *)Partition);
+ *Root = &RootDir->Protocol;
+
+ InsertTailList (&((EXT4_PARTITION *)Partition)->OpenFiles, &RootDir->OpenFilesListNode);
Casting like this is unusual in EDK II, usually the private part of your structure would start with a Signature and you would use CR() to get a pointer to the private part.

+
+ return EFI_SUCCESS;
+}
+
+/**
+ Validates a directory entry.
+
+ @param[in] Dirent Pointer to the directory entry.
+
+ @retval TRUE Valid directory entry.
+ FALSE Invalid directory entry.
+*/
+STATIC
+BOOLEAN
+Ext4ValidDirent (
+ IN CONST EXT4_DIR_ENTRY *Dirent
+ )
+{
+ UINTN RequiredSize = Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;
+
+ if (Dirent->rec_len < RequiredSize) {
+ DEBUG ((EFI_D_ERROR, "[ext4] dirent size %lu too small (compared to %lu)\n", Dirent->rec_len, RequiredSize));
I *think* "EFI_D_" is considered legacy in favour of "DEBUG_", but not 100 % sure.

+ return FALSE;
+ }
+
+ // Dirent sizes need to be 4 byte aligned
+ if (Dirent->rec_len % 4) {
The offset is used for casts to EXT4_DIR_ENTRY, maybe add a STATIC_ASSERT the alignment is sufficient? There is no macro for type alignment yet, but I will submit it soon, so maybe add a TODO if you agree.

+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ Reads a directory entry.
+
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[in] File Pointer to the open directory.
+ @param[out] Buffer Pointer to the output buffer.
+ @param[in] Offset Initial directory position.
+ @param[in out] OutLength Pointer to a UINTN that contains the length of the buffer,
+ and the length of the actual EFI_FILE_INFO after the call.
+
+ @return Result of the operation.
+*/
+EFI_STATUS
+Ext4ReadDir (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_FILE *File,
+ OUT VOID *Buffer,
+ IN UINT64 Offset,
+ IN OUT UINTN *OutLength
+ )
+{
+ DEBUG ((EFI_D_INFO, "[ext4] Ext4ReadDir offset %lu\n", Offset));
+ EXT4_INODE *DirIno;
+ EFI_STATUS Status;
+ UINT64 DirInoSize;
+ UINTN Len;
+ UINT32 BlockRemainder;
+ EXT4_DIR_ENTRY Entry;
+
+ DirIno = File->Inode;
+ Status = EFI_SUCCESS;
+ DirInoSize = Ext4InodeSize (DirIno);
+
+ DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
+ if(BlockRemainder != 0) {
+ // Directory inodes need to have block aligned sizes
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ while(TRUE) {
+ EXT4_FILE *TempFile;
+
+ TempFile = NULL;
+
+ // We (try to) read the maximum size of a directory entry at a time
+ // Note that we don't need to read any padding that may exist after it.
+ Len = sizeof (Entry);
+ Status = Ext4Read (Partition, File, &Entry, Offset, &Len);
+
+ if (EFI_ERROR (Status)) {
+ goto Out;
+ }
+
+ #if 0
+ DEBUG ((EFI_D_INFO, "[ext4] Length read %lu, offset %lu\n", Len, Offset));
+ #endif
+
+ if (Len == 0) {
+ *OutLength = 0;
+ Status = EFI_SUCCESS;
+ goto Out;
+ }
+
+ if (Len < EXT4_MIN_DIR_ENTRY_LEN) {
+ Status = EFI_VOLUME_CORRUPTED;
+ goto Out;
+ }
+
+ // Invalid directory entry length
+ if (!Ext4ValidDirent (&Entry)) {
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
+ Status = EFI_VOLUME_CORRUPTED;
+ goto Out;
+ }
+
+ DEBUG ((EFI_D_INFO, "[ext4] dirent size %lu\n", Entry.rec_len));
+
+ if (Entry.inode == 0) {
+ // When inode = 0, it's unused
+ Offset += Entry.rec_len;
+ continue;
+ }
+
+ Status = Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &Entry);
+
+ if (EFI_ERROR (Status)) {
+ goto Out;
+ }
+
+ // TODO: Is this needed?
+ if (!StrCmp (TempFile->FileName, L".") || !StrCmp (TempFile->FileName, L"..")) {
Integer comparisons with boolean operators are disallowed, please compare against int literals (-> "!= 0").

+ Offset += Entry.rec_len;
+ Ext4CloseInternal (TempFile);
+ continue;
+ }
+
+ #if 0
+ DEBUG ((EFI_D_INFO, "[ext4] Listing file %s\n", TempFile->FileName));
+ #endif
+
+ Status = Ext4GetFileInfo (TempFile, Buffer, OutLength);
+ if (!EFI_ERROR (Status)) {
+ File->Position = Offset + Entry.rec_len;
+ }
+
+ Ext4CloseInternal (TempFile);
+
+ break;
+ }
+
+ Status = EFI_SUCCESS;
+Out:
+ return Status;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
new file mode 100644
index 0000000000..1cafdd64cd
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
@@ -0,0 +1,83 @@
+/**
+ @file Disk utilities
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+#include "Ext4Dxe.h"
+
+/**
+ Reads from the partition's disk using the DISK_IO protocol.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[out] Buffer Pointer to a destination buffer.
+ @param[in] Length Length of the destination buffer.
+ @param[in] Offset Offset, in bytes, of the location to read.
+
+ @return Success status of the disk read.
+ */
+EFI_STATUS
+Ext4ReadDiskIo (
+ IN EXT4_PARTITION *Partition,
+ OUT VOID *Buffer,
+ IN UINTN Length,
+ IN UINT64 Offset
+ )
+{
+ return Ext4DiskIo (Partition)->ReadDisk (Ext4DiskIo (Partition), Ext4MediaId (Partition), Offset, Length, Buffer);
+}
+
+/**
+ Reads blocks from the partition's disk using the DISK_IO protocol.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[out] Buffer Pointer to a destination buffer.
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.
+ @param[in] BlockNumber Starting block number.
+
+ @return Success status of the read.
+ */
+EFI_STATUS
+Ext4ReadBlocks (
+ IN EXT4_PARTITION *Partition,
Nitpicking, but I'd say any "IN" pointer should be CONST.

+ OUT VOID *Buffer,
+ IN UINTN NumberBlocks,
+ IN EXT4_BLOCK_NR BlockNumber
+ )
+{
+ return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, BlockNumber * Partition->BlockSize);
I've seen a few multiplications, how do we know they do not wrap around?

+}
+
+/**
+ Allocates a buffer and reads blocks from the partition's disk using the DISK_IO protocol.
+ This function is deprecated and will be removed in the future.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.
+ @param[in] BlockNumber Starting block number.
+
+ @return Buffer allocated by AllocatePool, or NULL if some part of the process
+ failed.
+ */
+VOID *
+Ext4AllocAndReadBlocks (
+ IN EXT4_PARTITION *Partition,
+ IN UINTN NumberBlocks,
+ IN EXT4_BLOCK_NR BlockNumber
+ )
+{
+ VOID *Buf;
+
+ Buf = AllocatePool (NumberBlocks * Partition->BlockSize);
+
+ if(Buf == NULL) {
+ return NULL;
+ }
+
+ if(Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) != EFI_SUCCESS) {
+ FreePool (Buf);
+ return NULL;
+ }
+
+ return Buf;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
new file mode 100644
index 0000000000..d790e70be1
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
@@ -0,0 +1,450 @@
+/**
+ @file Raw filesystem data structures
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+ Layout of an EXT2/3/4 filesystem:
+ (note: this driver has been developed using
+ https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as
+ documentation).
+
+ An ext2/3/4 filesystem (here on out referred to as simply an ext4 filesystem,
+ due to the similarities) is composed of various concepts:
+
+ 1) Superblock
+ The superblock is the structure near (1024 bytes offset from the start)
+ the start of the partition, and describes the filesystem in general.
+ Here, we get to know the size of the filesystem's blocks, which features
+ it supports or not, whether it's been cleanly unmounted, how many blocks
+ we have, etc.
+
+ 2) Block groups
+ EXT4 filesystems are divided into block groups, and each block group covers
+ s_blocks_per_group(8 * Block Size) blocks. Each block group has an
+ associated block group descriptor; these are present directly after the
+ superblock. Each block group descriptor contains the location of the
+ inode table, and the inode and block bitmaps (note these bitmaps are only
+ a block long, which gets us the 8 * Block Size formula covered previously).
+
+ 3) Blocks
+ The ext4 filesystem is divided in blocks, of size s_log_block_size ^ 1024.
+ Blocks can be allocated using individual block groups's bitmaps. Note
+ that block 0 is invalid and its presence on extents/block tables means
+ it's part of a file hole, and that particular location must be read as
+ a block full of zeros.
+
+ 4) Inodes
+ The ext4 filesystem divides files/directories into inodes (originally
+ index nodes). Each file/socket/symlink/directory/etc (here on out referred
+ to as a file, since there is no distinction under the ext4 filesystem) is
+ stored as a /nameless/ inode, that is stored in some block group's inode
+ table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if it's
+ an old filesystem), and holds various metadata about the file. Since the
+ largest inode structure right now is ~160 bytes, the rest of the inode
+ contains inline extended attributes. Inodes' data is stored using either
+ data blocks (under ext2/3) or extents (under ext4).
+
+ 5) Extents
+ Ext4 inodes store data in extents. These let N contiguous logical blocks
+ that are represented by N contiguous physical blocks be represented by a
+ single extent structure, which minimizes filesystem metadata bloat and
+ speeds up block mapping (particularly due to the fact that high-quality
+ ext4 implementations like linux's try /really/ hard to make the file
+ contiguous, so it's common to have files with almost 0 fragmentation).
+ Inodes that use extents store them in a tree, and the top of the tree
+ is stored on i_data. The tree's leaves always start with an
+ EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth != 0 and
+ EXT4_EXTENT on eh_depth = 0; these entries are always sorted by logical
+ block.
+
+ 6) Directories
+ Ext4 directories are files that store name -> inode mappings for the
+ logical directory; this is where files get their names, which means ext4
+ inodes do not themselves have names, since they can be linked (present)
+ multiple times with different names. Directories can store entries in two
+ different ways:
+ 1) Classical linear directories: They store entries as a mostly-linked
+ mostly-list of EXT4_DIR_ENTRY.
+ 2) Hash tree directories: These are used for larger directories, with
+ hundreds of entries, and are designed in a backwards compatible way.
+ These are not yet implemented in the Ext4Dxe driver.
+
+ 7) Journal
+ Ext3/4 filesystems have a journal to help protect the filesystem against
+ system crashes. This is not yet implemented in Ext4Dxe but is described
+ in detail in the Linux kernel's documentation.
+ */
+
+#ifndef _EXT4_DISK_H
+#define _EXT4_DISK_H
+
+#include <Uefi.h>
+
+#define EXT4_SUPERBLOCK_OFFSET 1024U
+
+#define EXT4_SIGNATURE 0xEF53U
+
+#define EXT4_FS_STATE_UNMOUNTED 0x1
+#define EXT4_FS_STATE_ERRORS_DETECTED 0x2
+#define EXT4_FS_STATE_RECOVERING_ORPHANS 0x4
+
+#define EXT4_ERRORS_CONTINUE 1
+#define EXT4_ERRORS_RO 2
+#define EXT4_ERRORS_PANIC 3
+
+#define EXT4_LINUX_ID 0
+#define EXT4_GNU_HURD_ID 1
+#define EXT4_MASIX_ID 2
+#define EXT4_FREEBSD_ID 3
+#define EXT4_LITES_ID 4
+
+#define EXT4_GOOD_OLD_REV 0
+#define EXT4_DYNAMIC_REV 1
+
+#define EXT4_CHECKSUM_CRC32C 0x1
+
+#define EXT4_FEATURE_COMPAT_DIR_PREALLOC 0x01
+#define EXT4_FEATURE_COMPAT_IMAGIC_INODES 0x02
+#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x04
+#define EXT4_FEATURE_COMPAT_EXT_ATTR 0x08
+#define EXT4_FEATURE_COMPAT_RESIZE_INO 0x10
+#define EXT4_FEATURE_COMPAT_DIR_INDEX 0x20
+
+#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x00001
+#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x00002
+#define EXT4_FEATURE_INCOMPAT_RECOVER 0x00004
+#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x00008
+#define EXT4_FEATURE_INCOMPAT_META_BG 0x00010
+#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x00040
+#define EXT4_FEATURE_INCOMPAT_64BIT 0x00080
+#define EXT4_FEATURE_INCOMPAT_MMP 0x00100
+#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x00200
+#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x00400
+// It's not clear whether or not this feature (below) is used right now
+#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x01000
+#define EXT4_FEATURE_INCOMPAT_CSUM_SEED 0x02000
+#define EXT4_FEATURE_INCOMPAT_LARGEDIR 0x04000
+#define EXT4_FEATURE_INCOMPAT_INLINE_DATA 0x08000
+#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000
+
+#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
+#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
+#define EXT4_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 // Unused
+#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008
+#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010
+#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020
+#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040
+#define EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT 0x0080 // Not implemented in ext4
+#define EXT4_FEATURE_RO_COMPAT_QUOTA 0x0100
+#define EXT4_FEATURE_RO_COMPAT_BIGALLOC 0x0200
+#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
+#define EXT4_FEATURE_RO_COMPAT_REPLICA 0x0800 // Not used
+
+// We explicitly don't recognise this, so we get read only.
+#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000
+#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000
+
+/* Important notes about the features
+ * Absolutely needed features:
+ * 1) Every incompat, because we might want to mount root filesystems
+ * 2) Relevant RO_COMPATs(I'm not sure of what to do wrt quota, project)
+ */
+
+#define EXT4_INO_TYPE_FIFO 0x1000
+#define EXT4_INO_TYPE_CHARDEV 0x2000
+#define EXT4_INO_TYPE_DIR 0x4000
+#define EXT4_INO_TYPE_BLOCKDEV 0x6000
+#define EXT4_INO_TYPE_REGFILE 0x8000
+#define EXT4_INO_TYPE_SYMLINK 0xA000
+#define EXT4_INO_TYPE_UNIX_SOCK 0xC000
+
+/* Inode flags */
+#define EXT4_SECRM_FL 0x00000001
+#define EXT4_UNRM_FL 0x00000002
+#define EXT4_COMPR_FL 0x00000004
+#define EXT4_SYNC_FL 0x00000008
+#define EXT4_IMMUTABLE_FL 0x00000010
+#define EXT4_APPEND_FL 0x00000020
+#define EXT4_NODUMP_FL 0x00000040
+#define EXT4_NOATIME_FL 0x00000080
+#define EXT4_DIRTY_FL 0x00000100
+#define EXT4_COMPRBLK_FL 0x00000200
+#define EXT4_NOCOMPR_FL 0x00000400
+#define EXT4_ECOMPR_FL 0x00000800
+#define EXT4_BTREE_FL 0x00001000
+#define EXT4_INDEX_FL 0x00002000
+#define EXT4_JOURNAL_DATA_FL 0x00004000
+#define EXT4_NOTAIL_FL 0x00008000
+#define EXT4_DIRSYNC_FL 0x00010000
+#define EXT4_TOPDIR_FL 0x00020000
+#define EXT4_HUGE_FILE_FL 0x00040000
+#define EXT4_EXTENTS_FL 0x00080000
+#define EXT4_VERITY_FL 0x00100000
+#define EXT4_EA_INODE_FL 0x00200000
+#define EXT4_RESERVED_FL 0x80000000
+
+/* File type flags that are stored in the directory entries */
+#define EXT4_FT_UNKNOWN 0
+#define EXT4_FT_REG_FILE 1
+#define EXT4_FT_DIR 2
+#define EXT4_FT_CHRDEV 3
+#define EXT4_FT_BLKDEV 4
+#define EXT4_FT_FIFO 5
+#define EXT4_FT_SOCK 6
+#define EXT4_FT_SYMLINK 7
+
+typedef struct {
+ UINT32 s_inodes_count;
+ UINT32 s_blocks_count;
+ UINT32 s_r_blocks_count;
+ UINT32 s_free_blocks_count;
+ UINT32 s_free_inodes_count;
+ UINT32 s_first_data_block;
+ UINT32 s_log_block_size;
+ UINT32 s_log_frag_size;
+ UINT32 s_blocks_per_group;
+ UINT32 s_frags_per_group;
+ UINT32 s_inodes_per_group;
+ UINT32 s_mtime;
+ UINT32 s_wtime;
+ UINT16 s_mnt_count;
+ UINT16 s_max_mnt_count;
+ UINT16 s_magic;
+ UINT16 s_state;
+ UINT16 s_errors;
+ UINT16 s_minor_rev_level;
+ UINT32 s_lastcheck;
+ UINT32 s_check_interval;
+ UINT32 s_creator_os;
+ UINT32 s_rev_level;
+ UINT16 s_def_resuid;
+ UINT16 s_def_resgid;
+
+ /* Every field after this comment is revision >= 1 */
+
+ UINT32 s_first_ino;
+ UINT16 s_inode_size;
+ UINT16 s_block_group_nr;
+ UINT32 s_feature_compat;
+ UINT32 s_feature_incompat;
+ UINT32 s_feature_ro_compat;
+ UINT8 s_uuid[16];
+ UINT8 s_volume_name[16];
+ UINT8 s_last_mounted[64];
+ UINT32 s_algo_bitmap;
+ UINT8 s_prealloc_blocks;
+ UINT8 s_prealloc_dir_blocks;
+ UINT16 unused;
+ UINT8 s_journal_uuid[16];
+ UINT32 s_journal_inum;
+ UINT32 s_journal_dev;
+ UINT32 s_last_orphan;
+ UINT32 s_hash_seed[4];
+ UINT8 s_def_hash_version;
+ UINT8 s_jnl_backup_type;
+ UINT16 s_desc_size;
+ UINT32 s_default_mount_options;
+ UINT32 s_first_meta_bg;
+ UINT32 s_mkfs_time;
+ UINT32 s_jnl_blocks[17];
+ UINT32 s_blocks_count_hi;
+ UINT32 s_r_blocks_count_hi;
+ UINT32 s_free_blocks_count_hi;
+ UINT16 s_min_extra_isize;
+ UINT16 s_want_extra_isize;
+ UINT32 s_flags;
+ UINT16 s_raid_stride;
+ UINT16 s_mmp_interval;
+ UINT64 s_mmp_block;
+ UINT32 s_raid_stride_width;
+ UINT8 s_log_groups_per_flex;
+ UINT8 s_checksum_type; // Only valid value is 1 - CRC32C
+ UINT16 s_reserved_pad;
+ UINT64 s_kbytes_written;
+
+ // Snapshot stuff isn't used in Linux and isn't implemented here
+ UINT32 s_snapshot_inum;
+ UINT32 s_snapshot_id;
+ UINT64 s_snapshot_r_blocks_count;
+ UINT32 s_snapshot_list;
+ UINT32 s_error_count;
+ UINT32 s_first_error_time;
+ UINT32 s_first_error_ino;
+ UINT64 s_first_error_block;
+ UINT8 s_first_error_func[32];
+ UINT32 s_first_error_line;
+ UINT32 s_last_error_time;
+ UINT32 s_last_error_ino;
+ UINT32 s_last_error_line;
+ UINT64 s_last_error_block;
+ UINT8 s_last_error_func[32];
+ UINT8 s_mount_opts[64];
+ UINT32 s_usr_quota_inum;
+ UINT32 s_grp_quota_inum;
+ UINT32 s_overhead_blocks;
+ UINT32 s_backup_bgs[2]; // sparse_super2
+ UINT8 s_encrypt_algos[4];
+ UINT8 s_encrypt_pw_salt[16];
+ UINT32 s_lpf_ino;
+ UINT32 s_prj_quota_inum;
+ UINT32 s_checksum_seed;
+ UINT32 s_reserved[98];
+ UINT32 s_checksum;
+} EXT4_SUPERBLOCK;
+
+STATIC_ASSERT (sizeof (EXT4_SUPERBLOCK) == 1024, "ext4 superblock struct has incorrect size");
+
+typedef struct {
+ UINT32 bg_block_bitmap_lo;
+ UINT32 bg_inode_bitmap_lo;
+ UINT32 bg_inode_table_lo;
+ UINT16 bg_free_blocks_count_lo;
+ UINT16 bg_free_inodes_count_lo;
+ UINT16 bg_used_dirs_count_lo;
+ UINT16 bg_flags;
+ UINT32 bg_exclude_bitmap_lo;
+ UINT16 bg_block_bitmap_csum_lo;
+ UINT16 bg_inode_bitmap_csum_lo;
+ UINT16 bg_itable_unused_lo;
+ UINT16 bg_checksum;
+ UINT32 bg_block_bitmap_hi;
+ UINT32 bg_inode_bitmap_hi;
+ UINT32 bg_inode_table_hi;
+ UINT16 bg_free_blocks_count_hi;
+ UINT16 bg_free_inodes_count_hi;
+ UINT16 bg_used_dirs_count_hi;
+ UINT16 bg_itable_unused_hi;
+ UINT32 bg_exclude_bitmap_hi;
+ UINT16 bg_block_bitmap_csum_hi;
+ UINT16 bg_inode_bitmap_csum_hi;
+ UINT32 bg_reserved;
+} EXT4_BLOCK_GROUP_DESC;
+
+#define EXT4_OLD_BLOCK_DESC_SIZE 32
+#define EXT4_64BIT_BLOCK_DESC_SIZE 64
+
+STATIC_ASSERT (
+ sizeof (EXT4_BLOCK_GROUP_DESC) == EXT4_64BIT_BLOCK_DESC_SIZE,
+ "ext4 block group descriptor struct has incorrect size"
+ );
+
+#define EXT4_DBLOCKS 12
+#define EXT4_IND_BLOCK 12
+#define EXT4_DIND_BLOCK 13
+#define EXT4_TIND_BLOCK 14
+#define EXT4_NR_BLOCKS 15
+
+#define EXT4_GOOD_OLD_INODE_SIZE 128
+
+typedef struct _Ext4Inode {
+ UINT16 i_mode;
+ UINT16 i_uid;
+ UINT32 i_size_lo;
+ UINT32 i_atime;
+ UINT32 i_ctime;
+ UINT32 i_mtime;
+ UINT32 i_dtime;
+ UINT16 i_gid;
+ UINT16 i_links;
+ UINT32 i_blocks;
+ UINT32 i_flags;
+ UINT32 i_os_spec;
+ UINT32 i_data[EXT4_NR_BLOCKS];
+ UINT32 i_generation;
+ UINT32 i_file_acl;
+ UINT32 i_size_hi;
+ UINT32 i_faddr;
+ union {
+ // Note: Toolchain-specific defines (such as "linux") stops us from using simpler names down here.
+ struct _Ext4_I_OSD2_Linux {
+ UINT16 l_i_blocks_high;
+ UINT16 l_i_file_acl_high;
+ UINT16 l_i_uid_high;
+ UINT16 l_i_gid_high;
+ UINT16 l_i_checksum_lo;
+ UINT16 l_i_reserved;
+ } data_linux;
+
+ struct _Ext4_I_OSD2_Hurd {
+ UINT16 h_i_reserved1;
+ UINT16 h_i_mode_high;
+ UINT16 h_i_uid_high;
+ UINT16 h_i_gid_high;
+ UINT32 h_i_author;
+ } data_hurd;
+ } i_osd2;
+
+ UINT16 i_extra_isize;
+ UINT16 i_checksum_hi;
+ UINT32 i_ctime_extra;
+ UINT32 i_mtime_extra;
+ UINT32 i_atime_extra;
+ UINT32 i_crtime;
+ UINT32 i_crtime_extra;
+ UINT32 i_version_hi;
+ UINT32 i_projid;
+} EXT4_INODE;
+
+typedef struct {
+ UINT32 inode;
+ UINT16 rec_len;
+ UINT8 name_len;
+ UINT8 file_type;
+ CHAR8 name[255];
+} EXT4_DIR_ENTRY;
+
+#define EXT4_MIN_DIR_ENTRY_LEN 8
+
+// This on-disk structure is present at the bottom of the extent tree
+typedef struct {
+ // First logical block
+ UINT32 ee_block;
+ // Length of the extent, in blocks
+ UINT16 ee_len;
+ // The physical (filesystem-relative) block is split between the high 16 bits
+ // and the low 32 bits - this forms a 48-bit block number
+ UINT16 ee_start_hi;
+ UINT32 ee_start_lo;
+} EXT4_EXTENT;
+
+// This on-disk structure is present at all levels except the bottom
+typedef struct {
+ // This index covers logical blocks from 'ei_block'
+ UINT32 ei_block;
+ // Block of the next level of the extent tree, similarly split in a high and low portion.
+ UINT32 ei_leaf_lo;
+ UINT16 ei_leaf_hi;
+
+ UINT16 ei_unused;
+} EXT4_EXTENT_INDEX;
+
+typedef struct {
+ // Needs to be EXT4_EXTENT_HEADER_MAGIC
+ UINT16 eh_magic;
+ // Number of entries
+ UINT16 eh_entries;
+ // Maximum number of entries that could follow this header
+ UINT16 eh_max;
+ // Depth of this node in the tree - the tree can be at most 5 levels deep
+ UINT16 eh_depth;
+ // Unused by standard ext4
+ UINT32 eh_generation;
+} EXT4_EXTENT_HEADER;
+
+#define EXT4_EXTENT_HEADER_MAGIC 0xF30A
+
+// Specified by ext4 docs and backed by a bunch of math
+#define EXT4_EXTENT_TREE_MAX_DEPTH 5
+
+typedef struct {
+ // CRC32C of UUID + inode number + igeneration + extent block
+ UINT32 eb_checksum;
+} EXT4_EXTENT_TAIL;
+
+typedef UINT64 EXT4_BLOCK_NR;
+typedef UINT32 EXT4_INO_NR;
+
+#define EXT4_INODE_SIZE(ino) (((UINT64)ino->i_size_hi << 32) | ino->i_size_lo)
+
+#endif
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
new file mode 100644
index 0000000000..d1e289f8fa
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
@@ -0,0 +1,454 @@
+/**
+ @file Driver entry point
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4DriverNameTable[] = {
+ {
+ "eng;en",
+ L"Ext4 File System Driver"
+ },
+ {
+ NULL,
+ NULL
+ }
+};
+
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4ControllerNameTable[] = {
+ {
+ "eng;en",
+ L"Ext4 File System"
+ },
+ {
+ NULL,
+ NULL
+ }
+};
+
+// Needed by gExt4ComponentName*
+
+EFI_STATUS
+EFIAPI
+Ext4ComponentNameGetDriverName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN CHAR8 *Language,
+ OUT CHAR16 **DriverName
+ );
+
+EFI_STATUS
+EFIAPI
+Ext4ComponentNameGetControllerName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_HANDLE ChildHandle OPTIONAL,
+ IN CHAR8 *Language,
+ OUT CHAR16 **ControllerName
+ );
+
+extern EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentName;
+
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentName = {
+ Ext4ComponentNameGetDriverName,
+ Ext4ComponentNameGetControllerName,
+ "eng"
+};
+
+//
+// EFI Component Name 2 Protocol
+//
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME2_PROTOCOL gExt4ComponentName2 = {
+ (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)Ext4ComponentNameGetDriverName,
+ (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)Ext4ComponentNameGetControllerName,
+ "en"
+};
+
+// Needed by gExt4BindingProtocol
+
+EFI_STATUS EFIAPI
+Ext4IsBindingSupported (
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
+ );
+
+EFI_STATUS EFIAPI
+Ext4Bind (
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
+ );
+
+EFI_STATUS EFIAPI
+Ext4Stop (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN UINTN NumberOfChildren,
+ IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
+ );
+
+EFI_DRIVER_BINDING_PROTOCOL gExt4BindingProtocol =
+{
+ Ext4IsBindingSupported,
+ Ext4Bind,
+ Ext4Stop,
+ EXT4_DRIVER_VERSION
+};
+
+EFI_STATUS
+EFIAPI
+Ext4ComponentNameGetControllerName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_HANDLE ChildHandle OPTIONAL,
+ IN CHAR8 *Language,
+ OUT CHAR16 **ControllerName
+ )
+{
+ // TODO: Do we need to test whether we're managing the handle, like FAT does?
+ return LookupUnicodeString2 (
+ Language,
+ This->SupportedLanguages,
+ mExt4ControllerNameTable,
+ ControllerName,
+ (BOOLEAN)(This == &gExt4ComponentName)
+ );
+}
+
+EFI_STATUS
+EFIAPI
+Ext4ComponentNameGetDriverName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN CHAR8 *Language,
+ OUT CHAR16 **DriverName
+ )
+{
+ return LookupUnicodeString2 (
+ Language,
+ This->SupportedLanguages,
+ mExt4DriverNameTable,
+ DriverName,
+ (BOOLEAN)(This == &gExt4ComponentName)
+ );
+}
+
+EFI_STATUS EFIAPI
+Ext4IsBindingSupported (
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
+ );
+
+EFI_STATUS EFIAPI
+Ext4Bind (
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
+ );
+
+EFI_STATUS EFIAPI
+Ext4Stop (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN UINTN NumberOfChildren,
+ IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
+ )
+{
+ EFI_STATUS Status;
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Sfs;
+ EXT4_PARTITION *Partition;
+ BOOLEAN HasDiskIo2;
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiSimpleFileSystemProtocolGuid,
+ (VOID **)&Sfs,
+ This->DriverBindingHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL
+ );
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Partition = (EXT4_PARTITION *)Sfs;
+
+ HasDiskIo2 = Ext4DiskIo2 (Partition) != NULL;
+
+ Status = Ext4UnmountAndFreePartition (Partition);
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = gBS->UninstallMultipleProtocolInterfaces (
+ ControllerHandle,
+ &gEfiSimpleFileSystemProtocolGuid,
+ &Partition->Interface,
+ NULL
+ );
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ // Close all open protocols (DiskIo, DiskIo2, BlockIo)
+
+ Status = gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiDiskIoProtocolGuid,
+ This->DriverBindingHandle,
+ ControllerHandle
+ );
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiBlockIoProtocolGuid,
+ This->DriverBindingHandle,
+ ControllerHandle
+ );
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if(HasDiskIo2) {
+ Status = gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiDiskIo2ProtocolGuid,
+ This->DriverBindingHandle,
+ ControllerHandle
+ );
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ }
+
+ return Status;
+}
+
+EFI_STATUS
+EFIAPI
+Ext4EntryPoint (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ EFI_STATUS Status;
+
+ Status = EfiLibInstallAllDriverProtocols2 (
+ ImageHandle,
+ SystemTable,
+ &gExt4BindingProtocol,
+ ImageHandle,
+ &gExt4ComponentName,
+ &gExt4ComponentName2,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ );
+
+ if(EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ return Ext4InitialiseUnicodeCollation (ImageHandle);
+}
+
+EFI_STATUS
+EFIAPI
+Ext4Unload (
+ IN EFI_HANDLE ImageHandle
+ )
+{
+ EFI_STATUS Status;
+ EFI_HANDLE *DeviceHandleBuffer;
+ UINTN DeviceHandleCount;
+ UINTN Index;
+
+ Status = gBS->LocateHandleBuffer (
+ AllHandles,
+ NULL,
+ NULL,
+ &DeviceHandleCount,
+ &DeviceHandleBuffer
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ for(Index = 0; Index < DeviceHandleCount; Index++) {
+ EFI_HANDLE Handle;
+
+ Handle = DeviceHandleBuffer[Index];
+
+ Status = EfiTestManagedDevice (Handle, ImageHandle, &gEfiDiskIoProtocolGuid);
+
+ if(Status == EFI_SUCCESS) {
+ Status = gBS->DisconnectController (Handle, ImageHandle, NULL);
+
+ if (EFI_ERROR (Status)) {
+ break;
+ }
+ }
+ }
+
+ FreePool (DeviceHandleBuffer);
+
+ Status = EfiLibUninstallAllDriverProtocols2 (
+ &gExt4BindingProtocol,
+ &gExt4ComponentName,
+ &gExt4ComponentName2,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ );
+
+ return Status;
+}
+
+EFI_STATUS EFIAPI
+Ext4IsBindingSupported (
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
+ )
+{
+ // Note to self: EFI_OPEN_PROTOCOL_TEST_PROTOCOL lets us not close the
+ // protocol and ignore the output argument entirely
+
+ EFI_STATUS Status;
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiDiskIoProtocolGuid,
+ NULL,
+ BindingProtocol->ImageHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_TEST_PROTOCOL
+ );
+
+ if(EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiBlockIoProtocolGuid,
+ NULL,
+ BindingProtocol->ImageHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_TEST_PROTOCOL
+ );
+ return Status;
+}
+
+EFI_STATUS EFIAPI
+Ext4Bind (
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL
+ )
+{
+ EFI_DISK_IO_PROTOCOL *DiskIo;
+ EFI_DISK_IO2_PROTOCOL *DiskIo2;
+ EFI_BLOCK_IO_PROTOCOL *blockIo;
+ EFI_STATUS Status;
+
+ DiskIo2 = NULL;
+
+ DEBUG ((EFI_D_INFO, "[Ext4] Binding to controller\n"));
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiDiskIoProtocolGuid,
+ (VOID **)&DiskIo,
+ BindingProtocol->ImageHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_BY_DRIVER
+ );
+
+ if(EFI_ERROR (Status)) {
+ return Status;
Why does this return when the others jump to Error?

+ }
+
+ DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO\n"));
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiDiskIo2ProtocolGuid,
+ (VOID **)&DiskIo2,
+ BindingProtocol->ImageHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_BY_DRIVER
+ );
+ // It's okay to not support DISK_IO2
+
+ if(DiskIo2 != NULL) {
+ DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO2\n"));
+ }
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiBlockIoProtocolGuid,
+ (VOID **)&blockIo,
+ BindingProtocol->ImageHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL
+ );
+
+ if(EFI_ERROR (Status)) {
+ goto Error;
+ }
+
+ DEBUG ((EFI_D_INFO, "Opening partition\n"));
+
+ Status = Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, blockIo);
+
+ if(!EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ DEBUG ((EFI_D_INFO, "[ext4] Error mounting %x\n", Status));
You can print EFI_STATUS with "%r" to get a name string. I would move this into an error clause for Ext4OpenPartition() and jump to Error, with the outside branch returning success, to upkeep consistency and make it obvious to what the error message refers.

+
+Error:
+ if(DiskIo) {
+ gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiDiskIoProtocolGuid,
+ BindingProtocol->ImageHandle,
+ ControllerHandle
+ );
+ }
+
+ if(DiskIo2) {
+ gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiDiskIo2ProtocolGuid,
+ BindingProtocol->ImageHandle,
+ ControllerHandle
+ );
+ }
+
+ if(blockIo) {
+ gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiBlockIoProtocolGuid,
+ BindingProtocol->ImageHandle,
+ ControllerHandle
+ );
+ }
+
+ return Status;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
new file mode 100644
index 0000000000..f6875c919e
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
@@ -0,0 +1,942 @@
+/**
+ @file Common header for the driver
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#ifndef _EXT4_H
+#define _EXT4_H
+
+#include <Uefi.h>
+
+#include <Guid/FileInfo.h>
+#include <Guid/FileSystemInfo.h>
+#include <Guid/FileSystemVolumeLabelInfo.h>
+#include <Protocol/BlockIo.h>
+#include <Protocol/DiskIo.h>
+#include <Protocol/DiskIo2.h>
+#include <Protocol/SimpleFileSystem.h>
+
+#include <Library/PcdLib.h>
+#include <Library/DebugLib.h>
+#include <Library/UefiLib.h>
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiDriverEntryPoint.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+#include <Library/OrderedCollectionLib.h>
+
+#include "Ext4Disk.h"
+
+#define EXT4_NAME_MAX 255
+
+#define EXT4_DRIVER_VERSION 0x0000
+
+/**
+ Opens an ext4 partition and installs the Simple File System protocol.
+
+ @param[in] DeviceHandle Handle to the block device.
+ @param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.
+ @param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL, if supported.
+ @param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.
+
+ @retval EFI_SUCCESS The opening was successful.
+ !EFI_SUCCESS Opening failed.
+ */
+EFI_STATUS
+Ext4OpenPartition (
+ IN EFI_HANDLE DeviceHandle,
+ IN EFI_DISK_IO_PROTOCOL *DiskIo,
+ IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,
+ IN EFI_BLOCK_IO_PROTOCOL *BlockIo
+ );
+
+typedef struct _Ext4File EXT4_FILE;
+
+typedef struct _Ext4_PARTITION {
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL Interface;
+ EFI_DISK_IO_PROTOCOL *DiskIo;
+ EFI_DISK_IO2_PROTOCOL *DiskIo2;
+ EFI_BLOCK_IO_PROTOCOL *BlockIo;
+
+ EXT4_SUPERBLOCK SuperBlock;
+ BOOLEAN Unmounting;
+
+ UINT32 FeaturesIncompat;
+ UINT32 FeaturesCompat;
+ UINT32 FeaturesRoCompat;
+ UINT32 InodeSize;
+ UINT32 BlockSize;
+ BOOLEAN ReadOnly;
+ UINT64 NumberBlockGroups;
+ EXT4_BLOCK_NR NumberBlocks;
+
+ EXT4_BLOCK_GROUP_DESC *BlockGroups;
+ UINT32 DescSize;
+ EXT4_FILE *Root;
+
+ UINT32 InitialSeed;
+
+ LIST_ENTRY OpenFiles;
+} EXT4_PARTITION;
+
+/**
+ Opens and parses the superblock.
+
+ @param[out] Partition Partition structure to fill with filesystem details.
+ @retval EFI_SUCCESS Parsing was succesful and the partition is a
+ valid ext4 partition.
+ */
+EFI_STATUS
+Ext4OpenSuperblock (
+ OUT EXT4_PARTITION *Partition
+ );
+
+/**
+ Retrieves the EFI_BLOCK_IO_PROTOCOL of the partition.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @return The Block IO protocol associated with the partition.
+ */
+STATIC inline
+EFI_BLOCK_IO_PROTOCOL *
+Ext4BlockIo (
+ EXT4_PARTITION *Partition
+ )
+{
+ return Partition->BlockIo;
+}
+
+/**
+ Retrieves the EFI_DISK_IO_PROTOCOL of the partition.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @return The Disk IO protocol associated with the partition.
+ */
+STATIC inline
+EFI_DISK_IO_PROTOCOL *
+Ext4DiskIo (
+ EXT4_PARTITION *Partition
+ )
+{
+ return Partition->DiskIo;
+}
+
+/**
+ Retrieves the EFI_DISK_IO2_PROTOCOL of the partition.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @return The Disk IO 2 protocol associated with the partition, or NULL if
+ not supported.
+ */
+STATIC inline
+EFI_DISK_IO2_PROTOCOL *
+Ext4DiskIo2 (
+ EXT4_PARTITION *Partition
+ )
+{
+ return Partition->DiskIo2;
+}
+
+/**
+ Retrieves the media ID of the partition.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @return The media ID associated with the partition.
+ */
+STATIC inline
+UINT32
+Ext4MediaId (
+ EXT4_PARTITION *Partition
+ )
+{
+ return Partition->BlockIo->Media->MediaId;
+}
+
+/**
+ Reads from the partition's disk using the DISK_IO protocol.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[out] Buffer Pointer to a destination buffer.
+ @param[in] Length Length of the destination buffer.
+ @param[in] Offset Offset, in bytes, of the location to read.
+
+ @return Success status of the disk read.
+ */
+EFI_STATUS
+Ext4ReadDiskIo (
+ IN EXT4_PARTITION *Partition,
+ OUT VOID *Buffer,
+ IN UINTN Length,
+ IN UINT64 Offset
+ );
+
+/**
+ Reads blocks from the partition's disk using the DISK_IO protocol.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[out] Buffer Pointer to a destination buffer.
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.
+ @param[in] BlockNumber Starting block number.
+
+ @return Success status of the read.
+ */
+EFI_STATUS
+Ext4ReadBlocks (
+ IN EXT4_PARTITION *Partition,
+ OUT VOID *Buffer,
+ IN UINTN NumberBlocks,
+ IN EXT4_BLOCK_NR BlockNumber
+ );
+
+/**
+ Allocates a buffer and reads blocks from the partition's disk using the DISK_IO protocol.
+ This function is deprecated and will be removed in the future.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.
+ @param[in] BlockNumber Starting block number.
+
+ @return Buffer allocated by AllocatePool, or NULL if some part of the process
+ failed.
+ */
+VOID *
+Ext4AllocAndReadBlocks (
+ IN EXT4_PARTITION *Partition,
+ IN UINTN NumberBlocks,
+ IN EXT4_BLOCK_NR BlockNumber
+ );
+
+/**
+ Checks if the opened partition has the 64-bit feature (see EXT4_FEATURE_INCOMPAT_64BIT).
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+
+ @return TRUE if EXT4_FEATURE_INCOMPAT_64BIT is enabled, else FALSE.
+ */
+STATIC inline
+BOOLEAN
+Ext4Is64Bit (
+ IN CONST EXT4_PARTITION *Partition
+ )
+{
+ return Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_64BIT;
+}
+
+/**
+ Composes an EXT4_BLOCK_NR safely, from two halfs.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[in] Low Low half of the block number.
+ @param[in] High High half of the block number.
+
+ @return The block number formed by Low, and if 64 bit is enabled, High.
+ */
+STATIC inline
+EXT4_BLOCK_NR
+Ext4MakeBlockNumberFromHalfs (
+ IN CONST EXT4_PARTITION *Partition,
+ IN UINT32 Low,
+ IN UINT32 High
+ )
+{
+ // High might have garbage if it's not a 64 bit filesystem
+ return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;
+}
+
+/**
+ Retrieves a block group descriptor of the ext4 filesystem.
+
+ @param[in] Partition Pointer to the opened ext4 partition.
+ @param[in] BlockGroup Block group number.
+
+ @return A pointer to the block group descriptor.
+ */
+STATIC inline
+EXT4_BLOCK_GROUP_DESC *
+Ext4GetBlockGroupDesc (
+ IN EXT4_PARTITION *Partition,
+ IN UINT32 BlockGroup
+ )
+{
+ // Maybe assert that the block group nr isn't a nonsense number?
+ return (EXT4_BLOCK_GROUP_DESC *)((CHAR8 *)Partition->BlockGroups + BlockGroup * Partition->DescSize);
+}
+
+/**
+ Reads an inode from disk.
+
+ @param[in] Partition Pointer to the opened partition.
+ @param[in] InodeNum Number of the desired Inode
+ @param[out] OutIno Pointer to where it will be stored a pointer to the read inode.
+
+ @return Status of the inode read.
+ */
+EFI_STATUS
+Ext4ReadInode (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_INO_NR InodeNum,
+ OUT EXT4_INODE **OutIno
+ );
+
+/**
+ Converts blocks to bytes.
+
+ @param[in] Partition Pointer to the opened partition.
+ @param[in] Block Block number/number of blocks.
+
+ @return The number of bytes.
+ */
+STATIC inline
+UINT64
+Ext4BlockToByteOffset (
+ IN CONST EXT4_PARTITION *Partition,
+ IN EXT4_BLOCK_NR Block
+ )
+{
+ return Partition->BlockSize * Block;
+}
+
+/**
+ Reads from an EXT4 inode.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] File Pointer to the opened file.
+ @param[out] Buffer Pointer to the buffer.
+ @param[in] Offset Offset of the read.
+ @param[in out] Length Pointer to the length of the buffer, in bytes.
+ After a succesful read, it's updated to the number of read bytes.
+
+ @return Status of the read operation.
+*/
+EFI_STATUS
+Ext4Read (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_FILE *File,
+ OUT VOID *Buffer,
+ IN UINT64 Offset,
+ IN OUT UINTN *Length
+ );
+
+/**
+ Retrieves the size of the inode.
+
+ @param[in] Inode Pointer to the ext4 inode.
+
+ @return The size of the inode, in bytes.
+ */
+STATIC inline
+UINT64
+Ext4InodeSize (
+ CONST EXT4_INODE *Inode
+ )
+{
+ return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;
+}
+
+/**
+ Retrieves an extent from an EXT4 inode.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] File Pointer to the opened file.
+ @param[in] LogicalBlock Block number which the returned extent must cover.
+ @param[out] Extent Pointer to the output buffer, where the extent will be copied to.
+
+ @retval EFI_SUCCESS Retrieval was succesful.
+ @retval EFI_NO_MAPPING Block has no mapping.
+*/
+EFI_STATUS
+Ext4GetExtent (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_FILE *File,
+ IN EXT4_BLOCK_NR LogicalBlock,
+ OUT EXT4_EXTENT *Extent
+ );
+
+struct _Ext4File {
+ EFI_FILE_PROTOCOL Protocol;
+ EXT4_INODE *Inode;
+ EXT4_INO_NR InodeNum;
+
+ UINT64 OpenMode;
+ UINT64 Position;
+
+ EXT4_PARTITION *Partition;
+ CHAR16 *FileName;
+
+ ORDERED_COLLECTION *ExtentsMap;
+
+ LIST_ENTRY OpenFilesListNode;
+};
+
+#define Ext4FileFromOpenFileNode(Node) BASE_CR (Node, EXT4_FILE, OpenFilesListNode)
+
+/**
+ Retrieves a directory entry.
+
+ @param[in] Directory Pointer to the opened directory.
+ @param[in] NameUnicode Pointer to the UCS-2 formatted filename.
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[out] Result Pointer to the destination directory entry.
+
+ @return The result of the operation.
+*/
+EFI_STATUS
+Ext4RetrieveDirent (
+ IN EXT4_FILE *Directory,
+ IN CONST CHAR16 *NameUnicode,
+ IN EXT4_PARTITION *Partition,
+ OUT EXT4_DIR_ENTRY *Result
+ );
+
+/**
+ Opens a file.
+
+ @param[in] Directory Pointer to the opened directory.
+ @param[in] Name Pointer to the UCS-2 formatted filename.
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[in] OpenMode Mode in which the file is supposed to be open.
+ @param[out] OutFile Pointer to the newly opened file.
+
+ @return Result of the operation.
+*/
+EFI_STATUS
+Ext4OpenFile (
+ IN EXT4_FILE *Directory,
+ IN CONST CHAR16 *Name,
+ IN EXT4_PARTITION *Partition,
+ IN UINT64 OpenMode,
+ OUT EXT4_FILE **OutFile
+ );
+
+/**
+ Opens a file using a directory entry.
+
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[in] OpenMode Mode in which the file is supposed to be open.
+ @param[out] OutFile Pointer to the newly opened file.
+ @param[in] Entry Directory entry to be used.
+
+ @retval EFI_STATUS Result of the operation
+*/
+EFI_STATUS
+Ext4OpenDirent (
+ IN EXT4_PARTITION *Partition,
+ IN UINT64 OpenMode,
+ OUT EXT4_FILE **OutFile,
+ IN EXT4_DIR_ENTRY *Entry
+ );
+
+/**
+ Allocates a zeroed inode structure.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+
+ @return Pointer to the allocated structure, from the pool,
+ with size Partition->InodeSize.
+*/
+EXT4_INODE *
+Ext4AllocateInode (
+ IN EXT4_PARTITION *Partition
+ );
+
+// Part of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
+
+EFI_STATUS EFIAPI
+Ext4OpenVolume (
+ IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition,
+ IN EFI_FILE_PROTOCOL **Root
+ );
+
+// End of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
+
+/**
+ Sets up the protocol and metadata of a file that is being opened.
+
+ @param[in out] File Pointer to the file.
+ @param[in] Partition Pointer to the opened partition.
+ */
+VOID
+Ext4SetupFile (
+ IN OUT EXT4_FILE *File,
+ IN EXT4_PARTITION *Partition
+ );
+
+/**
+ Closes a file.
+
+ @param[in] File Pointer to the file.
+
+ @return Status of the closing of the file.
+ */
+EFI_STATUS
+Ext4CloseInternal (
+ IN EXT4_FILE *File
+ );
+
+// Part of the EFI_FILE_PROTOCOL
+
+EFI_STATUS EFIAPI
+Ext4Open (
+ IN EFI_FILE_PROTOCOL *This,
+ OUT EFI_FILE_PROTOCOL **NewHandle,
+ IN CHAR16 *FileName,
+ IN UINT64 OpenMode,
+ IN UINT64 Attributes
+ );
+
+EFI_STATUS EFIAPI
+Ext4Close (
+ IN EFI_FILE_PROTOCOL *This
+ );
+
+EFI_STATUS EFIAPI
+Ext4Delete (
+ IN EFI_FILE_PROTOCOL *This
+ );
+
+EFI_STATUS
+EFIAPI
+Ext4ReadFile (
+ IN EFI_FILE_PROTOCOL *This,
+ IN OUT UINTN *BufferSize,
+ OUT VOID *Buffer
+ );
+
+EFI_STATUS
+EFIAPI
+Ext4WriteFile (
+ IN EFI_FILE_PROTOCOL *This,
+ IN OUT UINTN *BufferSize,
+ IN VOID *Buffer
+ );
+
+EFI_STATUS
+EFIAPI
+Ext4GetPosition (
+ IN EFI_FILE_PROTOCOL *This,
+ OUT UINT64 *Position
+ );
+
+EFI_STATUS
+EFIAPI
+Ext4SetPosition (
+ IN EFI_FILE_PROTOCOL *This,
+ IN UINT64 Position
+ );
+
+EFI_STATUS
+EFIAPI
+Ext4GetInfo (
+ IN EFI_FILE_PROTOCOL *This,
+ IN EFI_GUID *InformationType,
+ IN OUT UINTN *BufferSize,
+ OUT VOID *Buffer
+ );
+
+// EFI_FILE_PROTOCOL implementation ends here.
+
+/**
+ Checks if a file is a directory.
+ @param[in] File Pointer to the opened file.
+
+ @return TRUE if file is a directory.
+*/
+BOOLEAN
+Ext4FileIsDir (
+ IN CONST EXT4_FILE *File
+ );
+
+/**
+ Checks if a file is a regular file.
+ @param[in] File Pointer to the opened file.
+
+ @return BOOLEAN TRUE if file is a regular file.
+*/
+BOOLEAN
+Ext4FileIsReg (
+ IN CONST EXT4_FILE *File
+ );
+
+// In EFI we can't open FIFO pipes, UNIX sockets, character/block devices since these concepts are
+// at the kernel level and are OS dependent.
+
+/**
+ Checks if a file is openable.
+ @param[in] File Pointer to the file trying to be opened.
+
+
+ @return TRUE if file is openable. A file is considered openable if
+ it's a regular file or a directory, since most other file types
+ don't make sense under UEFI.
+*/
+STATIC inline
+BOOLEAN
+Ext4FileIsOpenable (
+ IN CONST EXT4_FILE *File
+ )
+{
+ return Ext4FileIsReg (File) || Ext4FileIsDir (File);
+}
+
+#define Ext4InodeHasField(Inode, \
+ Field) (Inode->i_extra_isize + EXT4_GOOD_OLD_INODE_SIZE >= OFFSET_OF (EXT4_INODE, Field) + \
+ sizeof (((EXT4_INODE *)NULL)->Field))
+
+/**
+ Calculates the physical space used by a file.
+ @param[in] File Pointer to the opened file.
+
+ @return Physical space used by a file, in bytes.
+*/
+UINT64
+Ext4FilePhysicalSpace (
+ EXT4_FILE *File
+ );
+
+/**
+ Gets the file's last access time.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+*/
+VOID
+Ext4FileATime (
+ IN EXT4_FILE *File,
+ OUT EFI_TIME *Time
+ );
+
+/**
+ Gets the file's last (data) modification time.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+*/
+VOID
+Ext4FileMTime (
+ IN EXT4_FILE *File,
+ OUT EFI_TIME *Time
+ );
+
+/**
+ Gets the file's creation time, if possible.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+ In the case where the the creation time isn't recorded,
+ Time is zeroed.
+*/
+VOID
+Ext4FileCreateTime (
+ IN EXT4_FILE *File, OUT EFI_TIME *Time
+ );
+
+/**
+ Initialises Unicode collation, which is needed for case-insensitive string comparisons
+ within the driver (a good example of an application of this is filename comparison).
+
+ @param[in] DriverHandle Handle to the driver image.
+
+ @retval EFI_SUCCESS Unicode collation was successfully initialised.
+ @retval !EFI_SUCCESS Failure.
+*/
+EFI_STATUS
+Ext4InitialiseUnicodeCollation (
+ EFI_HANDLE DriverHandle
+ );
+
+/**
+ Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATION_PROTOCOL's StriColl
+ for more details.
+
+ @param[in] Str1 Pointer to a null terminated string.
+ @param[in] Str2 Pointer to a null terminated string.
+
+ @retval 0 Str1 is equivalent to Str2.
+ @retval >0 Str1 is lexically greater than Str2.
+ @retval <0 Str1 is lexically less than Str2.
+*/
+INTN
+Ext4StrCmpInsensitive (
+ IN CHAR16 *Str1,
+ IN CHAR16 *Str2
Why are they not CONST? The caller needs to cast this way. You can cast in the callee in my opinion.

+ );
+
+/**
+ Retrieves the filename of the directory entry and converts it to UTF-16/UCS-2
+
+ @param[in] Entry Pointer to a EXT4_DIR_ENTRY.
+ @param[out] Ucs2FileName Pointer to an array of CHAR16's, of size EXT4_NAME_MAX + 1.
+
+ @retval EFI_SUCCESS Unicode collation was successfully initialised.
+ @retval !EFI_SUCCESS Failure.
+*/
+EFI_STATUS
+Ext4GetUcs2DirentName (
+ IN EXT4_DIR_ENTRY *Entry,
+ OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]
+ );
+
+/**
+ Retrieves information about the file and stores it in the EFI_FILE_INFO format.
+
+ @param[in] File Pointer to an opened file.
+ @param[out] Info Pointer to a EFI_FILE_INFO.
+ @param[in out] BufferSize Pointer to the buffer size
+
+ @return Status of the file information request.
+*/
+EFI_STATUS
+Ext4GetFileInfo (
+ IN EXT4_FILE *File,
+ OUT EFI_FILE_INFO *Info,
+ IN OUT UINTN *BufferSize
+ );
+
+/**
+ Reads a directory entry.
+
+ @param[in] Partition Pointer to the ext4 partition.
+ @param[in] File Pointer to the open directory.
+ @param[out] Buffer Pointer to the output buffer.
+ @param[in] Offset Initial directory position.
+ @param[in out] OutLength Pointer to a UINTN that contains the length of the buffer,
+ and the length of the actual EFI_FILE_INFO after the call.
+
+ @return Result of the operation.
+*/
+EFI_STATUS
+Ext4ReadDir (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_FILE *File,
+ OUT VOID *Buffer,
+ IN UINT64 Offset,
+ IN OUT UINTN *OutLength
+ );
+
+/**
+ Initialises the (empty) extents map, that will work as a cache of extents.
+
+ @param[in] File Pointer to the open file.
+
+ @return Result of the operation.
+*/
+EFI_STATUS
+Ext4InitExtentsMap (
+ IN EXT4_FILE *File
+ );
+
+/**
+ Frees the extents map, deleting every extent stored.
+
+ @param[in] File Pointer to the open file.
+*/
+VOID
+Ext4FreeExtentsMap (
+ IN EXT4_FILE *File
+ );
+
+/**
+ Calculates the CRC32c checksum of the given buffer.
+
+ @param[in] Buffer Pointer to the buffer.
+ @param[in] Length Length of the buffer, in bytes.
+ @param[in] InitialValue Initial value of the CRC.
+
+ @return The CRC32c checksum.
+*/
+UINT32
+CalculateCrc32c (
+ IN CONST VOID *Buffer,
+ IN UINTN Length,
+ IN UINT32 InitialValue
+ );
+
+/**
+ Calculates the CRC16 checksum of the given buffer.
+
+ @param[in] Buffer Pointer to the buffer.
+ @param[in] Length Length of the buffer, in bytes.
+ @param[in] InitialValue Initial value of the CRC.
+
+ @return The CRC16 checksum.
+*/
+UINT16
+CalculateCrc16 (
+ IN CONST VOID *Buffer,
+ IN UINTN Length,
+ IN UINT16 InitialValue
+ );
+
+/**
+ Calculates the checksum of the given buffer.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] Buffer Pointer to the buffer.
+ @param[in] Length Length of the buffer, in bytes.
+ @param[in] InitialValue Initial value of the CRC.
+
+ @return The checksum.
+*/
+UINT32
+Ext4CalculateChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST VOID *Buffer,
+ IN UINTN Length,
+ IN UINT32 InitialValue
+ );
+
+/**
+ Calculates the checksum of the given inode.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] Inode Pointer to the inode.
+ @param[in] InodeNum Inode number.
+
+ @return The checksum.
+*/
+UINT32
+Ext4CalculateInodeChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_INODE *Inode,
+ IN EXT4_INO_NR InodeNum
+ );
+
+/**
+ Checks if the checksum of the inode is correct.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] Inode Pointer to the inode.
+ @param[in] InodeNum Inode number.
+
+ @return TRUE if checksum is correct, FALSE if there is corruption.
+*/
+BOOLEAN
+Ext4CheckInodeChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_INODE *Inode,
+ IN EXT4_INO_NR InodeNum
+ );
+
+/**
+ Unmounts and frees an ext4 partition.
+
+ @param[in] Partition Pointer to the opened partition.
+
+ @return Status of the unmount.
+ */
+EFI_STATUS
+Ext4UnmountAndFreePartition (
+ IN EXT4_PARTITION *Partition
+ );
+
+/**
+ Checks if the checksum of the block group descriptor is correct.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.
+ @param[in] BlockGroupNum Number of the block group.
+
+ @return TRUE if checksum is correct, FALSE if there is corruption.
+*/
+BOOLEAN
+Ext4VerifyBlockGroupDescChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
+ IN UINT32 BlockGroupNum
+ );
+
+/**
+ Calculates the checksum of the block group descriptor.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.
+ @param[in] BlockGroupNum Number of the block group.
+
+ @return The checksum.
+*/
+UINT16
+Ext4CalculateBlockGroupDescChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,
+ IN UINT32 BlockGroupNum
+ );
+
+/**
+ Verifies the existance of a particular RO compat feature set.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] RoCompatFeatureSet Feature set to test.
+
+ @return TRUE if all features are supported, else FALSE.
+*/
+STATIC inline
+BOOLEAN
+Ext4HasRoCompat (
+ IN CONST EXT4_PARTITION *Partition,
+ IN UINT32 RoCompatFeatureSet
+ )
+{
+ return (Partition->FeaturesRoCompat & RoCompatFeatureSet) == RoCompatFeatureSet;
+}
+
+/**
+ Verifies the existance of a particular compat feature set.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] RoCompatFeatureSet Feature set to test.
+
+ @return TRUE if all features are supported, else FALSE.
+*/
+STATIC inline
+BOOLEAN
+Ext4HasCompat (
+ IN CONST EXT4_PARTITION *Partition,
+ IN UINT32 CompatFeatureSet
+ )
+{
+ return (Partition->FeaturesCompat & CompatFeatureSet) == CompatFeatureSet;
+}
+
+/**
+ Verifies the existance of a particular compat feature set.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] RoCompatFeatureSet Feature set to test.
+
+ @return TRUE if all features are supported, else FALSE.
+*/
+STATIC inline
+BOOLEAN
+Ext4HasIncompat (
+ IN CONST EXT4_PARTITION *Partition,
+ IN UINT32 IncompatFeatureSet
+ )
+{
+ return (Partition->FeaturesIncompat & IncompatFeatureSet) == IncompatFeatureSet;
+}
+
+// Note: Might be a good idea to provide generic Ext4Has$feature() through macros.
+
+/**
+ Checks if metadata_csum is enabled on the partition.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] RoCompatFeatureSet Feature set to test.
+
+ @return TRUE if the feature is supported, else FALSE.
+*/
+STATIC inline
+BOOLEAN
+Ext4HasMetadataCsum (
+ IN CONST EXT4_PARTITION *Partition
+ )
+{
+ return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
+}
+
+/**
+ Checks if gdt_csum is enabled on the partition.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] RoCompatFeatureSet Feature set to test.
+
+ @return TRUE if the feature is supported, else FALSE.
+*/
+STATIC inline
+BOOLEAN
+Ext4HasGdtCsum (
+ IN CONST EXT4_PARTITION *Partition
+ )
+{
+ return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
+}
+
+#endif
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
new file mode 100644
index 0000000000..102b12d613
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
@@ -0,0 +1,147 @@
+## @file
+# Ext4 Package
+#
+# UEFI Driver that produces the Simple File System Protocol for a partition that is formatted
+# with the EXT4 file system.
+# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
+#
+# Copyright (c) 2021 Pedro Falcato
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+# Layout of an EXT2/3/4 filesystem:
+# (note: this driver has been developed using
+# https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as
+# documentation).
+#
+# An ext2/3/4 filesystem (here on out referred to as simply an ext4 filesystem,
+# due to the similarities) is composed of various concepts:
+#
+# 1) Superblock
+# The superblock is the structure near (1024 bytes offset from the start)
+# the start of the partition, and describes the filesystem in general.
+# Here, we get to know the size of the filesystem's blocks, which features
+# it supports or not, whether it's been cleanly unmounted, how many blocks
+# we have, etc.
+#
+# 2) Block groups
+# EXT4 filesystems are divided into block groups, and each block group covers
+# s_blocks_per_group(8 * Block Size) blocks. Each block group has an
+# associated block group descriptor; these are present directly after the
+# superblock. Each block group descriptor contains the location of the
+# inode table, and the inode and block bitmaps (note these bitmaps are only
+# a block long, which gets us the 8 * Block Size formula covered previously).
+#
+# 3) Blocks
+# The ext4 filesystem is divided in blocks, of size s_log_block_size ^ 1024.
+# Blocks can be allocated using individual block groups's bitmaps. Note
+# that block 0 is invalid and its presence on extents/block tables means
+# it's part of a file hole, and that particular location must be read as
+# a block full of zeros.
+#
+# 4) Inodes
+# The ext4 filesystem divides files/directories into inodes (originally
+# index nodes). Each file/socket/symlink/directory/etc (here on out referred
+# to as a file, since there is no distinction under the ext4 filesystem) is
+# stored as a /nameless/ inode, that is stored in some block group's inode
+# table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if it's
+# an old filesystem), and holds various metadata about the file. Since the
+# largest inode structure right now is ~160 bytes, the rest of the inode
+# contains inline extended attributes. Inodes' data is stored using either
+# data blocks (under ext2/3) or extents (under ext4).
+#
+# 5) Extents
+# Ext4 inodes store data in extents. These let N contiguous logical blocks
+# that are represented by N contiguous physical blocks be represented by a
+# single extent structure, which minimizes filesystem metadata bloat and
+# speeds up block mapping (particularly due to the fact that high-quality
+# ext4 implementations like linux's try /really/ hard to make the file
+# contiguous, so it's common to have files with almost 0 fragmentation).
+# Inodes that use extents store them in a tree, and the top of the tree
+# is stored on i_data. The tree's leaves always start with an
+# EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth != 0 and
+# EXT4_EXTENT on eh_depth = 0; these entries are always sorted by logical
+# block.
+#
+# 6) Directories
+# Ext4 directories are files that store name -> inode mappings for the
+# logical directory; this is where files get their names, which means ext4
+# inodes do not themselves have names, since they can be linked (present)
+# multiple times with different names. Directories can store entries in two
+# different ways:
+# 1) Classical linear directories: They store entries as a mostly-linked
+# mostly-list of EXT4_DIR_ENTRY.
+# 2) Hash tree directories: These are used for larger directories, with
+# hundreds of entries, and are designed in a backwards compatible way.
+# These are not yet implemented in the Ext4Dxe driver.
+#
+# 7) Journal
+# Ext3/4 filesystems have a journal to help protect the filesystem against
+# system crashes. This is not yet implemented in Ext4Dxe but is described
+# in detail in the Linux kernel's documentation.
+##
+
+
+[Defines]
+ INF_VERSION = 0x00010005
+ BASE_NAME = Ext4
+ MODULE_UNI_FILE = Ext4Dxe.uni
+ FILE_GUID = 75F2B676-D73B-45CB-B7C1-303C7F4E6FD6
+ MODULE_TYPE = UEFI_DRIVER
+ VERSION_STRING = 1.0
+
+ ENTRY_POINT = Ext4EntryPoint
+ UNLOAD_IMAGE = Ext4Unload
+
+#
+# The following information is for reference only and not required by the build tools.
+#
+# VALID_ARCHITECTURES = IA32 X64 EBC
+#
+
+[Sources]
+ Ext4Dxe.c
+ Partition.c
+ DiskUtil.c
+ Superblock.c
+ BlockGroup.c
+ Inode.c
+ Directory.c
+ Extents.c
+ File.c
+ Collation.c
+ Crc32c.c
+ Crc16.c
+
+[Packages]
+ MdePkg/MdePkg.dec
+ RedfishPkg/RedfishPkg.dec
+
+[LibraryClasses]
+ UefiRuntimeServicesTableLib
+ UefiBootServicesTableLib
+ MemoryAllocationLib
+ BaseMemoryLib
+ BaseLib
+ UefiLib
+ UefiDriverEntryPoint
+ DebugLib
+ PcdLib
+ OrderedCollectionLib
+ BaseUcs2Utf8Lib
+
+[Guids]
+ gEfiFileInfoGuid ## SOMETIMES_CONSUMES ## UNDEFINED
+ gEfiFileSystemInfoGuid ## SOMETIMES_CONSUMES ## UNDEFINED
+ gEfiFileSystemVolumeLabelInfoIdGuid ## SOMETIMES_CONSUMES ## UNDEFINED
+
+[Protocols]
+ gEfiDiskIoProtocolGuid ## TO_START
+ gEfiDiskIo2ProtocolGuid ## TO_START
+ gEfiBlockIoProtocolGuid ## TO_START
+ gEfiSimpleFileSystemProtocolGuid ## BY_START
+ gEfiUnicodeCollationProtocolGuid ## TO_START
+ gEfiUnicodeCollation2ProtocolGuid ## TO_START
+
+[Pcd]
+ gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultLang ## SOMETIMES_CONSUMES
+ gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLang ## SOMETIMES_CONSUMES
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
new file mode 100644
index 0000000000..7476fbf9bd
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
@@ -0,0 +1,15 @@
+## @file
+# Ext4 Package
+#
+# UEFI Driver that produces the Simple File System Protocol for a partition that is formatted
+# with the EXT4 file system.
+# More details are available at: https://www.kernel.org/doc/html/v5.4/filesystems/ext4/index.html
+#
+# Copyright (c) 2021 Pedro Falcato
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+#string STR_MODULE_ABSTRACT #language en-US "UEFI driver for the EXT4 file system."
+
+#string STR_MODULE_DESCRIPTION #language en-US "Produces the EFI Simple File System protocol."
diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/Extents.c
new file mode 100644
index 0000000000..db4bf5aa3f
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
@@ -0,0 +1,616 @@
+/**
+ @file Extent related routines
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+/**
+ Checks if the checksum of the extent data block is correct.
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
+ @param[in] File Pointer to the file.
+
+ @return TRUE if the checksum is correct, FALSE if there is corruption.
+*/
+BOOLEAN
+Ext4CheckExtentChecksum (
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,
+ IN CONST EXT4_FILE *File
+ );
+
+/**
+ Calculates the checksum of the extent data block.
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
+ @param[in] File Pointer to the file.
+
+ @return The checksum.
+*/
+UINT32
+Ext4CalculateExtentChecksum (
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,
+ IN CONST EXT4_FILE *File
+ );
+
+/**
+ Caches a range of extents, by allocating pool memory for each extent and adding it to the tree.
+
+ @param[in] File Pointer to the open file.
+ @param[in] Extents Pointer to an array of extents.
+ @param[in] NumberExtents Length of the array.
+*/
+VOID
+Ext4CacheExtents (
+ IN EXT4_FILE *File,
+ IN CONST EXT4_EXTENT *Extents,
+ IN UINT16 NumberExtents
+ );
+
+/**
+ Gets an extent from the extents cache of the file.
+
+ @param[in] File Pointer to the open file.
+ @param[in] Block Block we want to grab.
+
+ @return Pointer to the extent, or NULL if it was not found.
+*/
+EXT4_EXTENT *
+Ext4GetExtentFromMap (
+ IN EXT4_FILE *File,
+ IN UINT32 Block
+ );
+
+/**
+ Retrieves the pointer to the top of the extent tree.
+ @param[in] Inode Pointer to the inode structure.
+
+ @return Pointer to an EXT4_EXTENT_HEADER. This pointer is inside
+ the inode and must not be freed.
+*/
+STATIC
+EXT4_EXTENT_HEADER *
+Ext4GetInoExtentHeader (
+ IN EXT4_INODE *Inode
+ )
+{
+ return (EXT4_EXTENT_HEADER *)Inode->i_data;
+}
+
+/**
+ Checks if an extent header is valid.
+ @param[in] Header Pointer to the EXT4_EXTENT_HEADER structure.
+
+ @return TRUE if valid, FALSE if not.
+*/
+STATIC
+BOOLEAN
+Ext4ExtentHeaderValid (
+ IN CONST EXT4_EXTENT_HEADER *Header
+ )
+{
+ if(Header->eh_depth > EXT4_EXTENT_TREE_MAX_DEPTH) {
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header depth %u\n", Header->eh_depth));
+ return FALSE;
+ }
+
+ if(Header->eh_magic != EXT4_EXTENT_HEADER_MAGIC) {
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header magic %x\n", Header->eh_magic));
+ return FALSE;
+ }
+
+ if(Header->eh_max < Header->eh_entries) {
+ DEBUG ((
+ EFI_D_ERROR,
+ "[ext4] Invalid extent header num entries %u max entries %u\n",
+ Header->eh_entries,
+ Header->eh_max
+ ));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ Performs a binary search for a EXT4_EXTENT_INDEX that corresponds to a
+ logical block in a given extent tree node.
+
+ @param[in] Header Pointer to the EXT4_EXTENT_HEADER structure.
+ @param[in] LogicalBlock Block that will be searched
+
+ @return Pointer to the found EXT4_EXTENT_INDEX.
+*/
+STATIC
+EXT4_EXTENT_INDEX *
+Ext4BinsearchExtentIndex (
+ IN EXT4_EXTENT_HEADER *Header,
+ IN EXT4_BLOCK_NR LogicalBlock
+ )
+{
+ EXT4_EXTENT_INDEX *l;
+ EXT4_EXTENT_INDEX *r;
+ EXT4_EXTENT_INDEX *m;
+
+ l = ((EXT4_EXTENT_INDEX *)(Header + 1)) + 1;
+ r = ((EXT4_EXTENT_INDEX *)(Header + 1)) + Header->eh_entries - 1;
+
+ // Perform a mostly-standard binary search on the array
+ // This works very nicely because the extents arrays are always sorted.
+
+ while(l <= r) {
+ m = l + (r - l) / 2;
+
+ if(LogicalBlock < m->ei_block) {
+ r = m - 1;
+ } else {
+ l = m + 1;
+ }
+ }
+
+ return l - 1;
+}
+
+/**
+ Performs a binary search for a EXT4_EXTENT that corresponds to a
+ logical block in a given extent tree node.
+
+ @param[in] Header Pointer to the EXT4_EXTENT_HEADER structure.
+ @param[in] LogicalBlock Block that will be searched
+
+ @return Pointer to the found EXT4_EXTENT_INDEX, else NULL if the array is empty.
+ Note: The caller must check if the logical block
+ is actually mapped under the given extent.
+*/
+STATIC
+EXT4_EXTENT *
+Ext4BinsearchExtentExt (
+ IN EXT4_EXTENT_HEADER *Header,
+ IN EXT4_BLOCK_NR LogicalBlock
+ )
+{
+ EXT4_EXTENT *l;
+ EXT4_EXTENT *r;
+ EXT4_EXTENT *m;
+
+ l = ((EXT4_EXTENT *)(Header + 1)) + 1;
+ r = ((EXT4_EXTENT *)(Header + 1)) + Header->eh_entries - 1;
+ // Perform a mostly-standard binary search on the array
+ // This works very nicely because the extents arrays are always sorted.
+
+ // Empty array
+ if(Header->eh_entries == 0) {
+ return NULL;
+ }
+
+ while(l <= r) {
+ m = l + (r - l) / 2;
+
+ if(LogicalBlock < m->ee_block) {
+ r = m - 1;
+ } else {
+ l = m + 1;
+ }
+ }
+
+ return l - 1;
+}
+
+/**
+ Retrieves the leaf block from an EXT4_EXTENT_INDEX.
+
+ @param[in] Index Pointer to the EXT4_EXTENT_INDEX structure.
+
+ @return Block number of the leaf node.
+*/
+STATIC
+EXT4_BLOCK_NR
+Ext4ExtentIdxLeafBlock (
+ IN EXT4_EXTENT_INDEX *Index
+ )
+{
+ return ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;
+}
+
+STATIC UINTN GetExtentRequests = 0;
+STATIC UINTN GetExtentCacheHits = 0;
Private globals are defined with "m" ("module") and public ones with "g".

+
+/**
+ Retrieves an extent from an EXT4 inode.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] File Pointer to the opened file.
+ @param[in] LogicalBlock Block number which the returned extent must cover.
+ @param[out] Extent Pointer to the output buffer, where the extent will be copied to.
+
+ @retval EFI_SUCCESS Retrieval was succesful.
+ @retval EFI_NO_MAPPING Block has no mapping.
+*/
+EFI_STATUS
+Ext4GetExtent (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_FILE *File,
+ IN EXT4_BLOCK_NR LogicalBlock,
+ OUT EXT4_EXTENT *Extent
+ )
+{
+ EXT4_INODE *Inode;
+ VOID *Buffer;
+ EXT4_EXTENT *Ext;
+ UINT32 CurrentDepth;
+ EXT4_EXTENT_HEADER *ExtHeader;
+
+ Inode = File->Inode;
+ Ext = NULL;
+ Buffer = NULL;
+
+ DEBUG ((EFI_D_INFO, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
+
+ if (!(Inode->i_flags & EXT4_EXTENTS_FL)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ // ext4 does not have support for logical block numbers bigger than UINT32_MAX
+ if (LogicalBlock > (UINT32)- 1) {
+ return EFI_NO_MAPPING;
+ }
+
+ GetExtentRequests++;
+ #if DEBUG_EXTENT_CACHE
+ DEBUG ((
+ EFI_D_INFO,
+ "[ext4] Requests %lu, hits %lu, misses %lu\n",
+ GetExtentRequests,
+ GetExtentCacheHits,
+ GetExtentRequests - GetExtentCacheHits
+ ));
+ #endif
+
+ // Note: Right now, holes are the single biggest reason for cache misses
+ // We should find a way to get (or cache) holes
+ if ((Ext = Ext4GetExtentFromMap (File, (UINT32)LogicalBlock)) != NULL) {
+ *Extent = *Ext;
+ GetExtentCacheHits++;
+
+ return EFI_SUCCESS;
+ }
+
+ // Slow path, we'll need to read from disk and (try to) cache those extents.
+
+ ExtHeader = Ext4GetInoExtentHeader (Inode);
+
+ if(!Ext4ExtentHeaderValid (ExtHeader)) {
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ CurrentDepth = ExtHeader->eh_depth;
+
+ while(ExtHeader->eh_depth != 0) {
+ EXT4_EXTENT_INDEX *Index;
+ EFI_STATUS Status;
+
+ CurrentDepth--;
+ // While depth != 0, we're traversing the tree itself and not any leaves
+ // As such, every entry is an EXT4_EXTENT_INDEX entry
+ // Note: Entries after the extent header, either index or actual extent, are always sorted.
+ // Therefore, we can use binary search, and it's actually the standard for doing so
+ // (see FreeBSD).
+
+ Index = Ext4BinsearchExtentIndex (ExtHeader, LogicalBlock);
+
+ if(Buffer == NULL) {
+ Buffer = AllocatePool (Partition->BlockSize);
+ if(Buffer == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+ }
+
+ // Read the leaf block onto the previously-allocated buffer.
+
+ Status = Ext4ReadBlocks (Partition, Buffer, 1, Ext4ExtentIdxLeafBlock (Index));
+ if(EFI_ERROR (Status)) {
+ FreePool (Buffer);
+ return Status;
+ }
+
+ ExtHeader = Buffer;
+
+ if(!Ext4ExtentHeaderValid (ExtHeader)) {
+ FreePool (Buffer);
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ if(!Ext4CheckExtentChecksum (ExtHeader, File)) {
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent checksum\n"));
+ FreePool (Buffer);
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ if(ExtHeader->eh_depth != CurrentDepth) {
+ FreePool (Buffer);
+ return EFI_VOLUME_CORRUPTED;
+ }
+ }
+
+ /* We try to cache every extent under a single leaf, since it's quite likely that we
+ * may need to access things sequentially. Furthermore, ext4 block allocation as done
+ * by linux (and possibly other systems) is quite fancy and usually it results in a small number of extents.
+ * Therefore, we shouldn't have any memory issues.
+ */
+ Ext4CacheExtents (File, (EXT4_EXTENT *)(ExtHeader + 1), ExtHeader->eh_entries);
+
+ Ext = Ext4BinsearchExtentExt (ExtHeader, LogicalBlock);
+
+ if(!Ext) {
+ if(Buffer != NULL) {
+ FreePool (Buffer);
+ }
+
+ return EFI_NO_MAPPING;
+ }
+
+ if(!(LogicalBlock >= Ext->ee_block && Ext->ee_block + Ext->ee_len > LogicalBlock)) {
+ // This extent does not cover the block
+ if(Buffer != NULL) {
+ FreePool (Buffer);
+ }
+
+ return EFI_NO_MAPPING;
+ }
+
+ *Extent = *Ext;
+
+ if(Buffer != NULL) {
+ FreePool (Buffer);
+ }
+
+ return EFI_SUCCESS;
+}
+
+/**
+ Compare two EXT4_EXTENT structs.
+ Used in the extent map's ORDERED_COLLECTION.
+
+ @param[in] UserStruct1 Pointer to the first user structure.
+
+ @param[in] UserStruct2 Pointer to the second user structure.
+
+ @retval <0 If UserStruct1 compares less than UserStruct2.
+
+ @retval 0 If UserStruct1 compares equal to UserStruct2.
+
+ @retval >0 If UserStruct1 compares greater than UserStruct2.
+**/
+STATIC
+INTN EFIAPI
+Ext4ExtentsMapStructCompare (
+ IN CONST VOID *UserStruct1,
+ IN CONST VOID *UserStruct2
+ )
+{
+ CONST EXT4_EXTENT *Extent1 = UserStruct1;
+ CONST EXT4_EXTENT *Extent2 = UserStruct2;
+
+ // TODO: Detect extent overlaps? in case of corruption.
+
+ /* DEBUG((EFI_D_INFO, "[ext4] extent 1 %u extent 2 %u = %ld\n", Extent1->ee_block,
+ Extent2->ee_block, Extent1->ee_block - Extent2->ee_block)); */
+ return Extent1->ee_block < Extent2->ee_block ? - 1 :
+ Extent1->ee_block > Extent2->ee_block ? 1 : 0;
+}
+
+/**
+ Compare a standalone key against a EXT4_EXTENT containing an embedded key.
+ Used in the extent map's ORDERED_COLLECTION.
+
+ @param[in] StandaloneKey Pointer to the bare key.
+
+ @param[in] UserStruct Pointer to the user structure with the embedded
+ key.
+
+ @retval <0 If StandaloneKey compares less than UserStruct's key.
+
+ @retval 0 If StandaloneKey compares equal to UserStruct's key.
+
+ @retval >0 If StandaloneKey compares greater than UserStruct's key.
+**/
+STATIC
+INTN EFIAPI
+Ext4ExtentsMapKeyCompare (
+ IN CONST VOID *StandaloneKey,
+ IN CONST VOID *UserStruct
+ )
+{
+ CONST EXT4_EXTENT *Extent = UserStruct;
+
+ // Note that logical blocks are 32-bits in size so no truncation can happen here
+ // with regards to 32-bit architectures.
+ UINT32 Block = (UINT32)(UINTN)StandaloneKey;
+
+ // DEBUG((EFI_D_INFO, "[ext4] comparing %u %u\n", Block, Extent->ee_block));
+ if(Block >= Extent->ee_block && Block < Extent->ee_block + Extent->ee_len) {
+ return 0;
+ }
+
+ return Block < Extent->ee_block ? - 1 :
+ Block > Extent->ee_block ? 1 : 0;
+}
+
+/**
+ Initialises the (empty) extents map, that will work as a cache of extents.
+
+ @param[in] File Pointer to the open file.
+
+ @return Result of the operation.
+*/
+EFI_STATUS
+Ext4InitExtentsMap (
+ IN EXT4_FILE *File
+ )
+{
+ File->ExtentsMap = OrderedCollectionInit (Ext4ExtentsMapStructCompare, Ext4ExtentsMapKeyCompare);
+ if (!File->ExtentsMap) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ return EFI_SUCCESS;
+}
+
+/**
+ Frees the extents map, deleting every extent stored.
+
+ @param[in] File Pointer to the open file.
+*/
+VOID
+Ext4FreeExtentsMap (
+ IN EXT4_FILE *File
+ )
+{
+ // Keep calling Min(), so we get an arbitrary node we can delete.
+ // If Min() returns NULL, it's empty.
+
+ ORDERED_COLLECTION_ENTRY *MinEntry = NULL;
+
+ while ((MinEntry = OrderedCollectionMin (File->ExtentsMap)) != NULL) {
+ EXT4_EXTENT *Ext;
+ OrderedCollectionDelete (File->ExtentsMap, MinEntry, (VOID **)&Ext);
+ FreePool (Ext);
+ }
+
+ ASSERT (OrderedCollectionIsEmpty (File->ExtentsMap));
+
+ OrderedCollectionUninit (File->ExtentsMap);
+ File->ExtentsMap = NULL;
+}
+
+/**
+ Caches a range of extents, by allocating pool memory for each extent and adding it to the tree.
+
+ @param[in] File Pointer to the open file.
+ @param[in] Extents Pointer to an array of extents.
+ @param[in] NumberExtents Length of the array.
+*/
+VOID
+Ext4CacheExtents (
+ IN EXT4_FILE *File, IN CONST EXT4_EXTENT *Extents, IN UINT16 NumberExtents
+ )
+{
+ UINT16 i;
+
+ /* Note that any out of memory condition might mean we don't get to cache a whole leaf of extents
+ * in which case, future insertions might fail.
+ */
+
+ for(i = 0; i < NumberExtents; i++, Extents++) {
+ EXT4_EXTENT *Extent;
+ EFI_STATUS Status;
+
+ Extent = AllocatePool (sizeof (EXT4_EXTENT));
+
+ if (Extent == NULL) {
+ return;
+ }
+
+ CopyMem (Extent, Extents, sizeof (EXT4_EXTENT));
+ Status = OrderedCollectionInsert (File->ExtentsMap, NULL, Extent);
+
+ // EFI_ALREADY_STARTED = already exists in the tree.
+ if (EFI_ERROR (Status)) {
+ FreePool (Extent);
+
+ if(Status == EFI_ALREADY_STARTED) {
+ continue;
+ }
+
+ return;
+ }
+
+ #if DEBUG_EXTENT_CACHE
+ DEBUG ((
+ EFI_D_INFO,
+ "[ext4] Cached extent [%lu, %lu]\n",
+ Extent->ee_block,
+ Extent->ee_block + Extent->ee_len - 1
+ ));
+ #endif
+ }
+}
+
+/**
+ Gets an extent from the extents cache of the file.
+
+ @param[in] File Pointer to the open file.
+ @param[in] Block Block we want to grab.
+
+ @return Pointer to the extent, or NULL if it was not found.
+*/
+EXT4_EXTENT *
+Ext4GetExtentFromMap (
+ IN EXT4_FILE *File,
+ IN UINT32 Block
+ )
+{
+ ORDERED_COLLECTION_ENTRY *Entry;
+
+ Entry = OrderedCollectionFind (File->ExtentsMap, (CONST VOID *)(UINTN)Block);
+
+ if (Entry == NULL) {
+ return NULL;
+ }
+
+ return OrderedCollectionUserStruct (Entry);
+}
+
+/**
+ Calculates the checksum of the extent data block.
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
+ @param[in] File Pointer to the file.
+
+ @return The checksum.
+*/
+UINT32
+Ext4CalculateExtentChecksum (
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,
+ IN CONST EXT4_FILE *File
+ )
+{
+ UINT32 Csum;
+ EXT4_PARTITION *Partition;
+ EXT4_INODE *Inode;
+
+ Partition = File->Partition;
+ Inode = File->Inode;
+
+ Csum = Ext4CalculateChecksum (Partition, &File->InodeNum, sizeof (EXT4_INO_NR), Partition->InitialSeed);
+ Csum = Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (Inode->i_generation), Csum);
+ Csum = Ext4CalculateChecksum (Partition, ExtHeader, Partition->BlockSize - sizeof (EXT4_EXTENT_TAIL), Csum);
+
+ return Csum;
+}
+
+/**
+ Checks if the checksum of the extent data block is correct.
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.
+ @param[in] File Pointer to the file.
+
+ @return TRUE if the checksum is correct, FALSE if there is corruption.
+*/
+BOOLEAN
+Ext4CheckExtentChecksum (
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,
+ IN CONST EXT4_FILE *File
+ )
+{
+ EXT4_PARTITION *Partition;
+ EXT4_EXTENT_TAIL *Tail;
+
+ Partition = File->Partition;
+
+ if(!Ext4HasMetadataCsum (Partition)) {
+ return TRUE;
+ }
+
+ Tail = (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->BlockSize - 4));
+
+ return Tail->eb_checksum == Ext4CalculateExtentChecksum (ExtHeader, File);
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/File.c
new file mode 100644
index 0000000000..10dda64b16
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/File.c
@@ -0,0 +1,583 @@
+/**
+ @file EFI_FILE_PROTOCOL implementation for EXT4
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+#include <Library/BaseUcs2Utf8Lib.h>
+
+/**
+ Duplicates a file structure.
+
+ @param[in] Original Pointer to the original file.
+
+ @return Pointer to the new file structure.
+ */
+STATIC
+EXT4_FILE *
+Ext4DuplicateFile (
+ IN CONST EXT4_FILE *Original
+ );
+
+/**
+ Gets the next path segment.
+
+ @param[in] Path Pointer to the rest of the path.
+ @param[out] PathSegment Pointer to the buffer that will hold the path segment.
+ Note: It's necessarily EXT4_NAME_MAX +1 long.
+ @param[out] Length Pointer to the UINTN that will hold the length of the path segment.
+
+ @retval !EFI_SUCCESS The path segment is too large(> EXT4_NAME_MAX).
+ */
+STATIC
+EFI_STATUS
+GetPathSegment (
+ IN CONST CHAR16 *Path, OUT CHAR16 *PathSegment, OUT UINTN *Length
+ )
+{
+ CONST CHAR16 *Start = Path;
+ CONST CHAR16 *End = Path;
+
+ // The path segment ends on a backslash or a null terminator
+ for( ; *End != L'\0' && *End != L'\\'; End++) {
+ }
+
+ *Length = End - Start;
+
+ return StrnCpyS (PathSegment, EXT4_NAME_MAX, Start, End - Start);
Why not EXT4_NAME_MAX + 1?

+}
+
+/**
+ Detects if we have more path segments on the path.
+
+ @param[in] Path Pointer to the rest of the path.
+ @return True if we're on the last segment, false if there are more
+ segments.
+ */
+STATIC
+BOOLEAN
+Ext4IsLastPathSegment (
+ IN CONST CHAR16 *Path
+ )
+{
+ while(Path[0] == L'\\') {
+ Path++;
+ }
+
+ return Path[0] == '\0';
+}
+
+#define EXT4_INO_PERM_READ_OWNER 0400
+#define EXT4_INO_PERM_WRITE_OWNER 0200
+#define EXT4_INO_PERM_EXEC_OWNER 0100
+
+/**
+ Detects if we have permissions to open the file on the desired mode.
+
+ @param[in out] File Pointer to the file we're opening.
+ @param[in] OpenMode Mode in which to open the file.
+
+ @return True if the open was succesful, false if we don't have
+ enough permissions.
+ */
+STATIC
+BOOLEAN
+Ext4ApplyPermissions (
+ IN OUT EXT4_FILE *File, IN UINT64 OpenMode
+ )
+{
+ UINT16 NeededPerms = 0;
+
+ if((OpenMode & EFI_FILE_MODE_READ) != 0) {
+ NeededPerms |= EXT4_INO_PERM_READ_OWNER;
+ }
+
+ if((OpenMode & EFI_FILE_MODE_WRITE) != 0) {
+ NeededPerms |= EXT4_INO_PERM_WRITE_OWNER;
+ }
+
+ if((File->Inode->i_mode & NeededPerms) != NeededPerms) {
+ return FALSE;
+ }
+
+ File->OpenMode = OpenMode;
+
+ return TRUE;
+}
+
+/**
+ Detects if we have permissions to search on the directory.
+
+ @param[in out] File Pointer to the open directory.
+
+ @return True if we have permission to search, else false.
+ */
+STATIC
+BOOLEAN
+Ext4DirCanLookup (
+ IN CONST EXT4_FILE *File
+ )
+{
+ // In UNIX, executable permission on directories means that we have permission to look up
+ // files in a directory.
+ return (File->Inode->i_mode & EXT4_INO_PERM_EXEC_OWNER) == EXT4_INO_PERM_EXEC_OWNER;
+}
+
+EFI_STATUS EFIAPI
+Ext4Open (
+ IN EFI_FILE_PROTOCOL *This,
+ OUT EFI_FILE_PROTOCOL **NewHandle,
+ IN CHAR16 *FileName,
+ IN UINT64 OpenMode,
+ IN UINT64 Attributes
+ )
+{
+ EXT4_FILE *Current;
+ EXT4_PARTITION *Partition;
+ UINTN Level;
+
+ Current = (EXT4_FILE *)This;
+ Partition = Current->Partition;
+ Level = 0;
+
+ DEBUG ((EFI_D_INFO, "[ext4] Ext4Open %s\n", FileName));
+ // If the path starts with a backslash, we treat the root directory as the base directory
+ if(FileName[0] == L'\\') {
+ FileName++;
+ Current = Partition->Root;
+ }
+
+ while(FileName[0] != L'\0') {
+ CHAR16 PathSegment[EXT4_NAME_MAX + 1];
+ UINTN Length;
+ EXT4_FILE *File;
+ EFI_STATUS Status;
+
+ // Discard leading path separators
+ while(FileName[0] == L'\\') {
+ FileName++;
+ }
+
+ if(GetPathSegment (FileName, PathSegment, &Length) != EFI_SUCCESS) {
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ // Reached the end of the path
+ if(Length == 0) {
+ break;
+ }
+
+ FileName += Length;
+
+ DEBUG ((EFI_D_INFO, "[ext4] Opening %s\n", PathSegment));
+
+ // TODO: What to do with symlinks? They're nonsense when absolute but may
+ // be useful when they're relative.
+
+ if (!Ext4FileIsDir (Current)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (!Ext4IsLastPathSegment (FileName)) {
+ if (!Ext4DirCanLookup (Current)) {
+ return EFI_ACCESS_DENIED;
+ }
+ }
+
+ Status = Ext4OpenFile (Current, PathSegment, Partition, EFI_FILE_MODE_READ, &File);
+
+ if(EFI_ERROR (Status) && Status != EFI_NOT_FOUND) {
+ return Status;
+ } else if(Status == EFI_NOT_FOUND) {
+ // WRITE SUPPORT: Handle file creation when write support is added
+ return Status;
+ }
+
+ // Check if this is a valid file to open in EFI
+ if(!Ext4FileIsOpenable (File)) {
+ Ext4CloseInternal (File);
+ // This looks like an /okay/ status to return.
+ return EFI_ACCESS_DENIED;
+ }
+
+ if(Level != 0) {
+ // Careful not to close the base directory
+ Ext4CloseInternal (Current);
+ }
+
+ Level++;
+
+ Current = File;
+ }
+
+ if (Level == 0) {
+ // We opened the base directory again, so we need to duplicate the file structure
+ Current = Ext4DuplicateFile (Current);
+ if (Current == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+ }
+
+ if(!Ext4ApplyPermissions (Current, OpenMode)) {
+ Ext4CloseInternal (Current);
+ return EFI_ACCESS_DENIED;
+ }
+
+ *NewHandle = &Current->Protocol;
+
+ DEBUG ((EFI_D_INFO, "Open successful\n"));
+ DEBUG ((EFI_D_INFO, "Opened filename %s\n", Current->FileName));
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS EFIAPI
+Ext4Close (
+ IN EFI_FILE_PROTOCOL *This
+ )
+{
+ return Ext4CloseInternal ((EXT4_FILE *)This);
+}
+
+/**
+ Closes a file.
+
+ @param[in] File Pointer to the file.
+
+ @return Status of the closing of the file.
+ */
+EFI_STATUS
+Ext4CloseInternal (
+ IN EXT4_FILE *File
+ )
+{
+ if(File == File->Partition->Root && !File->Partition->Unmounting) {
+ return EFI_SUCCESS;
+ }
+
+ DEBUG ((EFI_D_INFO, "[ext4] Closed file %p (inode %lu)\n", File, File->InodeNum));
+ RemoveEntryList (&File->OpenFilesListNode);
+ FreePool (File->FileName);
+ FreePool (File->Inode);
+ Ext4FreeExtentsMap (File);
+ FreePool (File);
+ return EFI_SUCCESS;
As this function can only ever return success, and all direct uses discard the status anyway, in my opinion make it VOID and return success in Ext3Close().

+}
+
+EFI_STATUS EFIAPI
+Ext4Delete (
+ IN EFI_FILE_PROTOCOL *This
+ )
+{
+ // Write support
+ Ext4Close (This);
+ return EFI_WARN_DELETE_FAILURE;
+}
+
+EFI_STATUS
+EFIAPI
+Ext4ReadFile (
+ IN EFI_FILE_PROTOCOL *This,
+ IN OUT UINTN *BufferSize,
+ OUT VOID *Buffer
+ )
+{
+ EXT4_FILE *File;
+ EXT4_PARTITION *Partition;
+ EFI_STATUS Status;
+
+ File = (EXT4_FILE *)This;
+ Partition = File->Partition;
+
+ ASSERT (Ext4FileIsOpenable (File));
+
+ if(Ext4FileIsReg (File)) {
+ Status = Ext4Read (Partition, File, Buffer, File->Position, BufferSize);
+ if(Status == EFI_SUCCESS) {
+ File->Position += *BufferSize;
+ }
+
+ return Status;
+ } else if(Ext4FileIsDir (File)) {
+ Status = Ext4ReadDir (Partition, File, Buffer, File->Position, BufferSize);
+ DEBUG ((EFI_D_INFO, "[ext4] ReadDir status %lx\n", Status));
+
+ if(Status == EFI_SUCCESS) {
+ DEBUG ((EFI_D_INFO, "[ext4] ReadDir retlen %lu\n", *BufferSize));
+ }
+
+ return Status;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+Ext4WriteFile (
+ IN EFI_FILE_PROTOCOL *This,
+ IN OUT UINTN *BufferSize,
+ IN VOID *Buffer
+ )
+{
+ EXT4_FILE *File = (EXT4_FILE *)This;
+
+ if(!(File->OpenMode & EFI_FILE_MODE_WRITE)) {
+ return EFI_ACCESS_DENIED;
+ }
+
+ // TODO: Add write support
+ return EFI_WRITE_PROTECTED;
+}
+
+EFI_STATUS
+EFIAPI
+Ext4GetPosition (
+ IN EFI_FILE_PROTOCOL *This,
+ OUT UINT64 *Position
+ )
+{
+ EXT4_FILE *File = (EXT4_FILE *)This;
+
+ if(Ext4FileIsDir (File)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ *Position = File->Position;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+Ext4SetPosition (
+ IN EFI_FILE_PROTOCOL *This,
+ IN UINT64 Position
+ )
+{
+ EXT4_FILE *File = (EXT4_FILE *)This;
+
+ // Only seeks to 0 (so it resets the ReadDir operation) are allowed
+ if(Ext4FileIsDir (File) && Position != 0) {
+ return EFI_UNSUPPORTED;
+ }
+
+ // -1 (0xffffff.......) seeks to the end of the file
+ if(Position == (UINT64)- 1) {
MAX_UINT64?

+ Position = EXT4_INODE_SIZE (File->Inode);
+ }
+
+ File->Position = Position;
+
+ return EFI_SUCCESS;
+}
+
+/**
+ Retrieves information about the file and stores it in the EFI_FILE_INFO format.
+
+ @param[in] File Pointer to an opened file.
+ @param[out] Info Pointer to a EFI_FILE_INFO.
+ @param[in out] BufferSize Pointer to the buffer size
+
+ @return Status of the file information request.
+*/
+EFI_STATUS
+Ext4GetFileInfo (
+ IN EXT4_FILE *File, OUT EFI_FILE_INFO *Info, IN OUT UINTN *BufferSize
+ )
+{
+ // TODO: Get a way to set the directory entry for SetFileInfo
+ UINTN FileNameLen = StrLen (File->FileName);
+ UINTN FileNameSize = StrSize (File->FileName);
+
+ UINTN NeededLength = SIZE_OF_EFI_FILE_INFO + FileNameSize;
+
+ if(*BufferSize < NeededLength) {
+ *BufferSize = NeededLength;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ Info->FileSize = EXT4_INODE_SIZE (File->Inode);
+ Info->PhysicalSize = Ext4FilePhysicalSpace (File);
+ Ext4FileATime (File, &Info->LastAccessTime);
+ Ext4FileMTime (File, &Info->ModificationTime);
+ Ext4FileCreateTime (File, &Info->LastAccessTime);
+ Info->Attribute = 0;
+ Info->Size = NeededLength;
+
+ if(Ext4FileIsDir (File)) {
+ Info->Attribute |= EFI_FILE_DIRECTORY;
+ }
+
+ *BufferSize = NeededLength;
+
+ return StrCpyS (Info->FileName, FileNameLen + 1, File->FileName);
+}
+
+/**
+ Retrieves information about the filesystem and stores it in the EFI_FILE_SYSTEM_INFO format.
+
+ @param[in] File Pointer to an opened file.
+ @param[out] Info Pointer to a EFI_FILE_SYSTEM_INFO.
+ @param[in out] BufferSize Pointer to the buffer size
+
+ @return Status of the file information request.
+*/
+STATIC
+EFI_STATUS
+Ext4GetFilesystemInfo (
+ IN EXT4_PARTITION *Part, OUT EFI_FILE_SYSTEM_INFO *Info, IN OUT UINTN *BufferSize
+ )
+{
+ // Length of s_volume_name + null terminator
+ CHAR8 TempVolName[16 + 1];
+ CHAR16 *VolumeName;
+ UINTN VolNameLength;
+ EFI_STATUS Status;
+ UINTN NeededLength;
+ EXT4_BLOCK_NR TotalBlocks;
+ EXT4_BLOCK_NR FreeBlocks;
+
+ VolNameLength = 0;
+ VolumeName = NULL;
+
+ // s_volume_name is only valid on dynamic revision; old filesystems don't support this
+ if(Part->SuperBlock.s_rev_level == EXT4_DYNAMIC_REV) {
+ CopyMem (TempVolName, (CONST CHAR8 *)Part->SuperBlock.s_volume_name, 16);
+ TempVolName[16] = '\0';
+
+ Status = UTF8StrToUCS2 (TempVolName, &VolumeName);
+
+ if(EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ VolNameLength = StrLen (VolumeName);
+ }
+
+ NeededLength = SIZE_OF_EFI_FILE_SYSTEM_INFO;
+
+ if (VolumeName != NULL) {
+ NeededLength += StrSize (VolumeName);
+ } else {
+ // If we don't have a volume name, we set VolumeLabel to a single null terminator
+ NeededLength += sizeof (CHAR16);
+ }
+
+ if(*BufferSize < NeededLength) {
+ *BufferSize = NeededLength;
+
+ if (VolumeName != NULL) {
+ FreePool (VolumeName);
+ }
+
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ TotalBlocks = Part->NumberBlocks;
+
+ FreeBlocks = Ext4MakeBlockNumberFromHalfs (
+ Part,
+ Part->SuperBlock.s_free_blocks_count,
+ Part->SuperBlock.s_free_blocks_count_hi
+ );
+
+ Info->BlockSize = Part->BlockSize;
+ Info->Size = NeededLength;
+ Info->ReadOnly = Part->ReadOnly;
+ Info->VolumeSize = TotalBlocks * Part->BlockSize;
+ Info->FreeSpace = FreeBlocks * Part->BlockSize;
+
+ if (VolumeName != NULL) {
+ StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
+ } else {
+ Info->VolumeLabel[0] = L'\0';
+ }
+
+ if (VolumeName != NULL) {
+ FreePool (VolumeName);
+ }
+
+ *BufferSize = NeededLength;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+Ext4GetInfo (
+ IN EFI_FILE_PROTOCOL *This,
+ IN EFI_GUID *InformationType,
+ IN OUT UINTN *BufferSize,
+ OUT VOID *Buffer
+ )
+{
+ if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
+ return Ext4GetFileInfo ((EXT4_FILE *)This, Buffer, BufferSize);
+ }
+
+ if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
+ return Ext4GetFilesystemInfo (((EXT4_FILE *)This)->Partition, Buffer, BufferSize);
+ }
+
+ return EFI_UNSUPPORTED;
+}
+
+/**
+ Duplicates a file structure.
+
+ @param[in] Original Pointer to the original file.
+
+ @return Pointer to the new file structure.
+ */
+STATIC
+EXT4_FILE *
+Ext4DuplicateFile (
+ IN CONST EXT4_FILE *Original
+ )
+{
+ EXT4_PARTITION *Partition;
+ EXT4_FILE *File;
+
+ Partition = Original->Partition;
+ File = AllocateZeroPool (sizeof (EXT4_FILE));
+
+ if (File == NULL) {
+ return NULL;
+ }
+
+ File->Inode = Ext4AllocateInode (Partition);
+ if (File->Inode == NULL) {
+ FreePool (File);
+ return NULL;
+ }
+
+ CopyMem (File->Inode, Original->Inode, Partition->InodeSize);
+
+ File->FileName = AllocateZeroPool (StrSize (Original->FileName));
+ if (File->FileName == NULL) {
+ FreePool (File->Inode);
+ FreePool (File);
+ return NULL;
+ }
+
+ StrCpyS (File->FileName, StrLen (Original->FileName) + 1, Original->FileName);
+
+ File->Position = 0;
+ Ext4SetupFile (File, Partition);
+ File->InodeNum = Original->InodeNum;
+ File->OpenMode = 0; // Will be filled by other code
+
+ if (!Ext4InitExtentsMap (File)) {
+ FreePool (File->FileName);
+ FreePool (File->Inode);
+ FreePool (File);
+ return NULL;
+ }
+
+ InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
+
+ return File;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/Inode.c
new file mode 100644
index 0000000000..304bf0c4a9
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
@@ -0,0 +1,468 @@
+/**
+ @file Inode related routines
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+ EpochToEfiTime copied from EmbeddedPkg/Library/TimeBaseLib.c
+ Copyright (c) 2016, Hisilicon Limited. All rights reserved.
+ Copyright (c) 2016-2019, Linaro Limited. All rights reserved.
+ Copyright (c) 2021, Ampere Computing LLC. All rights reserved.
+ */
+
+#include "Ext4Dxe.h"
+
+#include <Uefi.h>
+
+/**
+ Calculates the checksum of the given inode.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] Inode Pointer to the inode.
+ @param[in] InodeNum Inode number.
+
+ @return The checksum.
+*/
+UINT32
+Ext4CalculateInodeChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_INODE *Inode,
+ IN EXT4_INO_NR InodeNum
+ )
+{
+ UINT32 Crc;
+ UINT16 Dummy;
+ BOOLEAN HasSecondChecksumField;
+ CONST VOID *RestOfInode;
+ UINTN RestOfInodeLength;
+
+ HasSecondChecksumField = Ext4InodeHasField (Inode, i_checksum_hi);
+
+ Dummy = 0;
+
+ Crc = Ext4CalculateChecksum (Partition, &InodeNum, sizeof (InodeNum), Partition->InitialSeed);
+ Crc = Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (Inode->i_generation), Crc);
+
+ Crc = Ext4CalculateChecksum (
+ Partition,
+ Inode,
+ OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_checksum_lo),
+ Crc
+ );
+
+ Crc = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);
+
+ RestOfInode = &Inode->i_osd2.data_linux.l_i_reserved;
+ RestOfInodeLength = Partition->InodeSize - OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_reserved);
+
+ if (HasSecondChecksumField) {
+ UINTN Length = OFFSET_OF (EXT4_INODE, i_checksum_hi) - OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_reserved);
+
+ Crc = Ext4CalculateChecksum (Partition, &Inode->i_osd2.data_linux.l_i_reserved, Length, Crc);
+ Crc = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);
+
+ // 4 is the size of the i_extra_size field + the size of i_checksum_hi
+ RestOfInodeLength = Partition->InodeSize - EXT4_GOOD_OLD_INODE_SIZE - 4;
+ RestOfInode = &Inode->i_ctime_extra;
+ }
+
+ Crc = Ext4CalculateChecksum (Partition, RestOfInode, RestOfInodeLength, Crc);
+
+ return Crc;
+}
+
+/**
+ Reads from an EXT4 inode.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] File Pointer to the opened file.
+ @param[out] Buffer Pointer to the buffer.
+ @param[in] Offset Offset of the read.
+ @param[in out] Length Pointer to the length of the buffer, in bytes.
+ After a succesful read, it's updated to the number of read bytes.
+
+ @return Status of the read operation.
+*/
+EFI_STATUS
+Ext4Read (
+ IN EXT4_PARTITION *Partition,
+ IN EXT4_FILE *File,
+ OUT VOID *Buffer,
+ IN UINT64 Offset,
+ IN OUT UINTN *Length
+ )
+{
+ // DEBUG((EFI_D_INFO, "Ext4Read[Offset %lu, Length %lu]\n", Offset, *Length));
+ EXT4_INODE *Inode;
+ UINT64 InodeSize;
+ UINT64 CurrentSeek;
+ UINTN RemainingRead;
+ UINTN BeenRead;
+
+ Inode = File->Inode;
+ InodeSize = Ext4InodeSize (Inode);
+ CurrentSeek = Offset;
+ RemainingRead = *Length;
+ BeenRead = 0;
+
+ if(Offset > InodeSize) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ if (RemainingRead > InodeSize - Offset) {
+ RemainingRead = (UINTN)(InodeSize - Offset);
+ }
+
+ while(RemainingRead != 0) {
+ UINTN WasRead;
+ EXT4_EXTENT Extent;
+ UINT32 BlockOff;
+ EFI_STATUS Status;
+ BOOLEAN HasBackingExtent;
+
+ WasRead = 0;
+
+ // The algorithm here is to get the extent corresponding to the current block
+ // and then read as much as we can from the current extent.
+
+ Status = Ext4GetExtent (
+ Partition,
+ File,
+ DivU64x32Remainder (CurrentSeek, Partition->BlockSize, &BlockOff),
+ &Extent
+ );
+
+ if(Status != EFI_SUCCESS && Status != EFI_NO_MAPPING) {
+ return Status;
+ }
+
+ HasBackingExtent = Status != EFI_NO_MAPPING;
+
+ if(!HasBackingExtent) {
+ UINT32 HoleOff;
+ UINTN HoleLen;
+
+ HoleOff = BlockOff;
+ HoleLen = Partition->BlockSize - HoleOff;
+ WasRead = HoleLen > RemainingRead ? RemainingRead : HoleLen;
+ // TODO: Get the hole size and memset all that
+ SetMem (Buffer, WasRead, 0);
+ } else {
+ UINT64 ExtentStartBytes;
+ UINT64 ExtentLengthBytes;
+ UINT64 ExtentLogicalBytes;
+
+ // Our extent offset is the difference between CurrentSeek and ExtentLogicalBytes
+ UINT64 ExtentOffset;
+ UINTN ExtentMayRead;
+
+ ExtentStartBytes = (((UINT64)Extent.ee_start_hi << 32) | Extent.ee_start_lo) * Partition->BlockSize;
+ ExtentLengthBytes = Extent.ee_len * Partition->BlockSize;
+ ExtentLogicalBytes = (UINT64)Extent.ee_block * Partition->BlockSize;
+ ExtentOffset = CurrentSeek - ExtentLogicalBytes;
+ ExtentMayRead = (UINTN)(ExtentLengthBytes - ExtentOffset);
+
+ WasRead = ExtentMayRead > RemainingRead ? RemainingRead : ExtentMayRead;
+
+ // DEBUG((EFI_D_INFO, "[ext4] may read %lu, remaining %lu\n", ExtentMayRead, RemainingRead));
+ // DEBUG((EFI_D_INFO, "[ext4] Reading block %lu\n", (ExtentStartBytes + ExtentOffset) / Partition->BlockSize));
+ Status = Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBytes + ExtentOffset);
+
+ if(EFI_ERROR (Status)) {
+ DEBUG ((
+ EFI_D_ERROR,
+ "[ext4] Error %x reading [%lu, %lu]\n",
+ Status,
+ ExtentStartBytes + ExtentOffset,
+ ExtentStartBytes + ExtentOffset + WasRead - 1
+ ));
+ return Status;
+ }
+ }
+
+ RemainingRead -= WasRead;
+ Buffer = (VOID *)((CHAR8 *)Buffer + WasRead);
+ BeenRead += WasRead;
+ CurrentSeek += WasRead;
+ }
+
+ *Length = BeenRead;
+
+ // DEBUG((EFI_D_INFO, "File length %lu crc %x\n", BeenRead, CalculateCrc32(original, BeenRead)));
+ return EFI_SUCCESS;
+}
+
+/**
+ Allocates a zeroed inode structure.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+
+ @return Pointer to the allocated structure, from the pool,
+ with size Partition->InodeSize.
+*/
+EXT4_INODE *
+Ext4AllocateInode (
+ IN EXT4_PARTITION *Partition
+ )
+{
+ BOOLEAN NeedsToZeroRest;
+ UINT32 InodeSize;
+ EXT4_INODE *Inode;
+
+ NeedsToZeroRest = FALSE;
+ InodeSize = Partition->InodeSize;
+
+ // HACK!: We allocate a structure of at least sizeof(EXT4_INODE), but in the future, when
+ // write support is added and we need to flush inodes to disk, we could have a bit better
+ // distinction between the on-disk inode and a separate, nicer to work with inode struct.
+ if (InodeSize < sizeof (EXT4_INODE)) {
+ InodeSize = sizeof (EXT4_INODE);
+ NeedsToZeroRest = TRUE;
+ }
+
+ Inode = AllocateZeroPool (InodeSize);
+
+ if (!Inode) {
+ return NULL;
+ }
+
+ if (NeedsToZeroRest) {
+ Inode->i_extra_isize = 0;
+ }
+
+ return Inode;
+}
+
+/**
+ Checks if a file is a directory.
+ @param[in] File Pointer to the opened file.
+
+ @return TRUE if file is a directory.
+*/
+BOOLEAN
+Ext4FileIsDir (
+ IN CONST EXT4_FILE *File
+ )
+{
+ return (File->Inode->i_mode & EXT4_INO_TYPE_DIR) == EXT4_INO_TYPE_DIR;
+}
+
+/**
+ Checks if a file is a regular file.
+ @param[in] File Pointer to the opened file.
+
+ @return BOOLEAN TRUE if file is a regular file.
+*/
+BOOLEAN
+Ext4FileIsReg (
+ IN CONST EXT4_FILE *File
+ )
+{
+ return (File->Inode->i_mode & EXT4_INO_TYPE_REGFILE) == EXT4_INO_TYPE_REGFILE;
+}
+
+/**
+ Calculates the physical space used by a file.
+ @param[in] File Pointer to the opened file.
+
+ @return Physical space used by a file, in bytes.
+*/
+UINT64
+Ext4FilePhysicalSpace (
+ EXT4_FILE *File
+ )
+{
+ BOOLEAN HugeFile;
+ UINT64 Blocks;
+
+ HugeFile = Ext4HasRoCompat (File->Partition, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
+ Blocks = File->Inode->i_blocks;
+
+ if(HugeFile) {
+ Blocks |= ((UINT64)File->Inode->i_osd2.data_linux.l_i_blocks_high) << 32;
+
+ // If HUGE_FILE is enabled and EXT4_HUGE_FILE_FL is set in the inode's flags, each unit
+ // in i_blocks corresponds to an actual filesystem block
+ if(File->Inode->i_flags & EXT4_HUGE_FILE_FL) {
+ return Blocks * File->Partition->BlockSize;
+ }
+ }
+
+ // Else, each i_blocks unit corresponds to 512 bytes
+ return Blocks * 512;
+}
+
+// Copied from EmbeddedPkg at my mentor's request.
+// The lack of comments and good variable names is frightening...
+
+/**
+ Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME.
+
+ @param EpochSeconds Epoch seconds.
+ @param Time The time converted to UEFI format.
+
+**/
+STATIC
+VOID
+EFIAPI
+EpochToEfiTime (
+ IN UINTN EpochSeconds,
+ OUT EFI_TIME *Time
+ )
+{
+ UINTN a;
+ UINTN b;
+ UINTN c;
+ UINTN d;
+ UINTN g;
+ UINTN j;
+ UINTN m;
+ UINTN y;
+ UINTN da;
+ UINTN db;
+ UINTN dc;
+ UINTN dg;
+ UINTN hh;
+ UINTN mm;
+ UINTN ss;
+ UINTN J;
+
+ J = (EpochSeconds / 86400) + 2440588;
+ j = J + 32044;
+ g = j / 146097;
+ dg = j % 146097;
+ c = (((dg / 36524) + 1) * 3) / 4;
+ dc = dg - (c * 36524);
+ b = dc / 1461;
+ db = dc % 1461;
+ a = (((db / 365) + 1) * 3) / 4;
+ da = db - (a * 365);
+ y = (g * 400) + (c * 100) + (b * 4) + a;
+ m = (((da * 5) + 308) / 153) - 2;
+ d = da - (((m + 4) * 153) / 5) + 122;
+
+ Time->Year = (UINT16)(y - 4800 + ((m + 2) / 12));
+ Time->Month = ((m + 2) % 12) + 1;
+ Time->Day = (UINT8)(d + 1);
+
+ ss = EpochSeconds % 60;
+ a = (EpochSeconds - ss) / 60;
+ mm = a % 60;
+ b = (a - mm) / 60;
+ hh = b % 24;
+
+ Time->Hour = (UINT8)hh;
+ Time->Minute = (UINT8)mm;
+ Time->Second = (UINT8)ss;
+ Time->Nanosecond = 0;
+
+}
+
+// The time format used to (de/en)code timestamp and timestamp_extra is documented on
+// the ext4 docs page in kernel.org
+#define EXT4_EXTRA_TIMESTAMP_MASK ((1 << 2) - 1)
+
+#define EXT4_FILE_GET_TIME_GENERIC(Name, Field) \
+ VOID \
+ Ext4File ## Name (IN EXT4_FILE *File, OUT EFI_TIME *Time) \
+ { \
+ EXT4_INODE *Inode = File->Inode; \
+ UINT64 SecondsEpoch = Inode->Field; \
+ UINT32 Nanoseconds = 0; \
+ \
+ if (Ext4InodeHasField (Inode, Field ## _extra)) { \
+ SecondsEpoch |= ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK)) << 32; \
+ Nanoseconds = Inode->Field ## _extra >> 2; \
+ } \
+ EpochToEfiTime ((UINTN)SecondsEpoch, Time); \
+ Time->Nanosecond = Nanoseconds; \
+ }
+
+// Note: EpochToEfiTime should be adjusted to take in a UINT64 instead of a UINTN, in order to avoid Y2038
+// on 32-bit systems.
+
+/**
+ Gets the file's last access time.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+*/
+EXT4_FILE_GET_TIME_GENERIC (ATime, i_atime);
+
+/**
+ Gets the file's last (data) modification time.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+*/
+EXT4_FILE_GET_TIME_GENERIC (MTime, i_mtime);
+
+/**
+ Gets the file's creation time.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+*/
+STATIC
+EXT4_FILE_GET_TIME_GENERIC (
+ CrTime, i_crtime
+ );
+
+/**
+ Gets the file's creation time, if possible.
+ @param[in] File Pointer to the opened file.
+ @param[out] Time Pointer to an EFI_TIME structure.
+ In the case where the the creation time isn't recorded,
+ Time is zeroed.
+*/
+VOID
+Ext4FileCreateTime (
+ IN EXT4_FILE *File,
+ OUT EFI_TIME *Time
+ )
+{
+ EXT4_INODE *Inode;
+
+ Inode = File->Inode;
+
+ if (!Ext4InodeHasField (Inode, i_crtime)) {
+ SetMem (Time, sizeof (EFI_TIME), 0);
+ return;
+ }
+
+ Ext4FileCrTime (File, Time);
+}
+
+/**
+ Checks if the checksum of the inode is correct.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] Inode Pointer to the inode.
+ @param[in] InodeNum Inode number.
+
+ @return TRUE if checksum is correct, FALSE if there is corruption.
+*/
+BOOLEAN
+Ext4CheckInodeChecksum (
+ IN CONST EXT4_PARTITION *Partition,
+ IN CONST EXT4_INODE *Inode,
+ IN EXT4_INO_NR InodeNum
+ )
+{
+ UINT32 Csum;
+ UINT32 DiskCsum;
+
+ if(!Ext4HasMetadataCsum (Partition)) {
+ return TRUE;
+ }
+
+ Csum = Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);
+
+ DiskCsum = Inode->i_osd2.data_linux.l_i_checksum_lo;
+
+ if (Ext4InodeHasField (Inode, i_checksum_hi)) {
+ DiskCsum |= ((UINT32)Inode->i_checksum_hi) << 16;
+ } else {
+ // Only keep the lower bits for the comparison if the checksum is 16 bits.
+ Csum &= 0xffff;
+ }
+
+ #if 0
+ DEBUG ((EFI_D_INFO, "[ext4] Inode %d csum %x vs %x\n", InodeNum, Csum, DiskCsum));
+ #endif
+
+ return Csum == DiskCsum;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Partition.c b/Features/Ext4Pkg/Ext4Dxe/Partition.c
new file mode 100644
index 0000000000..a2c4c57e78
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Partition.c
@@ -0,0 +1,120 @@
+/**
+ @file Driver entry point
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+/**
+ Opens an ext4 partition and installs the Simple File System protocol.
+
+ @param[in] DeviceHandle Handle to the block device.
+ @param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.
+ @param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL, if supported.
+ @param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.
+
+ @retval EFI_SUCCESS The opening was successful.
+ !EFI_SUCCESS Opening failed.
+ */
+EFI_STATUS
+Ext4OpenPartition (
+ IN EFI_HANDLE DeviceHandle,
+ IN EFI_DISK_IO_PROTOCOL *DiskIo,
+ IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,
+ IN EFI_BLOCK_IO_PROTOCOL *BlockIo
+ )
+{
+ EXT4_PARTITION *Part;
+ EFI_STATUS Status;
+
+ Part = AllocateZeroPool (sizeof (*Part));
+
+ if(Part == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ InitializeListHead(&Part->OpenFiles);
+
+ Part->BlockIo = BlockIo;
+ Part->DiskIo = DiskIo;
+ Part->DiskIo2 = DiskIo2;
+
+ Status = Ext4OpenSuperblock (Part);
+
+ if(EFI_ERROR (Status)) {
+ FreePool (Part);
+ return Status;
+ }
+
+ Part->Interface.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
+ Part->Interface.OpenVolume = Ext4OpenVolume;
+ Status = gBS->InstallMultipleProtocolInterfaces (
+ &DeviceHandle,
+ &gEfiSimpleFileSystemProtocolGuid,
+ &Part->Interface,
+ NULL
+ );
+
+ if(EFI_ERROR (Status)) {
+ FreePool (Part);
+ return Status;
+ }
+
+ return EFI_SUCCESS;
+}
+
+/**
+ Sets up the protocol and metadata of a file that is being opened.
+
+ @param[in out] File Pointer to the file.
+ @param[in] Partition Pointer to the opened partition.
+ */
+VOID
+Ext4SetupFile (
+ IN OUT EXT4_FILE *File, EXT4_PARTITION *Partition
+ )
+{
+ // TODO: Support revision 2 (needs DISK_IO2 + asynchronous IO)
+ File->Protocol.Revision = EFI_FILE_PROTOCOL_REVISION;
+ File->Protocol.Open = Ext4Open;
+ File->Protocol.Close = Ext4Close;
+ File->Protocol.Delete = Ext4Delete;
+ File->Protocol.Read = Ext4ReadFile;
+ File->Protocol.Write = Ext4WriteFile;
+ File->Protocol.SetPosition = Ext4SetPosition;
+ File->Protocol.GetPosition = Ext4GetPosition;
+ File->Protocol.GetInfo = Ext4GetInfo;
+
+ File->Partition = Partition;
+}
+
+/**
+ Unmounts and frees an ext4 partition.
+
+ @param[in] Partition Pointer to the opened partition.
+
+ @retval Status of the unmount.
+ */
+EFI_STATUS
+Ext4UnmountAndFreePartition (
+ IN EXT4_PARTITION *Partition
+ )
+{
+ LIST_ENTRY *Entry;
+ LIST_ENTRY *NextEntry;
+ Partition->Unmounting = TRUE;
+ Ext4CloseInternal (Partition->Root);
+
+ BASE_LIST_FOR_EACH_SAFE(Entry, NextEntry, &Partition->OpenFiles) {
+ EXT4_FILE *File = Ext4FileFromOpenFileNode(Entry);
+
+ Ext4CloseInternal(File);
+ }
+
+ FreePool (Partition->BlockGroups);
+ FreePool (Partition);
+
+ return EFI_SUCCESS;
+}
diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
new file mode 100644
index 0000000000..18d8295a1f
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
@@ -0,0 +1,257 @@
+/**
+ @file Superblock managing routines
+
+ Copyright (c) 2021 Pedro Falcato All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+ */
+
+#include "Ext4Dxe.h"
+
+STATIC CONST UINT32 gSupportedCompatFeat = EXT4_FEATURE_COMPAT_EXT_ATTR;
+
+STATIC CONST UINT32 gSupportedRoCompatFeat =
+ EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE |
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE | EXT4_FEATURE_RO_COMPAT_LARGE_FILE |
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM | EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER;
+// TODO: Add btree support
+STATIC CONST UINT32 gSupportedIncompatFeat =
+ EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_DIRDATA |
+ EXT4_FEATURE_INCOMPAT_FLEX_BG | EXT4_FEATURE_INCOMPAT_FILETYPE |
+ EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_LARGEDIR |
+ EXT4_FEATURE_INCOMPAT_MMP;
+
+// TODO: Add meta_bg support
+
+// Note: We ignore MMP because it's impossible that it's mapped elsewhere,
+// I think (unless there's some sort of network setup where we're accessing a remote partition).
+
+BOOLEAN
+Ext4SuperblockValidate (
+ EXT4_SUPERBLOCK *sb
+ )
+{
+ if(sb->s_magic != EXT4_SIGNATURE) {
+ return FALSE;
+ }
+
+ // TODO: We should try to support EXT2/3 partitions too
+ if(sb->s_rev_level != EXT4_DYNAMIC_REV && sb->s_rev_level != EXT4_GOOD_OLD_REV) {
+ return FALSE;
+ }
+
+ // TODO: Is this correct behaviour? Imagine the power cuts out, should the system fail to boot because
+ // we're scared of touching something corrupt?
+ if((sb->s_state & EXT4_FS_STATE_UNMOUNTED) == 0) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+STATIC UINT32
+Ext4CalculateSuperblockChecksum (
+ EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb
+ )
+{
+ // Most checksums require us to go through a dummy 0 as part of the requirement
+ // that the checksum is done over a structure with its checksum field = 0.
+ UINT32 Checksum = Ext4CalculateChecksum (
+ Partition,
+ sb,
+ OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),
+ ~0U
+ );
+
+ return Checksum;
+}
+
+STATIC BOOLEAN
+Ext4VerifySuperblockChecksum (
+ EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb
+ )
+{
+ if(!Ext4HasMetadataCsum (Partition)) {
+ return TRUE;
+ }
+
+ return sb->s_checksum == Ext4CalculateSuperblockChecksum (Partition, sb);
+}
+
+/**
+ Opens and parses the superblock.
+
+ @param[out] Partition Partition structure to fill with filesystem details.
+ @retval EFI_SUCCESS Parsing was succesful and the partition is a
+ valid ext4 partition.
+ */
+EFI_STATUS
+Ext4OpenSuperblock (
+ OUT EXT4_PARTITION *Partition
+ )
+{
+ UINT32 Index;
+ EFI_STATUS Status;
+ EXT4_SUPERBLOCK *Sb;
+ UINT32 NrBlocksRem;
+ UINTN NrBlocks;
+ UINT32 UnsupportedRoCompat;
+
+ Status = Ext4ReadDiskIo (
+ Partition,
+ &Partition->SuperBlock,
+ sizeof (EXT4_SUPERBLOCK),
+ EXT4_SUPERBLOCK_OFFSET
+ );
+
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Sb = &Partition->SuperBlock;
+
+ if (!Ext4SuperblockValidate (Sb)) {
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ if (Sb->s_rev_level == EXT4_DYNAMIC_REV) {
+ Partition->FeaturesCompat = Sb->s_feature_compat;
+ Partition->FeaturesIncompat = Sb->s_feature_incompat;
+ Partition->FeaturesRoCompat = Sb->s_feature_ro_compat;
+ Partition->InodeSize = Sb->s_inode_size;
+ } else {
+ // GOOD_OLD_REV
+ Partition->FeaturesCompat = Partition->FeaturesIncompat = Partition->FeaturesRoCompat = 0;
+ Partition->InodeSize = EXT4_GOOD_OLD_INODE_SIZE;
+ }
+
+ // Now, check for the feature set of the filesystem
+ // It's essential to check for this to avoid filesystem corruption and to avoid
+ // accidentally opening an ext2/3/4 filesystem we don't understand, which would be disasterous.
+
+ if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {
+ DEBUG ((EFI_D_INFO, "[Ext4] Unsupported %lx\n", Partition->FeaturesIncompat & ~gSupportedIncompatFeat));
+ return EFI_UNSUPPORTED;
+ }
+
+ // This should be removed once we add ext2/3 support in the future.
+ if ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_EXTENTS) == 0) {
+ return EFI_UNSUPPORTED;
+ }
+
+ // At the time of writing, it's the only supported checksum.
+ if (Partition->FeaturesCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM &&
+ Sb->s_checksum_type != EXT4_CHECKSUM_CRC32C) {
+ return EFI_UNSUPPORTED;
+ }
+
+ if (Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_CSUM_SEED) {
+ Partition->InitialSeed = Sb->s_checksum_seed;
+ } else {
+ Partition->InitialSeed = Ext4CalculateChecksum (Partition, Sb->s_uuid, 16, ~0U);
+ }
+
+ UnsupportedRoCompat = Partition->FeaturesRoCompat & ~gSupportedRoCompatFeat;
+
+ if (UnsupportedRoCompat != 0) {
+ DEBUG ((EFI_D_INFO, "[Ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
+ Partition->ReadOnly = TRUE;
+ }
+
+ (VOID)gSupportedCompatFeat;
+
+ DEBUG ((EFI_D_INFO, "Read only = %u\n", Partition->ReadOnly));
+
+ Partition->BlockSize = 1024 << Sb->s_log_block_size;
+
+ // The size of a block group can also be calculated as 8 * Partition->BlockSize
+ if(Sb->s_blocks_per_group != 8 * Partition->BlockSize) {
+ return EFI_UNSUPPORTED;
+ }
+
+ Partition->NumberBlocks = Ext4MakeBlockNumberFromHalfs (Partition, Sb->s_blocks_count, Sb->s_blocks_count_hi);
+ Partition->NumberBlockGroups = DivU64x32 (Partition->NumberBlocks, Sb->s_blocks_per_group);
+
+ DEBUG ((
+ EFI_D_INFO,
+ "[ext4] Number of blocks = %lu\n[ext4] Number of block groups: %lu\n",
+ Partition->NumberBlocks,
+ Partition->NumberBlockGroups
+ ));
+
+ if (Ext4Is64Bit (Partition)) {
+ Partition->DescSize = Sb->s_desc_size;
+ } else {
+ Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
+ }
+
+ if (Partition->DescSize < EXT4_64BIT_BLOCK_DESC_SIZE && Ext4Is64Bit (Partition)) {
+ // 64 bit filesystems need DescSize to be 64 bytes
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {
+ DEBUG ((EFI_D_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ NrBlocks = (UINTN)DivU64x32Remainder (
+ Partition->NumberBlockGroups * Partition->DescSize,
+ Partition->BlockSize,
+ &NrBlocksRem
+ );
+
+ if(NrBlocksRem != 0) {
+ NrBlocks++;
+ }
+
+ Partition->BlockGroups = Ext4AllocAndReadBlocks (Partition, NrBlocks, Partition->BlockSize == 1024 ? 2 : 1);
+
+ if (!Partition->BlockGroups) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ for (Index = 0; Index < Partition->NumberBlockGroups; Index++) {
+ EXT4_BLOCK_GROUP_DESC *Desc;
+
+ Desc = Ext4GetBlockGroupDesc (Partition, Index);
+ if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {
+ DEBUG ((EFI_D_INFO, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
+ return EFI_VOLUME_CORRUPTED;
+ }
+ }
+
+ // Note that the cast below is completely safe, because EXT4_FILE is a specialisation of EFI_FILE_PROTOCOL
+ Status = Ext4OpenVolume (&Partition->Interface, (EFI_FILE_PROTOCOL **)&Partition->Root);
+
+ DEBUG ((EFI_D_INFO, "[ext4] Root File %p\n", Partition->Root));
+ return Status;
+}
+
+/**
+ Calculates the checksum of the given buffer.
+ @param[in] Partition Pointer to the opened EXT4 partition.
+ @param[in] Buffer Pointer to the buffer.
+ @param[in] Length Length of the buffer, in bytes.
+ @param[in] InitialValue Initial value of the CRC.
+
+ @return The checksum.
+*/
+UINT32
+Ext4CalculateChecksum (
+ IN CONST EXT4_PARTITION *Partition, IN CONST VOID *Buffer, IN UINTN Length,
+ IN UINT32 InitialValue
+ )
+{
+ if(!Ext4HasMetadataCsum (Partition)) {
+ return 0;
+ }
+
+ switch(Partition->SuperBlock.s_checksum_type) {
+ case EXT4_CHECKSUM_CRC32C:
+ // For some reason, EXT4 really likes non-inverted CRC32C checksums, so we stick to that here.
+ return ~CalculateCrc32c(Buffer, Length, ~InitialValue);
+ default:
+ UNREACHABLE ();
UNREACHABLE allows the compiler to optimise the control flow with this assumption, e.g. remove the redundant "return 0" below. Basically you make a case distinction here (switch), and then tell the compiler only one of those cases is reachable. In my opinion, remove the switch altogether and add an ASSERT on the equality constraint. This would avoid awkward code generation and potentially reachable control flow interruptions (in case the constraint does not hold and the compiler decided to generate the branch, but just terminate it immediately or whatever), add debug feedback, increase readability, and so on. Also nitpicking, but I try to document *why* ASSERTs hold.

+ return 0;
+ }
+}


Re: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

Bret Barkelew
 

I don’t think this is a good dependency. StandaloneMmCore shouldn’t require anything from EmbeddedPkg, and if it does there’s probably some more generalization necessary.

 

Further, we can’t require the MmCore (which should be considered generic at the level of PEI Core or DXE Core) include some external library like Libfdt without significant justification.

 

- Bret

 

From: Sayanta Pattanayak via groups.io
Sent: Friday, July 30, 2021 10:36 AM
To: devel@edk2.groups.io
Cc: Ard Biesheuvel; Sami Mujawar; Achin Gupta
Subject: [EXTERNAL] [edk2-devel] [edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

 

Introduce support to populate StMM boot data via DTS parsing. The DTB is
passed as a boot argument by a binary of higer exception level.
Previously it was achieved by placing the boot data structure in a
shared buffer and the address of this shared buffer was passed by the
binary of higher exception level. Now either of the option can be used
for populating StMM boot info.

StMM boot information structure binding in device tree can be of following
prototype. Property values are not mentioned here.

bootarg {
  compatible = "bootargs";
  h_type  = <..>;
  h_version = <..>;
  h_size    = <..>;
  h_attr    = <..>;
  sp_mem_base         = <..>;
  sp_mem_limit        = <..>;
  sp_image_base       = <..>;
  sp_stack_base       = <..>;
  sp_heap_base        = <..>;
  sp_ns_comm_buf_base = <..>;fc
  sp_shared_buf_base  = <..>;
  sp_image_size       = <..>;
  sp_pcpu_stack_size  = <..>;
  sp_heap_size        = <..>;
  sp_ns_comm_buf_size = <..>;
  sp_shared_buf_size  = <..>;
  num_sp_mem_regions  = <..>;
  num_cpus            = <..>;
};

Addition of DTS supoort involves a dependency on FdtLib from EmbeddedPkg.

Signed-off-by: Sayanta Pattanayak <sayanta.pattanayak@...>
---
 Link to github branch with this patch -
 https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FSayantaP-arm%2Fedk2%2Ftree%2Fstmm-dts&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=gJw%2BH8Dj9lih1DNrIzbNPToJn9nH%2BHqpvpSr2JJrS4I%3D&amp;reserved=0

 StandaloneMmPkg/StandaloneMmPkg.dsc                                                     |   1 +
 StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf       |   3 +
 StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c | 153 ++++++++++++++++++--
 3 files changed, 143 insertions(+), 14 deletions(-)

diff --git a/StandaloneMmPkg/StandaloneMmPkg.dsc b/StandaloneMmPkg/StandaloneMmPkg.dsc
index 0c45df95e2dd..e3a3a6ee3ba1 100644
--- a/StandaloneMmPkg/StandaloneMmPkg.dsc
+++ b/StandaloneMmPkg/StandaloneMmPkg.dsc
@@ -49,6 +49,7 @@
   HobLib|StandaloneMmPkg/Library/StandaloneMmHobLib/StandaloneMmHobLib.inf
   IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
   MemLib|StandaloneMmPkg/Library/StandaloneMmMemLib/StandaloneMmMemLib.inf
+  FdtLib|EmbeddedPkg/Library/FdtLib/FdtLib.inf
   MemoryAllocationLib|StandaloneMmPkg/Library/StandaloneMmCoreMemoryAllocationLib/StandaloneMmCoreMemoryAllocationLib.inf
   MmServicesTableLib|MdePkg/Library/StandaloneMmServicesTableLib/StandaloneMmServicesTableLib.inf
   PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
index 4fa426f58ef4..0a2e519dd664 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntryPoint.inf
@@ -30,6 +30,7 @@
   X64/StandaloneMmCoreEntryPoint.c
 
 [Packages]
+  EmbeddedPkg/EmbeddedPkg.dec
   MdePkg/MdePkg.dec
   MdeModulePkg/MdeModulePkg.dec
   StandaloneMmPkg/StandaloneMmPkg.dec
@@ -40,10 +41,12 @@
 [LibraryClasses]
   BaseLib
   DebugLib
+  FdtLib
 
 [LibraryClasses.AARCH64]
   StandaloneMmMmuLib
   ArmSvcLib
+  FdtLib
 
 [Guids]
   gMpInformationHobGuid
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
index 6c50f470aa35..cc09d75dac36 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmCoreEntryPoint.c
@@ -16,6 +16,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
 #include <Guid/MmramMemoryReserve.h>
 #include <Guid/MpInformation.h>
 
+#include <libfdt.h>
 #include <Library/ArmMmuLib.h>
 #include <Library/ArmSvcLib.h>
 #include <Library/DebugLib.h>
@@ -45,33 +46,31 @@ STATIC CONST UINT32 mSpmMinorVerFfa = SPM_MINOR_VERSION_FFA;
 PI_MM_ARM_TF_CPU_DRIVER_ENTRYPOINT      CpuDriverEntryPoint = NULL;
 
 /**
-  Retrieve a pointer to and print the boot information passed by privileged
-  secure firmware.
+  Prints boot information.
 
-  @param  [in] SharedBufAddress   The pointer memory shared with privileged
-                                  firmware.
+  This function prints the boot information, which is passed by privileged
+  secure firmware through shared buffer or other mechanism.
 
+  @param  [in] PayloadBootInfo   Pointer to StandaloneMM Boot Info structure.
 **/
-EFI_SECURE_PARTITION_BOOT_INFO *
-GetAndPrintBootinformation (
-  IN VOID                      *SharedBufAddress
+VOID
+PrintBootinformation (
+  IN EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo
 )
 {
-  EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
   EFI_SECURE_PARTITION_CPU_INFO  *PayloadCpuInfo;
   UINTN                          Index;
 
-  PayloadBootInfo = (EFI_SECURE_PARTITION_BOOT_INFO *) SharedBufAddress;
 
   if (PayloadBootInfo == NULL) {
     DEBUG ((DEBUG_ERROR, "PayloadBootInfo NULL\n"));
-    return NULL;
+    return;
   }
 
   if (PayloadBootInfo->Header.Version != BOOT_PAYLOAD_VERSION) {
     DEBUG ((DEBUG_ERROR, "Boot Information Version Mismatch. Current=0x%x, Expected=0x%x.\n",
             PayloadBootInfo->Header.Version, BOOT_PAYLOAD_VERSION));
-    return NULL;
+    return;
   }
 
   DEBUG ((DEBUG_INFO, "NumSpMemRegions - 0x%x\n", PayloadBootInfo->NumSpMemRegions));
@@ -96,7 +95,7 @@ GetAndPrintBootinformation (
 
   if (PayloadCpuInfo == NULL) {
     DEBUG ((DEBUG_ERROR, "PayloadCpuInfo NULL\n"));
-    return NULL;
+    return;
   }
 
   for (Index = 0; Index < PayloadBootInfo->NumCpus; Index++) {
@@ -105,7 +104,7 @@ GetAndPrintBootinformation (
     DEBUG ((DEBUG_INFO, "Flags           - 0x%x\n", PayloadCpuInfo[Index].Flags));
   }
 
-  return PayloadBootInfo;
+  return;
 }
 
 /**
@@ -194,6 +193,119 @@ DelegatedEventLoop (
   }
 }
 
+/**
+  Populates StandAloneMM boot information structure.
+
+  This function receives dtb Address, where StMM Boot information specific
+  properties will be looked out to form the booting structure of type
+  EFI_SECURE_PARTITION_BOOT_INFO. At first, the properties for StandAloneMM
+  ConfigSize and  Memory limit will be checked out. Boot information will
+  be stored at address (Memory Limit - ConfigSize). Thereafter all boot
+  information specific properties will be parsed and corresponding values
+  will be obtained.
+
+  @param  [out] BootInfo   Pointer, where Boot Info structure will be populated.
+  @param  [in] DtbAddress  Address of the Device tree from where Boot
+                           information will be fetched.
+**/
+VOID
+PopulateBootinformation (
+  OUT EFI_SECURE_PARTITION_BOOT_INFO **BootInfo,
+  IN VOID   *DtbAddress
+)
+{
+  INT32           Offset;
+  CONST UINT32    *Property;
+  CONST UINT64    *Property64;
+  UINT32          ConfigSize;
+  UINT64          SpMemLimit;
+  EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
+
+  Offset = fdt_node_offset_by_compatible (DtbAddress, -1, "config-size");
+  if (Offset < 0) {
+    DEBUG ((DEBUG_WARN, "Total Config Size is not  defined\n"));
+  } else {
+    Property = fdt_getprop (DtbAddress, Offset, "size", NULL);
+    if (Property) {
+      ConfigSize = fdt32_to_cpu (*Property);
+      DEBUG ((DEBUG_INFO, "stmm dtb config-size  = 0x%x \n", ConfigSize));
+    }
+  }
+
+  Offset = fdt_node_offset_by_compatible (DtbAddress, -1, "bootargs");
+  if (Offset >= 0) {
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_mem_limit", NULL);
+    SpMemLimit = fdt64_to_cpu (*Property64);
+  }
+
+  if (SpMemLimit && ConfigSize)
+    PayloadBootInfo =
+      (EFI_SECURE_PARTITION_BOOT_INFO *)(SpMemLimit - ConfigSize);
+
+  if (PayloadBootInfo) {
+    PayloadBootInfo->SpMemLimit = SpMemLimit;
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_type", NULL);
+    PayloadBootInfo->Header.Type = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_version", NULL);
+    PayloadBootInfo->Header.Version = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_size", NULL);
+    PayloadBootInfo->Header.Size = (UINT8) fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "h_attr", NULL);
+    PayloadBootInfo->Header.Attr = fdt32_to_cpu(*Property);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_mem_base", NULL);
+    PayloadBootInfo->SpMemBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_image_base", NULL);
+    PayloadBootInfo->SpImageBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_stack_base", NULL);
+    PayloadBootInfo->SpStackBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_heap_base", NULL);
+    PayloadBootInfo->SpHeapBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_base", NULL);
+    PayloadBootInfo->SpNsCommBufBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_shared_buf_base", NULL);
+    PayloadBootInfo->SpSharedBufBase = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_image_size", NULL);
+    PayloadBootInfo->SpImageSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_pcpu_stack_size", NULL);
+    PayloadBootInfo->SpPcpuStackSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_heap_size", NULL);
+    PayloadBootInfo->SpHeapSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_size", NULL);
+    PayloadBootInfo->SpNsCommBufSize = fdt64_to_cpu(*Property64);
+
+    Property64 =  fdt_getprop (DtbAddress, Offset, "sp_shared_buf_size", NULL);
+    PayloadBootInfo->SpPcpuSharedBufSize = fdt64_to_cpu(*Property64);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "num_sp_mem_regions", NULL);
+    PayloadBootInfo->NumSpMemRegions = fdt32_to_cpu(*Property);
+
+    Property =  fdt_getprop (DtbAddress, Offset, "num_cpus", NULL);
+    PayloadBootInfo->NumCpus = fdt32_to_cpu(*Property);
+
+    PayloadBootInfo->CpuInfo =
+      (EFI_SECURE_PARTITION_CPU_INFO *)((UINT64)PayloadBootInfo +
+                                        sizeof(EFI_SECURE_PARTITION_BOOT_INFO));
+  }
+
+  *BootInfo = PayloadBootInfo;
+
+  return;
+}
+
 /**
   Query the SPM version, check compatibility and return success if compatible.
 
@@ -313,6 +425,7 @@ _ModuleEntryPoint (
   VOID                                    *TeData;
   UINTN                                   TeDataSize;
   EFI_PHYSICAL_ADDRESS                    ImageBase;
+  VOID                                    *DtbAddress;
 
   // Get Secure Partition Manager Version Information
   Status = GetSpmVersion ();
@@ -320,12 +433,24 @@ _ModuleEntryPoint (
     goto finish;
   }
 
-  PayloadBootInfo = GetAndPrintBootinformation (SharedBufAddress);
+  // In cookie1 the DTB address is passed. With reference to DTB, Boot
+  // info structure can be populated.
+  // If cookie1 doesn't have any value, then Boot info is copied from
+  // Sharedbuffer.
+  if (cookie1) {
+    DtbAddress = (void *)cookie1;
+    PopulateBootinformation (&PayloadBootInfo, DtbAddress);
+  } else {
+    PayloadBootInfo = (EFI_SECURE_PARTITION_BOOT_INFO *)SharedBufAddress;
+  }
+
   if (PayloadBootInfo == NULL) {
     Status = EFI_UNSUPPORTED;
     goto finish;
   }
 
+  PrintBootinformation (PayloadBootInfo);
+
   // Locate PE/COFF File information for the Standalone MM core module
   Status = LocateStandaloneMmCorePeCoffData (
              (EFI_FIRMWARE_VOLUME_HEADER *) PayloadBootInfo->SpImageBase,
--
2.17.1



-=-=-=-=-=-=
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#78449): https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Fmessage%2F78449&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=0DbkyBJe0prgx2vSCVVsrI0GoVbCipcfmbZp83LREjo%3D&amp;reserved=0
Mute This Topic: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgroups.io%2Fmt%2F84555304%2F1852292&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786132182%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=1u3cr0MmJmjrkxtGgfOe1mb8%2F7vUXaoyUr5%2FmC1Uq%2F8%3D&amp;reserved=0
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Funsub&amp;data=04%7C01%7Cbret.barkelew%40microsoft.com%7Cb365266c83cb433d1e0108d953806c83%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632633786142135%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=Yr%2BSNqWHllfxHkFqBhCyP2TE4IGRLFLm%2FCCWexjB0AM%3D&amp;reserved=0 [bret.barkelew@...]
-=-=-=-=-=-=

 


Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Sean
 

Jiewen,

**Slight rant**

I agree with libraries as an effective abstraction method. But I think there needs to be a broad discussion about the order of preference for methods of abstraction. Today the edk2 code base is a mix and often there are numerous methods abstracting the same thing which leads to confusion, misconfiguration, and error.

In the UEFI specification we have PPIs/Protocols/Events for functional abstraction. We have variables, guided config tables, and HII for data abstraction.

In the PI specification we add HOBs and PCDs for data abstractions.

Finally, in EDKII we add the library class concept and leverage it heavily for arch, phase, and platform/behavioral abstractions.

Without clear guidance for how and when to use the above it is hard to keep code being developed by the larger community consistent.

**End**

I was leaning towards something closer to

Option 1:
https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib_2

the HOB method and internally as we develop more code we are preferring HOB and data abstractions more than functional abstraction. Data abstractions can be used to control functional differences as well if needed. Data abstractions allow for easier validation and support diverse code environments. For example standalone MM and payloadpkg/payload concepts. Finally, data abstractions break the need for a monolithic code base. But as you can see in option 1 it actually uses a library class abstraction as well because no one wants to write the same code over and over again to get the HOB. The contract of the library is just data but it still requires library mappings. Maybe these types of libraries need to be treated differently.

Anyway it would be great to hear from other members of the community around not just the memory protections RFC (this RFC) but around preferences for abstraction techniques (pro/con). If an actual discussion starts it could move to design meeting.

Thanks
Sean







On 7/29/2021 7:34 PM, Yao, Jiewen wrote:
Thanks. Code talks better.
I prefer option 2, which is a generic way for abstraction.
And you may enable option 1 under the cover of option 2, just create a lib instance to get config from Hob.
Thank you
Yao Jiewen

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 30, 2021 10:07 AM
To: Yao, Jiewen <jiewen.yao@intel.com>; Wang, Jian J <jian.j.wang@intel.com>;
devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <rahul1.kumar@intel.com>;
mikuback@linux.microsoft.com; Wu, Hao A <hao.a.wu@intel.com>; Bi, Dandan
<dandan.bi@intel.com>; gaoliming@byosoft.com.cn; Dong, Guo
<guo.dong@intel.com>; Ma, Maurice <maurice.ma@intel.com>; You, Benjamin
<benjamin.you@intel.com>
Subject: Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Of course - here are a couple of rough drafts:

Option 1: https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib_2
Option 2: https://github.com/TaylorBeebe/edk2/tree/memory_protection_lib

On 7/29/2021 6:57 PM, Yao, Jiewen wrote:
Hi
Sorry, I am not able to follow the discussion.

Is there any sample or POC code to show the concept?

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 30, 2021 9:55 AM
To: Wang, Jian J <jian.j.wang@intel.com>; devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <rahul1.kumar@intel.com>;
mikuback@linux.microsoft.com; Wu, Hao A <hao.a.wu@intel.com>; Bi,
Dandan
<dandan.bi@intel.com>; gaoliming@byosoft.com.cn; Dong, Guo
<guo.dong@intel.com>; Ma, Maurice <maurice.ma@intel.com>; You,
Benjamin
<benjamin.you@intel.com>; Yao, Jiewen <jiewen.yao@intel.com>
Subject: Re: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Thanks for your feedback, Jian.

In option 2, a most basic implementation would returning the current
FixedAtBuild PCDs assuming they are kept. If they aren't, the library
implementer could simply hard-code the return value for each memory
protection setting.

In option 1, the HOB would be published in pre-mem and I'm not an expert
on exploiting the pre-mem environment. Jiewen may have more to say on
this.

-Taylor

On 7/28/2021 7:18 PM, Wang, Jian J wrote:
Thanks for the RFC. I'm not object to this idea. The only concern from me
is the potential security holes introduced by the changes. According to your
description, it allows 3rd party software to violate memory protection
policy.
I'd like to see more explanations on how to avoid it to be exploited.

+Jiewen, what's current process to evaluate the security threat?

Regards,
Jian

-----Original Message-----
From: Taylor Beebe <t@taylorbeebe.com>
Sent: Friday, July 23, 2021 8:33 AM
To: devel@edk2.groups.io
Cc: spbrogan@outlook.com; Dong, Eric <eric.dong@intel.com>; Ni, Ray
<ray.ni@intel.com>; Kumar, Rahul1 <Rahul1.Kumar@intel.com>;
mikuback@linux.microsoft.com; Wang, Jian J <jian.j.wang@intel.com>;
Wu,
Hao A <hao.a.wu@intel.com>; Bi, Dandan <dandan.bi@intel.com>;
gaoliming@byosoft.com.cn; Dong, Guo <guo.dong@intel.com>; Ma,
Maurice
<maurice.ma@intel.com>; You, Benjamin <benjamin.you@intel.com>
Subject: [RFC] MemoryProtectionLib for Dynamic Memory Guard Settings

Current memory protection settings rely on FixedAtBuild PCD values
(minus PcdSetNxForStack). Because of this, the memory protection
configuration interface is fixed in nature. Cases arise in which memory
protections might need to be adjusted between boots (if platform design
allows) to avoid disabling a system. For example, platforms might choose
to allow the user to control their protection policies such as allow
execution of critical 3rd party software that might violate memory
protections.

This RFC seeks your feedback regarding introducing an interface that
allows dynamic configuration of memory protection settings.

I would like to propose two options:
1. Describing the memory protection setting configuration in a HOB that
is produced by the platform.
2. Introducing a library class (e.g. MemoryProtectionLib) that allows
abstraction of the memory protection setting configuration data source.

In addition, I would like to know if the memory protection FixedAtBuild
PCDs currently in MdeModulePkg can be removed so we can move the
configuration interface entirely to an option above.

In any case, I would like the settings to be visible to environments
such as Standalone MM where dynamic PCDs are not accessible.

I am seeking your feedback on this proposal in preparation for sending
an edk2 patch series.

--
Taylor Beebe
Software Engineer @ Microsoft
--
Taylor Beebe
Software Engineer @ Microsoft
--
Taylor Beebe
Software Engineer @ Microsoft


Re: [EXTERNAL] [edk2-devel] [PATCH v1 1/1] SecurityPkg/Library: Add Tpm2NvUndefineSpaceSpecial to Tpm2CommandLib

Bret Barkelew
 

Note, even though this keeps with the style of the rest of the file, it breaks ECC:

SecurityPkg/Library: Add Tpm2NvUndefineSpaceSpecial to Tpm2CommandLib by corthon · Pull Request #1848 · tianocore/edk2 (github.com)

 

PROGRESS - --Running SecurityPkg: EccCheck Test NO-TARGET --

ERROR -

ERROR -

ERROR - EFI coding style error

ERROR - *Error code: 8001

ERROR - *Only capital letters are allowed to be used for #define declarations

ERROR - *file: //home/vsts/work/1/s/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c

ERROR - *Line number: 27

ERROR - *The #define name [RC_NV_UndefineSpaceSpecial_nvIndex] does no

 

Thoughts?

 

- Bret

 

From: Bret Barkelew via groups.io
Sent: Friday, July 30, 2021 10:55 AM
To: devel@edk2.groups.io
Cc: Yao, Jiewen; Jian J Wang; Qi Zhang; Rahul Kumar
Subject: [EXTERNAL] [edk2-devel] [PATCH v1 1/1] SecurityPkg/Library: Add Tpm2NvUndefineSpaceSpecial to Tpm2CommandLib

 

Used to provision and maintain certain HW-defined NV spaces.

REF: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fbugzilla.tianocore.org%2Fshow_bug.cgi%3Fid%3D2994&amp;data=04%7C01%7CBret.Barkelew%40microsoft.com%7Cb7ae3c62047c48fc85d908d953833ca0%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632645397602953%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=s96M3RvxMOY831Vfr1nt%2Fz1h3cyb6jU9eFzvjKO7Dtc%3D&amp;reserved=0

Signed-off-by: Bret Barkelew <bret.barkelew@...>
Cc: Jiewen Yao <jiewen.yao@...>
Cc: Jian J Wang <jian.j.wang@...>
Cc: Qi Zhang <qi1.zhang@...>
Cc: Rahul Kumar <rahul1.kumar@...>
---
 SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c | 122 ++++++++++++++++++++
 SecurityPkg/Include/Library/Tpm2CommandLib.h       |  22 ++++
 2 files changed, 144 insertions(+)

diff --git a/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c b/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c
index 87572de20164..7931fade9190 100644
--- a/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c
+++ b/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c
@@ -24,6 +24,8 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
 #define RC_NV_UndefineSpace_authHandle      (TPM_RC_H + TPM_RC_1)

 #define RC_NV_UndefineSpace_nvIndex         (TPM_RC_H + TPM_RC_2)

 

+#define RC_NV_UndefineSpaceSpecial_nvIndex  (TPM_RC_H + TPM_RC_1)

+

 #define RC_NV_Read_authHandle               (TPM_RC_H + TPM_RC_1)

 #define RC_NV_Read_nvIndex                  (TPM_RC_H + TPM_RC_2)

 #define RC_NV_Read_size                     (TPM_RC_P + TPM_RC_1)

@@ -74,6 +76,20 @@ typedef struct {
   TPMS_AUTH_RESPONSE         AuthSession;

 } TPM2_NV_UNDEFINESPACE_RESPONSE;

 

+typedef struct {

+  TPM2_COMMAND_HEADER       Header;

+  TPMI_RH_NV_INDEX          NvIndex;

+  TPMI_RH_PLATFORM          Platform;

+  UINT32                    AuthSessionSize;

+  TPMS_AUTH_COMMAND         AuthSession;

+} TPM2_NV_UNDEFINESPACESPECIAL_COMMAND;

+

+typedef struct {

+  TPM2_RESPONSE_HEADER       Header;

+  UINT32                     AuthSessionSize;

+  TPMS_AUTH_RESPONSE         AuthSession;

+} TPM2_NV_UNDEFINESPACESPECIAL_RESPONSE;

+

 typedef struct {

   TPM2_COMMAND_HEADER       Header;

   TPMI_RH_NV_AUTH           AuthHandle;

@@ -506,6 +522,112 @@ Done:
   return Status;

 }

 

+/**

+  This command removes an index from the TPM.

+

+  @param[in]  NvIndex             The NV Index.

+  @param[in]  IndexAuthSession    Auth session context for the Index auth/policy

+  @param[in]  PlatAuthSession     Auth session context for the Platform auth/policy

+

+  @retval EFI_SUCCESS             Operation completed successfully.

+  @retval EFI_NOT_FOUND           The command was returned successfully, but NvIndex is not found.

+  @retval EFI_UNSUPPORTED         Selected NvIndex does not support deletion through this call.

+  @retval EFI_SECURITY_VIOLATION  Deletion is not authorized by current policy session.

+  @retval EFI_INVALID_PARAMETER   The command was unsuccessful.

+  @retval EFI_DEVICE_ERROR        The command was unsuccessful.

+**/

+EFI_STATUS

+EFIAPI

+Tpm2NvUndefineSpaceSpecial (

+  IN      TPMI_RH_NV_INDEX          NvIndex,

+  IN      TPMS_AUTH_COMMAND         *IndexAuthSession OPTIONAL,

+  IN      TPMS_AUTH_COMMAND         *PlatAuthSession OPTIONAL

+  )

+{

+  EFI_STATUS                              Status;

+  TPM2_NV_UNDEFINESPACESPECIAL_COMMAND    SendBuffer;

+  TPM2_NV_UNDEFINESPACESPECIAL_RESPONSE   RecvBuffer;

+  UINT32                                  SendBufferSize;

+  UINT32                                  RecvBufferSize;

+  UINT8                                   *Buffer;

+  UINT32                                  IndexAuthSize, PlatAuthSize;

+  TPM_RC                                  ResponseCode;

+

+  //

+  // Construct command

+  //

+  SendBuffer.Header.tag = SwapBytes16(TPM_ST_SESSIONS);

+  SendBuffer.Header.commandCode = SwapBytes32(TPM_CC_NV_UndefineSpaceSpecial);

+

+  SendBuffer.NvIndex = SwapBytes32 (NvIndex);

+  SendBuffer.Platform = SwapBytes32 (TPM_RH_PLATFORM);

+

+  //

+  // Marshall the Auth Sessions for the two handles.

+  Buffer = (UINT8 *)&SendBuffer.AuthSession;

+  // IndexAuthSession

+  IndexAuthSize = CopyAuthSessionCommand (IndexAuthSession, Buffer);

+  Buffer += IndexAuthSize;

+  // PlatAuthSession

+  PlatAuthSize = CopyAuthSessionCommand (PlatAuthSession, Buffer);

+  Buffer += PlatAuthSize;

+  // AuthSessionSize

+  SendBuffer.AuthSessionSize = SwapBytes32(IndexAuthSize + PlatAuthSize);

+

+  // Update total command size.

+  SendBufferSize = (UINT32)(Buffer - (UINT8 *)&SendBuffer);

+  SendBuffer.Header.paramSize = SwapBytes32 (SendBufferSize);

+

+  //

+  // send Tpm command

+  //

+  RecvBufferSize = sizeof (RecvBuffer);

+  Status = Tpm2SubmitCommand (SendBufferSize, (UINT8 *)&SendBuffer, &RecvBufferSize, (UINT8 *)&RecvBuffer);

+  if (EFI_ERROR (Status)) {

+    goto Done;

+  }

+

+  if (RecvBufferSize < sizeof (TPM2_RESPONSE_HEADER)) {

+    DEBUG ((EFI_D_ERROR, "Tpm2NvUndefineSpaceSpecial - RecvBufferSize Error - %x\n", RecvBufferSize));

+    Status = EFI_DEVICE_ERROR;

+    goto Done;

+  }

+

+  ResponseCode = SwapBytes32(RecvBuffer.Header.responseCode);

+  if (ResponseCode != TPM_RC_SUCCESS) {

+    DEBUG ((EFI_D_ERROR, "Tpm2NvUndefineSpaceSpecial - responseCode - %x\n", SwapBytes32(RecvBuffer.Header.responseCode)));

+  }

+  switch (ResponseCode) {

+  case TPM_RC_SUCCESS:

+    // return data

+    break;

+  case TPM_RC_ATTRIBUTES:

+  case TPM_RC_ATTRIBUTES + RC_NV_UndefineSpaceSpecial_nvIndex:

+    Status = EFI_UNSUPPORTED;

+    break;

+  case TPM_RC_NV_AUTHORIZATION:

+    Status = EFI_SECURITY_VIOLATION;

+    break;

+  case TPM_RC_HANDLE + RC_NV_UndefineSpaceSpecial_nvIndex: // TPM_RC_NV_DEFINED:

+    Status = EFI_NOT_FOUND;

+    break;

+  case TPM_RC_VALUE + RC_NV_UndefineSpace_nvIndex:

+    Status = EFI_INVALID_PARAMETER;

+    break;

+  default:

+    Status = EFI_DEVICE_ERROR;

+    break;

+  }

+

+Done:

+  //

+  // Clear AuthSession Content

+  //

+  ZeroMem (&SendBuffer, sizeof(SendBuffer));

+  ZeroMem (&RecvBuffer, sizeof(RecvBuffer));

+  return Status;

+} // Tpm2NvUndefineSpaceSpecial()

+

 /**

   This command reads a value from an area in NV memory previously defined by TPM2_NV_DefineSpace().

 

diff --git a/SecurityPkg/Include/Library/Tpm2CommandLib.h b/SecurityPkg/Include/Library/Tpm2CommandLib.h
index ee8eb622951c..8d7b4998d98d 100644
--- a/SecurityPkg/Include/Library/Tpm2CommandLib.h
+++ b/SecurityPkg/Include/Library/Tpm2CommandLib.h
@@ -364,6 +364,28 @@ Tpm2NvUndefineSpace (
   IN      TPMS_AUTH_COMMAND         *AuthSession OPTIONAL

   );

 

+/**

+  This command removes an index from the TPM.

+

+  @param[in]  NvIndex             The NV Index.

+  @param[in]  IndexAuthSession    Auth session context for the Index auth/policy

+  @param[in]  PlatAuthSession     Auth session context for the Platform auth/policy

+

+  @retval EFI_SUCCESS             Operation completed successfully.

+  @retval EFI_NOT_FOUND           The command was returned successfully, but NvIndex is not found.

+  @retval EFI_UNSUPPORTED         Selected NvIndex does not support deletion through this call.

+  @retval EFI_SECURITY_VIOLATION  Deletion is not authorized by current policy session.

+  @retval EFI_INVALID_PARAMETER   The command was unsuccessful.

+  @retval EFI_DEVICE_ERROR        The command was unsuccessful.

+**/

+EFI_STATUS

+EFIAPI

+Tpm2NvUndefineSpaceSpecial (

+  IN      TPMI_RH_NV_INDEX          NvIndex,

+  IN      TPMS_AUTH_COMMAND         *IndexAuthSession OPTIONAL,

+  IN      TPMS_AUTH_COMMAND         *PlatAuthSession OPTIONAL

+  );

+

 /**

   This command reads a value from an area in NV memory previously defined by TPM2_NV_DefineSpace().

 

--
2.31.1.windows.1



-=-=-=-=-=-=
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#78450): https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Fmessage%2F78450&amp;data=04%7C01%7CBret.Barkelew%40microsoft.com%7Cb7ae3c62047c48fc85d908d953833ca0%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632645397612922%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=CWxLwgp73z2XQEa%2FN77gsCwRF73xha0RZCKwcFTlrRE%3D&amp;reserved=0
Mute This Topic: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgroups.io%2Fmt%2F84555713%2F1822150&amp;data=04%7C01%7CBret.Barkelew%40microsoft.com%7Cb7ae3c62047c48fc85d908d953833ca0%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632645397612922%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=IWQ6E4yP0ECt3oYLYQa%2BnddGfcQEDMgfASlcxRuda%2BQ%3D&amp;reserved=0
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fedk2.groups.io%2Fg%2Fdevel%2Funsub&amp;data=04%7C01%7CBret.Barkelew%40microsoft.com%7Cb7ae3c62047c48fc85d908d953833ca0%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637632645397612922%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=qor4Y5FZEH8ch0AEmWDbe97FIQk4V1qx7IURcTHzjAU%3D&amp;reserved=0 [brbarkel@...]
-=-=-=-=-=-=

 


[PATCH v1 1/1] SecurityPkg/Library: Add Tpm2NvUndefineSpaceSpecial to Tpm2CommandLib

Bret Barkelew
 

Used to provision and maintain certain HW-defined NV spaces.

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

Signed-off-by: Bret Barkelew <bret.barkelew@microsoft.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jian J Wang <jian.j.wang@intel.com>
Cc: Qi Zhang <qi1.zhang@intel.com>
Cc: Rahul Kumar <rahul1.kumar@intel.com>
---
SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c | 122 +++++++++++++++++=
+++
SecurityPkg/Include/Library/Tpm2CommandLib.h | 22 ++++
2 files changed, 144 insertions(+)

diff --git a/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c b/SecurityP=
kg/Library/Tpm2CommandLib/Tpm2NVStorage.c
index 87572de20164..7931fade9190 100644
--- a/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c
+++ b/SecurityPkg/Library/Tpm2CommandLib/Tpm2NVStorage.c
@@ -24,6 +24,8 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
#define RC_NV_UndefineSpace_authHandle (TPM_RC_H + TPM_RC_1)=0D
#define RC_NV_UndefineSpace_nvIndex (TPM_RC_H + TPM_RC_2)=0D
=0D
+#define RC_NV_UndefineSpaceSpecial_nvIndex (TPM_RC_H + TPM_RC_1)=0D
+=0D
#define RC_NV_Read_authHandle (TPM_RC_H + TPM_RC_1)=0D
#define RC_NV_Read_nvIndex (TPM_RC_H + TPM_RC_2)=0D
#define RC_NV_Read_size (TPM_RC_P + TPM_RC_1)=0D
@@ -74,6 +76,20 @@ typedef struct {
TPMS_AUTH_RESPONSE AuthSession;=0D
} TPM2_NV_UNDEFINESPACE_RESPONSE;=0D
=0D
+typedef struct {=0D
+ TPM2_COMMAND_HEADER Header;=0D
+ TPMI_RH_NV_INDEX NvIndex;=0D
+ TPMI_RH_PLATFORM Platform;=0D
+ UINT32 AuthSessionSize;=0D
+ TPMS_AUTH_COMMAND AuthSession;=0D
+} TPM2_NV_UNDEFINESPACESPECIAL_COMMAND;=0D
+=0D
+typedef struct {=0D
+ TPM2_RESPONSE_HEADER Header;=0D
+ UINT32 AuthSessionSize;=0D
+ TPMS_AUTH_RESPONSE AuthSession;=0D
+} TPM2_NV_UNDEFINESPACESPECIAL_RESPONSE;=0D
+=0D
typedef struct {=0D
TPM2_COMMAND_HEADER Header;=0D
TPMI_RH_NV_AUTH AuthHandle;=0D
@@ -506,6 +522,112 @@ Done:
return Status;=0D
}=0D
=0D
+/**=0D
+ This command removes an index from the TPM.=0D
+=0D
+ @param[in] NvIndex The NV Index.=0D
+ @param[in] IndexAuthSession Auth session context for the Index auth/=
policy=0D
+ @param[in] PlatAuthSession Auth session context for the Platform au=
th/policy=0D
+=0D
+ @retval EFI_SUCCESS Operation completed successfully.=0D
+ @retval EFI_NOT_FOUND The command was returned successfully, b=
ut NvIndex is not found.=0D
+ @retval EFI_UNSUPPORTED Selected NvIndex does not support deleti=
on through this call.=0D
+ @retval EFI_SECURITY_VIOLATION Deletion is not authorized by current po=
licy session.=0D
+ @retval EFI_INVALID_PARAMETER The command was unsuccessful.=0D
+ @retval EFI_DEVICE_ERROR The command was unsuccessful.=0D
+**/=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Tpm2NvUndefineSpaceSpecial (=0D
+ IN TPMI_RH_NV_INDEX NvIndex,=0D
+ IN TPMS_AUTH_COMMAND *IndexAuthSession OPTIONAL,=0D
+ IN TPMS_AUTH_COMMAND *PlatAuthSession OPTIONAL=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+ TPM2_NV_UNDEFINESPACESPECIAL_COMMAND SendBuffer;=0D
+ TPM2_NV_UNDEFINESPACESPECIAL_RESPONSE RecvBuffer;=0D
+ UINT32 SendBufferSize;=0D
+ UINT32 RecvBufferSize;=0D
+ UINT8 *Buffer;=0D
+ UINT32 IndexAuthSize, PlatAuthSize;=0D
+ TPM_RC ResponseCode;=0D
+=0D
+ //=0D
+ // Construct command=0D
+ //=0D
+ SendBuffer.Header.tag =3D SwapBytes16(TPM_ST_SESSIONS);=0D
+ SendBuffer.Header.commandCode =3D SwapBytes32(TPM_CC_NV_UndefineSpaceSpe=
cial);=0D
+=0D
+ SendBuffer.NvIndex =3D SwapBytes32 (NvIndex);=0D
+ SendBuffer.Platform =3D SwapBytes32 (TPM_RH_PLATFORM);=0D
+=0D
+ //=0D
+ // Marshall the Auth Sessions for the two handles.=0D
+ Buffer =3D (UINT8 *)&SendBuffer.AuthSession;=0D
+ // IndexAuthSession=0D
+ IndexAuthSize =3D CopyAuthSessionCommand (IndexAuthSession, Buffer);=0D
+ Buffer +=3D IndexAuthSize;=0D
+ // PlatAuthSession=0D
+ PlatAuthSize =3D CopyAuthSessionCommand (PlatAuthSession, Buffer);=0D
+ Buffer +=3D PlatAuthSize;=0D
+ // AuthSessionSize=0D
+ SendBuffer.AuthSessionSize =3D SwapBytes32(IndexAuthSize + PlatAuthSize)=
;=0D
+=0D
+ // Update total command size.=0D
+ SendBufferSize =3D (UINT32)(Buffer - (UINT8 *)&SendBuffer);=0D
+ SendBuffer.Header.paramSize =3D SwapBytes32 (SendBufferSize);=0D
+=0D
+ //=0D
+ // send Tpm command=0D
+ //=0D
+ RecvBufferSize =3D sizeof (RecvBuffer);=0D
+ Status =3D Tpm2SubmitCommand (SendBufferSize, (UINT8 *)&SendBuffer, &Rec=
vBufferSize, (UINT8 *)&RecvBuffer);=0D
+ if (EFI_ERROR (Status)) {=0D
+ goto Done;=0D
+ }=0D
+=0D
+ if (RecvBufferSize < sizeof (TPM2_RESPONSE_HEADER)) {=0D
+ DEBUG ((EFI_D_ERROR, "Tpm2NvUndefineSpaceSpecial - RecvBufferSize Erro=
r - %x\n", RecvBufferSize));=0D
+ Status =3D EFI_DEVICE_ERROR;=0D
+ goto Done;=0D
+ }=0D
+=0D
+ ResponseCode =3D SwapBytes32(RecvBuffer.Header.responseCode);=0D
+ if (ResponseCode !=3D TPM_RC_SUCCESS) {=0D
+ DEBUG ((EFI_D_ERROR, "Tpm2NvUndefineSpaceSpecial - responseCode - %x\n=
", SwapBytes32(RecvBuffer.Header.responseCode)));=0D
+ }=0D
+ switch (ResponseCode) {=0D
+ case TPM_RC_SUCCESS:=0D
+ // return data=0D
+ break;=0D
+ case TPM_RC_ATTRIBUTES:=0D
+ case TPM_RC_ATTRIBUTES + RC_NV_UndefineSpaceSpecial_nvIndex:=0D
+ Status =3D EFI_UNSUPPORTED;=0D
+ break;=0D
+ case TPM_RC_NV_AUTHORIZATION:=0D
+ Status =3D EFI_SECURITY_VIOLATION;=0D
+ break;=0D
+ case TPM_RC_HANDLE + RC_NV_UndefineSpaceSpecial_nvIndex: // TPM_RC_NV_DE=
FINED:=0D
+ Status =3D EFI_NOT_FOUND;=0D
+ break;=0D
+ case TPM_RC_VALUE + RC_NV_UndefineSpace_nvIndex:=0D
+ Status =3D EFI_INVALID_PARAMETER;=0D
+ break;=0D
+ default:=0D
+ Status =3D EFI_DEVICE_ERROR;=0D
+ break;=0D
+ }=0D
+=0D
+Done:=0D
+ //=0D
+ // Clear AuthSession Content=0D
+ //=0D
+ ZeroMem (&SendBuffer, sizeof(SendBuffer));=0D
+ ZeroMem (&RecvBuffer, sizeof(RecvBuffer));=0D
+ return Status;=0D
+} // Tpm2NvUndefineSpaceSpecial()=0D
+=0D
/**=0D
This command reads a value from an area in NV memory previously defined =
by TPM2_NV_DefineSpace().=0D
=0D
diff --git a/SecurityPkg/Include/Library/Tpm2CommandLib.h b/SecurityPkg/Inc=
lude/Library/Tpm2CommandLib.h
index ee8eb622951c..8d7b4998d98d 100644
--- a/SecurityPkg/Include/Library/Tpm2CommandLib.h
+++ b/SecurityPkg/Include/Library/Tpm2CommandLib.h
@@ -364,6 +364,28 @@ Tpm2NvUndefineSpace (
IN TPMS_AUTH_COMMAND *AuthSession OPTIONAL=0D
);=0D
=0D
+/**=0D
+ This command removes an index from the TPM.=0D
+=0D
+ @param[in] NvIndex The NV Index.=0D
+ @param[in] IndexAuthSession Auth session context for the Index auth/=
policy=0D
+ @param[in] PlatAuthSession Auth session context for the Platform au=
th/policy=0D
+=0D
+ @retval EFI_SUCCESS Operation completed successfully.=0D
+ @retval EFI_NOT_FOUND The command was returned successfully, b=
ut NvIndex is not found.=0D
+ @retval EFI_UNSUPPORTED Selected NvIndex does not support deleti=
on through this call.=0D
+ @retval EFI_SECURITY_VIOLATION Deletion is not authorized by current po=
licy session.=0D
+ @retval EFI_INVALID_PARAMETER The command was unsuccessful.=0D
+ @retval EFI_DEVICE_ERROR The command was unsuccessful.=0D
+**/=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Tpm2NvUndefineSpaceSpecial (=0D
+ IN TPMI_RH_NV_INDEX NvIndex,=0D
+ IN TPMS_AUTH_COMMAND *IndexAuthSession OPTIONAL,=0D
+ IN TPMS_AUTH_COMMAND *PlatAuthSession OPTIONAL=0D
+ );=0D
+=0D
/**=0D
This command reads a value from an area in NV memory previously defined =
by TPM2_NV_DefineSpace().=0D
=0D
--=20
2.31.1.windows.1


[edk2][PATCH v1 1/1] StandaloneMmPkg: add support to populate StMM boot data from device tree

Sayanta Pattanayak
 

Introduce support to populate StMM boot data via DTS parsing. The DTB is
passed as a boot argument by a binary of higer exception level.
Previously it was achieved by placing the boot data structure in a
shared buffer and the address of this shared buffer was passed by the
binary of higher exception level. Now either of the option can be used
for populating StMM boot info.

StMM boot information structure binding in device tree can be of followin=
g
prototype. Property values are not mentioned here.

bootarg {
compatible =3D "bootargs";
h_type =3D <..>;
h_version =3D <..>;
h_size =3D <..>;
h_attr =3D <..>;
sp_mem_base =3D <..>;
sp_mem_limit =3D <..>;
sp_image_base =3D <..>;
sp_stack_base =3D <..>;
sp_heap_base =3D <..>;
sp_ns_comm_buf_base =3D <..>;
sp_shared_buf_base =3D <..>;
sp_image_size =3D <..>;
sp_pcpu_stack_size =3D <..>;
sp_heap_size =3D <..>;
sp_ns_comm_buf_size =3D <..>;
sp_shared_buf_size =3D <..>;
num_sp_mem_regions =3D <..>;
num_cpus =3D <..>;
};

Addition of DTS supoort involves a dependency on FdtLib from EmbeddedPkg.

Signed-off-by: Sayanta Pattanayak <sayanta.pattanayak@arm.com>
---
Link to github branch with this patch -
https://github.com/SayantaP-arm/edk2/tree/stmm-dts

StandaloneMmPkg/StandaloneMmPkg.dsc =
| 1 +
StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCoreEntry=
Point.inf | 3 +
StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/StandaloneMmC=
oreEntryPoint.c | 153 ++++++++++++++++++--
3 files changed, 143 insertions(+), 14 deletions(-)

diff --git a/StandaloneMmPkg/StandaloneMmPkg.dsc b/StandaloneMmPkg/Standa=
loneMmPkg.dsc
index 0c45df95e2dd..e3a3a6ee3ba1 100644
--- a/StandaloneMmPkg/StandaloneMmPkg.dsc
+++ b/StandaloneMmPkg/StandaloneMmPkg.dsc
@@ -49,6 +49,7 @@
HobLib|StandaloneMmPkg/Library/StandaloneMmHobLib/StandaloneMmHobLib.i=
nf
IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
MemLib|StandaloneMmPkg/Library/StandaloneMmMemLib/StandaloneMmMemLib.i=
nf
+ FdtLib|EmbeddedPkg/Library/FdtLib/FdtLib.inf
MemoryAllocationLib|StandaloneMmPkg/Library/StandaloneMmCoreMemoryAllo=
cationLib/StandaloneMmCoreMemoryAllocationLib.inf
MmServicesTableLib|MdePkg/Library/StandaloneMmServicesTableLib/Standal=
oneMmServicesTableLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/Standalon=
eMmCoreEntryPoint.inf b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoin=
t/StandaloneMmCoreEntryPoint.inf
index 4fa426f58ef4..0a2e519dd664 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCore=
EntryPoint.inf
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/StandaloneMmCore=
EntryPoint.inf
@@ -30,6 +30,7 @@
X64/StandaloneMmCoreEntryPoint.c
=20
[Packages]
+ EmbeddedPkg/EmbeddedPkg.dec
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
StandaloneMmPkg/StandaloneMmPkg.dec
@@ -40,10 +41,12 @@
[LibraryClasses]
BaseLib
DebugLib
+ FdtLib
=20
[LibraryClasses.AARCH64]
StandaloneMmMmuLib
ArmSvcLib
+ FdtLib
=20
[Guids]
gMpInformationHobGuid
diff --git a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/S=
tandaloneMmCoreEntryPoint.c b/StandaloneMmPkg/Library/StandaloneMmCoreEnt=
ryPoint/AArch64/StandaloneMmCoreEntryPoint.c
index 6c50f470aa35..cc09d75dac36 100644
--- a/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/Standalo=
neMmCoreEntryPoint.c
+++ b/StandaloneMmPkg/Library/StandaloneMmCoreEntryPoint/AArch64/Standalo=
neMmCoreEntryPoint.c
@@ -16,6 +16,7 @@ SPDX-License-Identifier: BSD-2-Clause-Patent
#include <Guid/MmramMemoryReserve.h>
#include <Guid/MpInformation.h>
=20
+#include <libfdt.h>
#include <Library/ArmMmuLib.h>
#include <Library/ArmSvcLib.h>
#include <Library/DebugLib.h>
@@ -45,33 +46,31 @@ STATIC CONST UINT32 mSpmMinorVerFfa =3D SPM_MINOR_VER=
SION_FFA;
PI_MM_ARM_TF_CPU_DRIVER_ENTRYPOINT CpuDriverEntryPoint =3D NULL;
=20
/**
- Retrieve a pointer to and print the boot information passed by privile=
ged
- secure firmware.
+ Prints boot information.
=20
- @param [in] SharedBufAddress The pointer memory shared with privile=
ged
- firmware.
+ This function prints the boot information, which is passed by privileg=
ed
+ secure firmware through shared buffer or other mechanism.
=20
+ @param [in] PayloadBootInfo Pointer to StandaloneMM Boot Info struc=
ture.
**/
-EFI_SECURE_PARTITION_BOOT_INFO *
-GetAndPrintBootinformation (
- IN VOID *SharedBufAddress
+VOID
+PrintBootinformation (
+ IN EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo
)
{
- EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
EFI_SECURE_PARTITION_CPU_INFO *PayloadCpuInfo;
UINTN Index;
=20
- PayloadBootInfo =3D (EFI_SECURE_PARTITION_BOOT_INFO *) SharedBufAddres=
s;
=20
if (PayloadBootInfo =3D=3D NULL) {
DEBUG ((DEBUG_ERROR, "PayloadBootInfo NULL\n"));
- return NULL;
+ return;
}
=20
if (PayloadBootInfo->Header.Version !=3D BOOT_PAYLOAD_VERSION) {
DEBUG ((DEBUG_ERROR, "Boot Information Version Mismatch. Current=3D0=
x%x, Expected=3D0x%x.\n",
PayloadBootInfo->Header.Version, BOOT_PAYLOAD_VERSION));
- return NULL;
+ return;
}
=20
DEBUG ((DEBUG_INFO, "NumSpMemRegions - 0x%x\n", PayloadBootInfo->NumSp=
MemRegions));
@@ -96,7 +95,7 @@ GetAndPrintBootinformation (
=20
if (PayloadCpuInfo =3D=3D NULL) {
DEBUG ((DEBUG_ERROR, "PayloadCpuInfo NULL\n"));
- return NULL;
+ return;
}
=20
for (Index =3D 0; Index < PayloadBootInfo->NumCpus; Index++) {
@@ -105,7 +104,7 @@ GetAndPrintBootinformation (
DEBUG ((DEBUG_INFO, "Flags - 0x%x\n", PayloadCpuInfo[Index=
].Flags));
}
=20
- return PayloadBootInfo;
+ return;
}
=20
/**
@@ -194,6 +193,119 @@ DelegatedEventLoop (
}
}
=20
+/**
+ Populates StandAloneMM boot information structure.
+
+ This function receives dtb Address, where StMM Boot information specif=
ic
+ properties will be looked out to form the booting structure of type
+ EFI_SECURE_PARTITION_BOOT_INFO. At first, the properties for StandAlon=
eMM
+ ConfigSize and Memory limit will be checked out. Boot information wil=
l
+ be stored at address (Memory Limit - ConfigSize). Thereafter all boot
+ information specific properties will be parsed and corresponding value=
s
+ will be obtained.
+
+ @param [out] BootInfo Pointer, where Boot Info structure will be po=
pulated.
+ @param [in] DtbAddress Address of the Device tree from where Boot
+ information will be fetched.
+**/
+VOID
+PopulateBootinformation (
+ OUT EFI_SECURE_PARTITION_BOOT_INFO **BootInfo,
+ IN VOID *DtbAddress
+)
+{
+ INT32 Offset;
+ CONST UINT32 *Property;
+ CONST UINT64 *Property64;
+ UINT32 ConfigSize;
+ UINT64 SpMemLimit;
+ EFI_SECURE_PARTITION_BOOT_INFO *PayloadBootInfo;
+
+ Offset =3D fdt_node_offset_by_compatible (DtbAddress, -1, "config-size=
");
+ if (Offset < 0) {
+ DEBUG ((DEBUG_WARN, "Total Config Size is not defined\n"));
+ } else {
+ Property =3D fdt_getprop (DtbAddress, Offset, "size", NULL);
+ if (Property) {
+ ConfigSize =3D fdt32_to_cpu (*Property);
+ DEBUG ((DEBUG_INFO, "stmm dtb config-size =3D 0x%x \n", ConfigSiz=
e));
+ }
+ }
+
+ Offset =3D fdt_node_offset_by_compatible (DtbAddress, -1, "bootargs");
+ if (Offset >=3D 0) {
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_mem_limit", NUL=
L);
+ SpMemLimit =3D fdt64_to_cpu (*Property64);
+ }
+
+ if (SpMemLimit && ConfigSize)
+ PayloadBootInfo =3D
+ (EFI_SECURE_PARTITION_BOOT_INFO *)(SpMemLimit - ConfigSize);
+
+ if (PayloadBootInfo) {
+ PayloadBootInfo->SpMemLimit =3D SpMemLimit;
+
+ Property =3D fdt_getprop (DtbAddress, Offset, "h_type", NULL);
+ PayloadBootInfo->Header.Type =3D (UINT8) fdt32_to_cpu(*Property);
+
+ Property =3D fdt_getprop (DtbAddress, Offset, "h_version", NULL);
+ PayloadBootInfo->Header.Version =3D (UINT8) fdt32_to_cpu(*Property);
+
+ Property =3D fdt_getprop (DtbAddress, Offset, "h_size", NULL);
+ PayloadBootInfo->Header.Size =3D (UINT8) fdt32_to_cpu(*Property);
+
+ Property =3D fdt_getprop (DtbAddress, Offset, "h_attr", NULL);
+ PayloadBootInfo->Header.Attr =3D fdt32_to_cpu(*Property);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_mem_base", NULL=
);
+ PayloadBootInfo->SpMemBase =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_image_base", NU=
LL);
+ PayloadBootInfo->SpImageBase =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_stack_base", NU=
LL);
+ PayloadBootInfo->SpStackBase =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_heap_base", NUL=
L);
+ PayloadBootInfo->SpHeapBase =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_bas=
e", NULL);
+ PayloadBootInfo->SpNsCommBufBase =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_shared_buf_base=
", NULL);
+ PayloadBootInfo->SpSharedBufBase =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_image_size", NU=
LL);
+ PayloadBootInfo->SpImageSize =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_pcpu_stack_size=
", NULL);
+ PayloadBootInfo->SpPcpuStackSize =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_heap_size", NUL=
L);
+ PayloadBootInfo->SpHeapSize =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_ns_comm_buf_siz=
e", NULL);
+ PayloadBootInfo->SpNsCommBufSize =3D fdt64_to_cpu(*Property64);
+
+ Property64 =3D fdt_getprop (DtbAddress, Offset, "sp_shared_buf_size=
", NULL);
+ PayloadBootInfo->SpPcpuSharedBufSize =3D fdt64_to_cpu(*Property64);
+
+ Property =3D fdt_getprop (DtbAddress, Offset, "num_sp_mem_regions",=
NULL);
+ PayloadBootInfo->NumSpMemRegions =3D fdt32_to_cpu(*Property);
+
+ Property =3D fdt_getprop (DtbAddress, Offset, "num_cpus", NULL);
+ PayloadBootInfo->NumCpus =3D fdt32_to_cpu(*Property);
+
+ PayloadBootInfo->CpuInfo =3D
+ (EFI_SECURE_PARTITION_CPU_INFO *)((UINT64)PayloadBootInfo +
+ sizeof(EFI_SECURE_PARTITION_BOOT=
_INFO));
+ }
+
+ *BootInfo =3D PayloadBootInfo;
+
+ return;
+}
+
/**
Query the SPM version, check compatibility and return success if compa=
tible.
=20
@@ -313,6 +425,7 @@ _ModuleEntryPoint (
VOID *TeData;
UINTN TeDataSize;
EFI_PHYSICAL_ADDRESS ImageBase;
+ VOID *DtbAddress;
=20
// Get Secure Partition Manager Version Information
Status =3D GetSpmVersion ();
@@ -320,12 +433,24 @@ _ModuleEntryPoint (
goto finish;
}
=20
- PayloadBootInfo =3D GetAndPrintBootinformation (SharedBufAddress);
+ // In cookie1 the DTB address is passed. With reference to DTB, Boot
+ // info structure can be populated.
+ // If cookie1 doesn't have any value, then Boot info is copied from
+ // Sharedbuffer.
+ if (cookie1) {
+ DtbAddress =3D (void *)cookie1;
+ PopulateBootinformation (&PayloadBootInfo, DtbAddress);
+ } else {
+ PayloadBootInfo =3D (EFI_SECURE_PARTITION_BOOT_INFO *)SharedBufAddre=
ss;
+ }
+
if (PayloadBootInfo =3D=3D NULL) {
Status =3D EFI_UNSUPPORTED;
goto finish;
}
=20
+ PrintBootinformation (PayloadBootInfo);
+
// Locate PE/COFF File information for the Standalone MM core module
Status =3D LocateStandaloneMmCorePeCoffData (
(EFI_FIRMWARE_VOLUME_HEADER *) PayloadBootInfo->SpImageBase=
,
--=20
2.17.1


Re: [PATCH] SecurityPkg: TPM must go to Idle state on CRB command completion.

Rodrigo Gonzalez del Cueto
 

Missed adding the Bugzilla reference to the patch.

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


[Patch 2/3] Ext4Pkg: Add Ext4Dxe driver.

Pedro Falcato
 

Adds a UEFI EXT4 filesystem driver that implements the EFI_FILE_PROTOCOL
and EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.

Cc: Leif Lindholm <leif@nuviainc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>

Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com>
---
Features/Ext4Pkg/Ext4Dxe/BlockGroup.c | 208 ++++++
Features/Ext4Pkg/Ext4Dxe/Collation.c | 157 +++++
Features/Ext4Pkg/Ext4Dxe/Crc16.c | 75 ++
Features/Ext4Pkg/Ext4Dxe/Crc32c.c | 84 +++
Features/Ext4Pkg/Ext4Dxe/Directory.c | 492 ++++++++++++++
Features/Ext4Pkg/Ext4Dxe/DiskUtil.c | 83 +++
Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h | 450 ++++++++++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c | 454 +++++++++++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h | 942 ++++++++++++++++++++++++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf | 147 ++++
Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni | 15 +
Features/Ext4Pkg/Ext4Dxe/Extents.c | 616 +++++++++++++++++
Features/Ext4Pkg/Ext4Dxe/File.c | 583 ++++++++++++++++
Features/Ext4Pkg/Ext4Dxe/Inode.c | 468 +++++++++++++
Features/Ext4Pkg/Ext4Dxe/Partition.c | 120 ++++
Features/Ext4Pkg/Ext4Dxe/Superblock.c | 257 +++++++
16 files changed, 5151 insertions(+)
create mode 100644 Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Collation.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Crc16.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Crc32c.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Directory.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Extents.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/File.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Inode.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Partition.c
create mode 100644 Features/Ext4Pkg/Ext4Dxe/Superblock.c

diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Features/Ext4Pkg/Ext4D=
xe/BlockGroup.c
new file mode 100644
index 0000000000..10a82d40a0
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
@@ -0,0 +1,208 @@
+/**=0D
+ @file Block group related routines=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>=0D
+=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+/**=0D
+ Reads an inode from disk.=0D
+=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+ @param[in] InodeNum Number of the desired Inode=0D
+ @param[out] OutIno Pointer to where it will be stored a pointer t=
o the read inode.=0D
+=0D
+ @return Status of the inode read.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4ReadInode (=0D
+ IN EXT4_PARTITION *Partition, IN EXT4_INO_NR InodeNum, OUT EXT4_INODE **=
OutIno=0D
+ )=0D
+{=0D
+ UINT64 InodeOffset;=0D
+ UINT32 BlockGroupNumber;=0D
+ EXT4_INODE *Inode;=0D
+ EXT4_BLOCK_GROUP_DESC *BlockGroup;=0D
+ EXT4_BLOCK_NR InodeTableStart;=0D
+ EFI_STATUS Status;=0D
+=0D
+ BlockGroupNumber =3D (UINT32)DivU64x64Remainder (=0D
+ InodeNum - 1,=0D
+ Partition->SuperBlock.s_inodes_per_group,=0D
+ &InodeOffset=0D
+ );=0D
+=0D
+ // Check for the block group number's correctness=0D
+ if (BlockGroupNumber >=3D Partition->NumberBlockGroups) {=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ Inode =3D Ext4AllocateInode (Partition);=0D
+=0D
+ if (Inode =3D=3D NULL) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ BlockGroup =3D Ext4GetBlockGroupDesc (Partition, BlockGroupNumber);=0D
+=0D
+ // Note: We'll need to check INODE_UNINIT and friends when we add write =
support=0D
+=0D
+ InodeTableStart =3D Ext4MakeBlockNumberFromHalfs (=0D
+ Partition,=0D
+ BlockGroup->bg_inode_table_lo,=0D
+ BlockGroup->bg_inode_table_hi=0D
+ );=0D
+=0D
+ Status =3D Ext4ReadDiskIo (=0D
+ Partition,=0D
+ Inode,=0D
+ Partition->InodeSize,=0D
+ Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOff=
set * Partition->InodeSize=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ DEBUG ((=0D
+ EFI_D_ERROR,=0D
+ "[ext4] Error reading inode: status %x; inode offset %lx"=0D
+ " inode table start %lu block group %lu\n",=0D
+ Status,=0D
+ InodeOffset,=0D
+ InodeTableStart,=0D
+ BlockGroupNumber=0D
+ ));=0D
+ FreePool (Inode);=0D
+ return Status;=0D
+ }=0D
+=0D
+ if (!Ext4CheckInodeChecksum (Partition, Inode, InodeNum)) {=0D
+ DEBUG ((=0D
+ EFI_D_ERROR,=0D
+ "[ext4] Inode %llu has invalid checksum (calculated %x)\n",=0D
+ InodeNum,=0D
+ Ext4CalculateInodeChecksum (Partition, Inode, InodeNum)=0D
+ ));=0D
+ FreePool (Inode);=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ *OutIno =3D Inode;=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Calculates the checksum of the block group descriptor for METADATA_CSUM=
enabled filesystems.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.=
=0D
+ @param[in] BlockGroupNum Number of the block group.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+STATIC=0D
+UINT16=0D
+Ext4CalculateBlockGroupDescChecksumMetadataCsum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D
+ IN UINT32 BlockGroupNum=0D
+ )=0D
+{=0D
+ UINT32 Csum;=0D
+ UINT16 Dummy;=0D
+=0D
+ Dummy =3D 0;=0D
+=0D
+ Csum =3D Ext4CalculateChecksum (Partition, &BlockGroupNum, sizeof (Block=
GroupNum), Partition->InitialSeed);=0D
+ Csum =3D Ext4CalculateChecksum (Partition, BlockGroupDesc, OFFSET_OF (EX=
T4_BLOCK_GROUP_DESC, bg_checksum), Csum);=0D
+ Csum =3D Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Csum)=
;=0D
+ Csum =3D=0D
+ Ext4CalculateChecksum (=0D
+ Partition,=0D
+ &BlockGroupDesc->bg_block_bitmap_hi,=0D
+ Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bit=
map_hi),=0D
+ Csum=0D
+ );=0D
+ return (UINT16)Csum;=0D
+}=0D
+=0D
+/**=0D
+ Calculates the checksum of the block group descriptor for GDT_CSUM enab=
led filesystems.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.=
=0D
+ @param[in] BlockGroupNum Number of the block group.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+STATIC=0D
+UINT16=0D
+Ext4CalculateBlockGroupDescChecksumGdtCsum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D
+ IN UINT32 BlockGroupNum=0D
+ )=0D
+{=0D
+ UINT16 Csum;=0D
+ UINT16 Dummy;=0D
+=0D
+ Dummy =3D 0;=0D
+=0D
+ Csum =3D CalculateCrc16 (Partition->SuperBlock.s_uuid, 16, 0);=0D
+ Csum =3D CalculateCrc16 (&BlockGroupNum, sizeof (BlockGroupNum), Csum);=
=0D
+ Csum =3D CalculateCrc16 (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DES=
C, bg_checksum), Csum);=0D
+ Csum =3D CalculateCrc16 (&Dummy, sizeof (Dummy), Csum);=0D
+ Csum =3D=0D
+ CalculateCrc16 (=0D
+ &BlockGroupDesc->bg_block_bitmap_hi,=0D
+ Partition->DescSize - OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_block_bit=
map_hi),=0D
+ Csum=0D
+ );=0D
+ return Csum;=0D
+}=0D
+=0D
+/**=0D
+ Checks if the checksum of the block group descriptor is correct.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.=
=0D
+ @param[in] BlockGroupNum Number of the block group.=0D
+=0D
+ @return TRUE if checksum is correct, FALSE if there is corruption.=0D
+*/=0D
+BOOLEAN=0D
+Ext4VerifyBlockGroupDescChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D
+ IN UINT32 BlockGroupNum=0D
+ )=0D
+{=0D
+ if(!Ext4HasMetadataCsum (Partition) && !Ext4HasGdtCsum (Partition)) {=0D
+ return TRUE;=0D
+ }=0D
+=0D
+ return Ext4CalculateBlockGroupDescChecksum (Partition, BlockGroupDesc, B=
lockGroupNum) =3D=3D BlockGroupDesc->bg_checksum;=0D
+}=0D
+=0D
+/**=0D
+ Calculates the checksum of the block group descriptor.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.=
=0D
+ @param[in] BlockGroupNum Number of the block group.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT16=0D
+Ext4CalculateBlockGroupDescChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D
+ IN UINT32 BlockGroupNum=0D
+ )=0D
+{=0D
+ if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) {=
=0D
+ return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, Blo=
ckGroupDesc, BlockGroupNum);=0D
+ } else if(Partition->FeaturesRoCompat & EXT4_FEATURE_RO_COMPAT_GDT_CSUM)=
{=0D
+ return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGro=
upDesc, BlockGroupNum);=0D
+ }=0D
+=0D
+ return 0;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Collation.c b/Features/Ext4Pkg/Ext4Dx=
e/Collation.c
new file mode 100644
index 0000000000..92d6a9184b
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Collation.c
@@ -0,0 +1,157 @@
+/**=0D
+ @file Unicode collation routines=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ Copyright (c) 2005 - 2017, Intel Corporation. All rights reserved.=0D
+=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include <Uefi.h>=0D
+=0D
+#include <Library/UefiLib.h>=0D
+#include <Library/UefiBootServicesTableLib.h>=0D
+#include <Library/MemoryAllocationLib.h>=0D
+=0D
+#include <Protocol/UnicodeCollation.h>=0D
+=0D
+STATIC EFI_UNICODE_COLLATION_PROTOCOL *gUnicodeCollationInterface =3D NUL=
L;=0D
+=0D
+/*=0D
+ * Note: This code is heavily based on FatPkg's Unicode collation, since t=
hey seem to know what=0D
+ * they're doing.=0D
+ * PS: Maybe all this code could be put in a library? It looks heavily sha=
reable.=0D
+ */=0D
+STATIC=0D
+EFI_STATUS=0D
+Ext4InitialiseUnicodeCollationInternal (=0D
+ IN EFI_HANDLE DriverHandle,=0D
+ IN EFI_GUID *ProtocolGuid,=0D
+ IN CONST CHAR16 *VariableName,=0D
+ IN CONST CHAR8 *DefaultLanguage=0D
+ )=0D
+{=0D
+ UINTN NumHandles;=0D
+ EFI_HANDLE *Handles;=0D
+ EFI_UNICODE_COLLATION_PROTOCOL *Uci;=0D
+ BOOLEAN Iso639Language;=0D
+ CHAR8 *Language;=0D
+ EFI_STATUS RetStatus;=0D
+ EFI_STATUS Status;=0D
+=0D
+ Iso639Language =3D (BOOLEAN)(ProtocolGuid =3D=3D &gEfiUnicodeCollationPr=
otocolGuid);=0D
+ RetStatus =3D EFI_UNSUPPORTED;=0D
+ GetEfiGlobalVariable2 (VariableName, (VOID **)&Language, NULL);=0D
+=0D
+ Status =3D gBS->LocateHandleBuffer (=0D
+ ByProtocol,=0D
+ ProtocolGuid,=0D
+ NULL,=0D
+ &NumHandles,=0D
+ &Handles=0D
+ );=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ // Note: FatPkg also doesn't close unneeded protocols.=0D
+ // This looks like a leak but I'm likely wrong.=0D
+ for(UINTN i =3D 0; i < NumHandles; i++) {=0D
+ Status =3D gBS->OpenProtocol (=0D
+ Handles[i],=0D
+ ProtocolGuid,=0D
+ (VOID **)&Uci,=0D
+ DriverHandle,=0D
+ NULL,=0D
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL=0D
+ );=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ continue;=0D
+ }=0D
+=0D
+ CHAR8 *BestLanguage =3D GetBestLanguage (=0D
+ Uci->SupportedLanguages,=0D
+ Iso639Language,=0D
+ (Language =3D=3D NULL) ? "" : Language,=0D
+ DefaultLanguage,=0D
+ NULL=0D
+ );=0D
+ if (BestLanguage !=3D NULL) {=0D
+ FreePool (BestLanguage);=0D
+ gUnicodeCollationInterface =3D Uci;=0D
+ RetStatus =3D EFI_SUCCESS;=0D
+ break;=0D
+ }=0D
+ }=0D
+=0D
+ if (Language !=3D NULL) {=0D
+ FreePool (Language);=0D
+ }=0D
+=0D
+ FreePool (Handles);=0D
+ return RetStatus;=0D
+}=0D
+=0D
+/**=0D
+ Initialises Unicode collation, which is needed for case-insensitive str=
ing comparisons=0D
+ within the driver (a good example of an application of this is filename=
comparison).=0D
+=0D
+ @param[in] DriverHandle Handle to the driver image.=0D
+=0D
+ @retval EFI_SUCCESS Unicode collation was successfully initialised.=0D
+ @retval !EFI_SUCCESS Failure.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4InitialiseUnicodeCollation (=0D
+ EFI_HANDLE DriverHandle=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+=0D
+ Status =3D EFI_UNSUPPORTED;=0D
+=0D
+ //=0D
+ // First try to use RFC 4646 Unicode Collation 2 Protocol.=0D
+ //=0D
+ Status =3D Ext4InitialiseUnicodeCollationInternal (=0D
+ DriverHandle,=0D
+ &gEfiUnicodeCollation2ProtocolGuid,=0D
+ L"PlatformLang",=0D
+ (CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultPlatformLang)=
=0D
+ );=0D
+ //=0D
+ // If the attempt to use Unicode Collation 2 Protocol fails, then we fal=
l back=0D
+ // on the ISO 639-2 Unicode Collation Protocol.=0D
+ //=0D
+ if (EFI_ERROR (Status)) {=0D
+ Status =3D Ext4InitialiseUnicodeCollationInternal (=0D
+ DriverHandle,=0D
+ &gEfiUnicodeCollationProtocolGuid,=0D
+ L"Lang",=0D
+ (CONST CHAR8 *)PcdGetPtr (PcdUefiVariableDefaultLang)=0D
+ );=0D
+ }=0D
+=0D
+ return Status;=0D
+}=0D
+=0D
+/**=0D
+ Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATI=
ON_PROTOCOL's StriColl=0D
+ for more details.=0D
+=0D
+ @param[in] Str1 Pointer to a null terminated string.=0D
+ @param[in] Str2 Pointer to a null terminated string.=0D
+=0D
+ @retval 0 Str1 is equivalent to Str2.=0D
+ @retval >0 Str1 is lexically greater than Str2.=0D
+ @retval <0 Str1 is lexically less than Str2.=0D
+*/=0D
+INTN=0D
+Ext4StrCmpInsensitive (=0D
+ IN CHAR16 *Str1,=0D
+ IN CHAR16 *Str2=0D
+ )=0D
+{=0D
+ return gUnicodeCollationInterface->StriColl (gUnicodeCollationInterface,=
Str1, Str2);=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc16.c b/Features/Ext4Pkg/Ext4Dxe/Cr=
c16.c
new file mode 100644
index 0000000000..25a11cfde3
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Crc16.c
@@ -0,0 +1,75 @@
+/**=0D
+ @file CRC16 calculation routines.=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include <Uefi.h>=0D
+=0D
+STATIC CONST UINT16 gCrc16LookupTable[256] =3D=0D
+{=0D
+ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,=0D
+ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,=0D
+ 0x0919, 0x1890, 0x2a0b, 0x3b82, 0x4f3d, 0x5eb4, 0x6c2f, 0x7da6,=0D
+ 0x8551, 0x94d8, 0xa643, 0xb7ca, 0xc375, 0xd2fc, 0xe067, 0xf1ee,=0D
+ 0x1232, 0x03bb, 0x3120, 0x20a9, 0x5416, 0x459f, 0x7704, 0x668d,=0D
+ 0x9e7a, 0x8ff3, 0xbd68, 0xace1, 0xd85e, 0xc9d7, 0xfb4c, 0xeac5,=0D
+ 0x1b2b, 0x0aa2, 0x3839, 0x29b0, 0x5d0f, 0x4c86, 0x7e1d, 0x6f94,=0D
+ 0x9763, 0x86ea, 0xb471, 0xa5f8, 0xd147, 0xc0ce, 0xf255, 0xe3dc,=0D
+ 0x2464, 0x35ed, 0x0776, 0x16ff, 0x6240, 0x73c9, 0x4152, 0x50db,=0D
+ 0xa82c, 0xb9a5, 0x8b3e, 0x9ab7, 0xee08, 0xff81, 0xcd1a, 0xdc93,=0D
+ 0x2d7d, 0x3cf4, 0x0e6f, 0x1fe6, 0x6b59, 0x7ad0, 0x484b, 0x59c2,=0D
+ 0xa135, 0xb0bc, 0x8227, 0x93ae, 0xe711, 0xf698, 0xc403, 0xd58a,=0D
+ 0x3656, 0x27df, 0x1544, 0x04cd, 0x7072, 0x61fb, 0x5360, 0x42e9,=0D
+ 0xba1e, 0xab97, 0x990c, 0x8885, 0xfc3a, 0xedb3, 0xdf28, 0xcea1,=0D
+ 0x3f4f, 0x2ec6, 0x1c5d, 0x0dd4, 0x796b, 0x68e2, 0x5a79, 0x4bf0,=0D
+ 0xb307, 0xa28e, 0x9015, 0x819c, 0xf523, 0xe4aa, 0xd631, 0xc7b8,=0D
+ 0x48c8, 0x5941, 0x6bda, 0x7a53, 0x0eec, 0x1f65, 0x2dfe, 0x3c77,=0D
+ 0xc480, 0xd509, 0xe792, 0xf61b, 0x82a4, 0x932d, 0xa1b6, 0xb03f,=0D
+ 0x41d1, 0x5058, 0x62c3, 0x734a, 0x07f5, 0x167c, 0x24e7, 0x356e,=0D
+ 0xcd99, 0xdc10, 0xee8b, 0xff02, 0x8bbd, 0x9a34, 0xa8af, 0xb926,=0D
+ 0x5afa, 0x4b73, 0x79e8, 0x6861, 0x1cde, 0x0d57, 0x3fcc, 0x2e45,=0D
+ 0xd6b2, 0xc73b, 0xf5a0, 0xe429, 0x9096, 0x811f, 0xb384, 0xa20d,=0D
+ 0x53e3, 0x426a, 0x70f1, 0x6178, 0x15c7, 0x044e, 0x36d5, 0x275c,=0D
+ 0xdfab, 0xce22, 0xfcb9, 0xed30, 0x998f, 0x8806, 0xba9d, 0xab14,=0D
+ 0x6cac, 0x7d25, 0x4fbe, 0x5e37, 0x2a88, 0x3b01, 0x099a, 0x1813,=0D
+ 0xe0e4, 0xf16d, 0xc3f6, 0xd27f, 0xa6c0, 0xb749, 0x85d2, 0x945b,=0D
+ 0x65b5, 0x743c, 0x46a7, 0x572e, 0x2391, 0x3218, 0x0083, 0x110a,=0D
+ 0xe9fd, 0xf874, 0xcaef, 0xdb66, 0xafd9, 0xbe50, 0x8ccb, 0x9d42,=0D
+ 0x7e9e, 0x6f17, 0x5d8c, 0x4c05, 0x38ba, 0x2933, 0x1ba8, 0x0a21,=0D
+ 0xf2d6, 0xe35f, 0xd1c4, 0xc04d, 0xb4f2, 0xa57b, 0x97e0, 0x8669,=0D
+ 0x7787, 0x660e, 0x5495, 0x451c, 0x31a3, 0x202a, 0x12b1, 0x0338,=0D
+ 0xfbcf, 0xea46, 0xd8dd, 0xc954, 0xbdeb, 0xac62, 0x9ef9, 0x8f70=0D
+};=0D
+=0D
+/**=0D
+ Calculates the CRC16 checksum of the given buffer.=0D
+=0D
+ @param[in] Buffer Pointer to the buffer.=0D
+ @param[in] Length Length of the buffer, in bytes.=0D
+ @param[in] InitialValue Initial value of the CRC.=0D
+=0D
+ @return The CRC16 checksum.=0D
+*/=0D
+UINT16=0D
+CalculateCrc16 (=0D
+ IN CONST VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT16 InitialValue=0D
+ )=0D
+{=0D
+ CONST UINT8 *Buf;=0D
+ UINT16 Crc;=0D
+=0D
+ Buf =3D Buffer;=0D
+=0D
+ Crc =3D ~InitialValue;=0D
+=0D
+ while(Length-- !=3D 0) {=0D
+ Crc =3D gCrc16LookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);=0D
+ }=0D
+=0D
+ return ~Crc;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Crc32c.c b/Features/Ext4Pkg/Ext4Dxe/C=
rc32c.c
new file mode 100644
index 0000000000..3986c9b10f
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Crc32c.c
@@ -0,0 +1,84 @@
+/**=0D
+ @file CRC32c calculation routines.=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include <Uefi.h>=0D
+=0D
+STATIC CONST UINT32 gCrc32cLookupTable[256] =3D {=0D
+ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,=
=0D
+ 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,=
=0D
+ 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,=
=0D
+ 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,=
=0D
+ 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,=
=0D
+ 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,=
=0D
+ 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,=
=0D
+ 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,=
=0D
+ 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,=
=0D
+ 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,=
=0D
+ 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,=
=0D
+ 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,=
=0D
+ 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,=
=0D
+ 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,=
=0D
+ 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,=
=0D
+ 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,=
=0D
+ 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,=
=0D
+ 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,=
=0D
+ 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,=
=0D
+ 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,=
=0D
+ 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,=
=0D
+ 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,=
=0D
+ 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,=
=0D
+ 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,=
=0D
+ 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,=
=0D
+ 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,=
=0D
+ 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,=
=0D
+ 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,=
=0D
+ 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,=
=0D
+ 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,=
=0D
+ 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,=
=0D
+ 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,=
=0D
+ 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,=
=0D
+ 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,=
=0D
+ 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,=
=0D
+ 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,=
=0D
+ 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,=
=0D
+ 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,=
=0D
+ 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,=
=0D
+ 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,=
=0D
+ 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,=
=0D
+ 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,=
=0D
+ 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351=0D
+};=0D
+=0D
+/**=0D
+ Calculates the CRC32c checksum of the given buffer.=0D
+=0D
+ @param[in] Buffer Pointer to the buffer.=0D
+ @param[in] Length Length of the buffer, in bytes.=0D
+ @param[in] InitialValue Initial value of the CRC.=0D
+=0D
+ @return The CRC32c checksum.=0D
+*/=0D
+UINT32=0D
+CalculateCrc32c (=0D
+ IN CONST VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT32 InitialValue=0D
+ )=0D
+{=0D
+ CONST UINT8 *Buf;=0D
+ UINT32 Crc;=0D
+=0D
+ Buf =3D Buffer;=0D
+ Crc =3D ~InitialValue;=0D
+=0D
+ while(Length-- !=3D 0) {=0D
+ Crc =3D gCrc32cLookupTable[(Crc & 0xFF) ^ *(Buf++)] ^ (Crc >> 8);=0D
+ }=0D
+=0D
+ return ~Crc;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Directory.c b/Features/Ext4Pkg/Ext4Dx=
e/Directory.c
new file mode 100644
index 0000000000..caa97cf9f1
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Directory.c
@@ -0,0 +1,492 @@
+/**=0D
+ @file Directory related routines=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+#include <Library/BaseUcs2Utf8Lib.h>=0D
+=0D
+/**=0D
+ Retrieves the filename of the directory entry and converts it to UTF-16=
/UCS-2=0D
+=0D
+ @param[in] Entry Pointer to a EXT4_DIR_ENTRY.=0D
+ @param[out] Ucs2FileName Pointer to an array of CHAR16's, of siz=
e EXT4_NAME_MAX + 1.=0D
+=0D
+ @retval EFI_SUCCESS The filename was succesfully retrieved and conver=
ted to UCS2.=0D
+ @retval !EFI_SUCCESS Failure.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4GetUcs2DirentName (=0D
+ IN EXT4_DIR_ENTRY *Entry,=0D
+ OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]=0D
+ )=0D
+{=0D
+ CHAR8 Utf8NameBuf[EXT4_NAME_MAX + 1];=0D
+ UINT16 *Str;=0D
+ EFI_STATUS Status;=0D
+=0D
+ CopyMem (Utf8NameBuf, Entry->name, Entry->name_len);=0D
+=0D
+ Utf8NameBuf[Entry->name_len] =3D '\0';=0D
+=0D
+ // Unfortunately, BaseUcs2Utf8Lib doesn't have a convert-buffer-to-buffe=
r-like=0D
+ // function. Therefore, we need to allocate from the pool (inside UTF8St=
rToUCS2),=0D
+ // copy it to our out buffer (Ucs2FileName) and free.=0D
+=0D
+ Status =3D UTF8StrToUCS2 (Utf8NameBuf, &Str);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ Status =3D StrCpyS (Ucs2FileName, EXT4_NAME_MAX + 1, Str);=0D
+=0D
+ FreePool (Str);=0D
+=0D
+ return Status;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves a directory entry.=0D
+=0D
+ @param[in] Directory Pointer to the opened directory.=0D
+ @param[in] NameUnicode Pointer to the UCS-2 formatted filename.=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[out] Result Pointer to the destination directory entry.=
=0D
+=0D
+ @return The result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4RetrieveDirent (=0D
+ IN EXT4_FILE *File,=0D
+ IN CONST CHAR16 *Name,=0D
+ IN EXT4_PARTITION *Partition,=0D
+ OUT EXT4_DIR_ENTRY *res=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+ CHAR8 *Buf;=0D
+ UINT64 Off;=0D
+ EXT4_INODE *Inode;=0D
+ UINT64 DirInoSize;=0D
+ UINT32 BlockRemainder;=0D
+=0D
+ Status =3D EFI_NOT_FOUND;=0D
+ Buf =3D AllocatePool (Partition->BlockSize);=0D
+=0D
+ if(Buf =3D=3D NULL) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ Off =3D 0;=0D
+=0D
+ Inode =3D File->Inode;=0D
+ DirInoSize =3D EXT4_INODE_SIZE (Inode);=0D
+=0D
+ DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);=
=0D
+ if(BlockRemainder !=3D 0) {=0D
+ // Directory inodes need to have block aligned sizes=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ while(Off < DirInoSize) {=0D
+ UINTN Length;=0D
+=0D
+ Length =3D Partition->BlockSize;=0D
+=0D
+ Status =3D Ext4Read (Partition, File, Buf, Off, &Length);=0D
+=0D
+ if (Status !=3D EFI_SUCCESS) {=0D
+ FreePool (Buf);=0D
+ return Status;=0D
+ }=0D
+=0D
+ for(CHAR8 *b =3D Buf; b < Buf + Partition->BlockSize; ) {=0D
+ EXT4_DIR_ENTRY *Entry;=0D
+ UINTN RemainingBlock;=0D
+=0D
+ Entry =3D (EXT4_DIR_ENTRY *)b;=0D
+ ASSERT (Entry->rec_len !=3D 0);=0D
+=0D
+ RemainingBlock =3D Partition->BlockSize - (b - Buf);=0D
+=0D
+ if(Entry->name_len > RemainingBlock || Entry->rec_len > RemainingBlo=
ck) {=0D
+ // Corrupted filesystem=0D
+ // TODO: Do the proper ext4 corruption detection thing and dirty t=
he filesystem.=0D
+ FreePool (Buf);=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ // Ignore names bigger than our limit.=0D
+=0D
+ /* Note: I think having a limit is sane because:=0D
+ 1) It's nicer to work with.=0D
+ 2) Linux and a number of BSDs also have a filename limit of 255.=0D
+ */=0D
+ if(Entry->name_len > EXT4_NAME_MAX) {=0D
+ continue;=0D
+ }=0D
+=0D
+ // Unused entry=0D
+ if(Entry->inode =3D=3D 0) {=0D
+ b +=3D Entry->rec_len;=0D
+ continue;=0D
+ }=0D
+=0D
+ CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1];=0D
+=0D
+ Status =3D Ext4GetUcs2DirentName (Entry, Ucs2FileName);=0D
+=0D
+ /* In theory, this should never fail.=0D
+ * In reality, it's quite possible that it can fail, considering fil=
enames in=0D
+ * Linux (and probably other nixes) are just null-terminated bags of=
bytes, and don't=0D
+ * need to form valid ASCII/UTF-8 sequences.=0D
+ */=0D
+ if (EFI_ERROR (Status)) {=0D
+ // If we error out, skip this entry=0D
+ // I'm not sure if this is correct behaviour, but I don't think th=
ere's a precedent here.=0D
+ b +=3D Entry->rec_len;=0D
+ continue;=0D
+ }=0D
+=0D
+ if (Entry->name_len =3D=3D StrLen (Name) &&=0D
+ !Ext4StrCmpInsensitive (Ucs2FileName, (CHAR16 *)Name)) {=0D
+ UINTN ToCopy;=0D
+=0D
+ ToCopy =3D Entry->rec_len > sizeof (EXT4_DIR_ENTRY) ?=0D
+ sizeof (EXT4_DIR_ENTRY) :=0D
+ Entry->rec_len;=0D
+=0D
+ CopyMem (res, Entry, ToCopy);=0D
+ FreePool (Buf);=0D
+ return EFI_SUCCESS;=0D
+ }=0D
+=0D
+ b +=3D Entry->rec_len;=0D
+ }=0D
+=0D
+ Off +=3D Partition->BlockSize;=0D
+ }=0D
+=0D
+ FreePool (Buf);=0D
+ return EFI_NOT_FOUND;=0D
+}=0D
+=0D
+/**=0D
+ Opens a file using a directory entry.=0D
+=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[in] OpenMode Mode in which the file is supposed to be op=
en.=0D
+ @param[out] OutFile Pointer to the newly opened file.=0D
+ @param[in] Entry Directory entry to be used.=0D
+=0D
+ @retval EFI_STATUS Result of the operation=0D
+*/=0D
+EFI_STATUS=0D
+Ext4OpenDirent (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINT64 OpenMode,=0D
+ OUT EXT4_FILE **OutFile,=0D
+ IN EXT4_DIR_ENTRY *Entry=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+ CHAR16 FileName[EXT4_NAME_MAX + 1];=0D
+ EXT4_FILE *File;=0D
+=0D
+ File =3D AllocateZeroPool (sizeof (EXT4_FILE));=0D
+=0D
+ if (File =3D=3D NULL) {=0D
+ Status =3D EFI_OUT_OF_RESOURCES;=0D
+ goto Error;=0D
+ }=0D
+=0D
+ Status =3D Ext4GetUcs2DirentName (Entry, FileName);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ goto Error;=0D
+ }=0D
+=0D
+ File->FileName =3D AllocateZeroPool (StrSize (FileName));=0D
+=0D
+ if (!File->FileName) {=0D
+ Status =3D EFI_OUT_OF_RESOURCES;=0D
+ goto Error;=0D
+ }=0D
+=0D
+ Status =3D Ext4InitExtentsMap (File);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ goto Error;=0D
+ }=0D
+=0D
+ // This should not fail.=0D
+ StrCpyS (File->FileName, EXT4_NAME_MAX + 1, FileName);=0D
+=0D
+ File->InodeNum =3D Entry->inode;=0D
+=0D
+ Ext4SetupFile (File, (EXT4_PARTITION *)Partition);=0D
+=0D
+ Status =3D Ext4ReadInode (Partition, Entry->inode, &File->Inode);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ goto Error;=0D
+ }=0D
+=0D
+ *OutFile =3D File;=0D
+=0D
+ InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);=0D
+=0D
+ return EFI_SUCCESS;=0D
+=0D
+Error:=0D
+ if (File !=3D NULL) {=0D
+ if (File->FileName !=3D NULL) {=0D
+ FreePool (File->FileName);=0D
+ }=0D
+=0D
+ if (File->ExtentsMap !=3D NULL) {=0D
+ OrderedCollectionUninit (File->ExtentsMap);=0D
+ }=0D
+=0D
+ FreePool (File);=0D
+ }=0D
+=0D
+ return Status;=0D
+}=0D
+=0D
+/**=0D
+ Opens a file.=0D
+=0D
+ @param[in] Directory Pointer to the opened directory.=0D
+ @param[in] Name Pointer to the UCS-2 formatted filename.=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[in] OpenMode Mode in which the file is supposed to be op=
en.=0D
+ @param[out] OutFile Pointer to the newly opened file.=0D
+=0D
+ @return Result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4OpenFile (=0D
+ IN EXT4_FILE *Directory,=0D
+ IN CONST CHAR16 *Name,=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINT64 OpenMode,=0D
+ OUT EXT4_FILE **OutFile=0D
+ )=0D
+{=0D
+ EXT4_DIR_ENTRY Entry;=0D
+ EFI_STATUS Status;=0D
+=0D
+ Status =3D Ext4RetrieveDirent (Directory, Name, Partition, &Entry);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ // EFI requires us to error out on ".." opens for the root directory=0D
+ if (Entry.inode =3D=3D Directory->InodeNum) {=0D
+ return EFI_NOT_FOUND;=0D
+ }=0D
+=0D
+ return Ext4OpenDirent (Partition, OpenMode, OutFile, &Entry);=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4OpenVolume (=0D
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition, EFI_FILE_PROTOCOL **Root=0D
+ )=0D
+{=0D
+ EXT4_INODE *RootInode;=0D
+ EFI_STATUS Status;=0D
+=0D
+ Status =3D Ext4ReadInode ((EXT4_PARTITION *)Partition, 2, &RootInode);=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] Could not open root inode - status %x\n",=
Status));=0D
+ return Status;=0D
+ }=0D
+=0D
+ EXT4_FILE *RootDir =3D AllocateZeroPool (sizeof (EXT4_FILE));=0D
+=0D
+ if(!RootDir) {=0D
+ FreePool (RootInode);=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ // The filename will be "\"(null terminated of course)=0D
+ RootDir->FileName =3D AllocateZeroPool (2 * sizeof (CHAR16));=0D
+=0D
+ if (!RootDir->FileName) {=0D
+ FreePool (RootDir);=0D
+ FreePool (RootInode);=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ RootDir->FileName[0] =3D L'\\';=0D
+=0D
+ RootDir->Inode =3D RootInode;=0D
+ RootDir->InodeNum =3D 2;=0D
+=0D
+ if (EFI_ERROR (Ext4InitExtentsMap (RootDir))) {=0D
+ FreePool (RootDir->FileName);=0D
+ FreePool (RootInode);=0D
+ FreePool (RootDir);=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ Ext4SetupFile (RootDir, (EXT4_PARTITION *)Partition);=0D
+ *Root =3D &RootDir->Protocol;=0D
+=0D
+ InsertTailList (&((EXT4_PARTITION *)Partition)->OpenFiles, &RootDir->Ope=
nFilesListNode);=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Validates a directory entry.=0D
+=0D
+ @param[in] Dirent Pointer to the directory entry.=0D
+=0D
+ @retval TRUE Valid directory entry.=0D
+ FALSE Invalid directory entry.=0D
+*/=0D
+STATIC=0D
+BOOLEAN=0D
+Ext4ValidDirent (=0D
+ IN CONST EXT4_DIR_ENTRY *Dirent=0D
+ )=0D
+{=0D
+ UINTN RequiredSize =3D Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;=0D
+=0D
+ if (Dirent->rec_len < RequiredSize) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] dirent size %lu too small (compared to %l=
u)\n", Dirent->rec_len, RequiredSize));=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ // Dirent sizes need to be 4 byte aligned=0D
+ if (Dirent->rec_len % 4) {=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ return TRUE;=0D
+}=0D
+=0D
+/**=0D
+ Reads a directory entry.=0D
+=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[in] File Pointer to the open directory.=0D
+ @param[out] Buffer Pointer to the output buffer.=0D
+ @param[in] Offset Initial directory position.=0D
+ @param[in out] OutLength Pointer to a UINTN that contains the length=
of the buffer,=0D
+ and the length of the actual EFI_FILE_INFO =
after the call.=0D
+=0D
+ @return Result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4ReadDir (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_FILE *File,=0D
+ OUT VOID *Buffer,=0D
+ IN UINT64 Offset,=0D
+ IN OUT UINTN *OutLength=0D
+ )=0D
+{=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Ext4ReadDir offset %lu\n", Offset));=0D
+ EXT4_INODE *DirIno;=0D
+ EFI_STATUS Status;=0D
+ UINT64 DirInoSize;=0D
+ UINTN Len;=0D
+ UINT32 BlockRemainder;=0D
+ EXT4_DIR_ENTRY Entry;=0D
+=0D
+ DirIno =3D File->Inode;=0D
+ Status =3D EFI_SUCCESS;=0D
+ DirInoSize =3D Ext4InodeSize (DirIno);=0D
+=0D
+ DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);=
=0D
+ if(BlockRemainder !=3D 0) {=0D
+ // Directory inodes need to have block aligned sizes=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ while(TRUE) {=0D
+ EXT4_FILE *TempFile;=0D
+=0D
+ TempFile =3D NULL;=0D
+=0D
+ // We (try to) read the maximum size of a directory entry at a time=0D
+ // Note that we don't need to read any padding that may exist after it=
.=0D
+ Len =3D sizeof (Entry);=0D
+ Status =3D Ext4Read (Partition, File, &Entry, Offset, &Len);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ goto Out;=0D
+ }=0D
+=0D
+ #if 0=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Length read %lu, offset %lu\n", Len, Off=
set));=0D
+ #endif=0D
+=0D
+ if (Len =3D=3D 0) {=0D
+ *OutLength =3D 0;=0D
+ Status =3D EFI_SUCCESS;=0D
+ goto Out;=0D
+ }=0D
+=0D
+ if (Len < EXT4_MIN_DIR_ENTRY_LEN) {=0D
+ Status =3D EFI_VOLUME_CORRUPTED;=0D
+ goto Out;=0D
+ }=0D
+=0D
+ // Invalid directory entry length=0D
+ if (!Ext4ValidDirent (&Entry)) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset=
));=0D
+ Status =3D EFI_VOLUME_CORRUPTED;=0D
+ goto Out;=0D
+ }=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] dirent size %lu\n", Entry.rec_len));=0D
+=0D
+ if (Entry.inode =3D=3D 0) {=0D
+ // When inode =3D 0, it's unused=0D
+ Offset +=3D Entry.rec_len;=0D
+ continue;=0D
+ }=0D
+=0D
+ Status =3D Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &=
Entry);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ goto Out;=0D
+ }=0D
+=0D
+ // TODO: Is this needed?=0D
+ if (!StrCmp (TempFile->FileName, L".") || !StrCmp (TempFile->FileName,=
L"..")) {=0D
+ Offset +=3D Entry.rec_len;=0D
+ Ext4CloseInternal (TempFile);=0D
+ continue;=0D
+ }=0D
+=0D
+ #if 0=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Listing file %s\n", TempFile->FileName))=
;=0D
+ #endif=0D
+=0D
+ Status =3D Ext4GetFileInfo (TempFile, Buffer, OutLength);=0D
+ if (!EFI_ERROR (Status)) {=0D
+ File->Position =3D Offset + Entry.rec_len;=0D
+ }=0D
+=0D
+ Ext4CloseInternal (TempFile);=0D
+=0D
+ break;=0D
+ }=0D
+=0D
+ Status =3D EFI_SUCCESS;=0D
+Out:=0D
+ return Status;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Features/Ext4Pkg/Ext4Dxe=
/DiskUtil.c
new file mode 100644
index 0000000000..1cafdd64cd
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
@@ -0,0 +1,83 @@
+/**=0D
+ @file Disk utilities=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+#include "Ext4Dxe.h"=0D
+=0D
+/**=0D
+ Reads from the partition's disk using the DISK_IO protocol.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[out] Buffer Pointer to a destination buffer.=0D
+ @param[in] Length Length of the destination buffer.=0D
+ @param[in] Offset Offset, in bytes, of the location to read.=0D
+=0D
+ @return Success status of the disk read.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4ReadDiskIo (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ OUT VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT64 Offset=0D
+ )=0D
+{=0D
+ return Ext4DiskIo (Partition)->ReadDisk (Ext4DiskIo (Partition), Ext4Med=
iaId (Partition), Offset, Length, Buffer);=0D
+}=0D
+=0D
+/**=0D
+ Reads blocks from the partition's disk using the DISK_IO protocol.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[out] Buffer Pointer to a destination buffer.=0D
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D
+ @param[in] BlockNumber Starting block number.=0D
+=0D
+ @return Success status of the read.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4ReadBlocks (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ OUT VOID *Buffer,=0D
+ IN UINTN NumberBlocks,=0D
+ IN EXT4_BLOCK_NR BlockNumber=0D
+ )=0D
+{=0D
+ return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->Bloc=
kSize, BlockNumber * Partition->BlockSize);=0D
+}=0D
+=0D
+/**=0D
+ Allocates a buffer and reads blocks from the partition's disk using the=
DISK_IO protocol.=0D
+ This function is deprecated and will be removed in the future.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D
+ @param[in] BlockNumber Starting block number.=0D
+=0D
+ @return Buffer allocated by AllocatePool, or NULL if some part of the p=
rocess=0D
+ failed.=0D
+ */=0D
+VOID *=0D
+Ext4AllocAndReadBlocks (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINTN NumberBlocks,=0D
+ IN EXT4_BLOCK_NR BlockNumber=0D
+ )=0D
+{=0D
+ VOID *Buf;=0D
+=0D
+ Buf =3D AllocatePool (NumberBlocks * Partition->BlockSize);=0D
+=0D
+ if(Buf =3D=3D NULL) {=0D
+ return NULL;=0D
+ }=0D
+=0D
+ if(Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) !=3D EFI_S=
UCCESS) {=0D
+ FreePool (Buf);=0D
+ return NULL;=0D
+ }=0D
+=0D
+ return Buf;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe=
/Ext4Disk.h
new file mode 100644
index 0000000000..d790e70be1
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
@@ -0,0 +1,450 @@
+/**=0D
+ @file Raw filesystem data structures=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ =0D
+ Layout of an EXT2/3/4 filesystem:=0D
+ (note: this driver has been developed using=0D
+ https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as=0D
+ documentation).=0D
+ =0D
+ An ext2/3/4 filesystem (here on out referred to as simply an ext4 filesy=
stem,=0D
+ due to the similarities) is composed of various concepts:=0D
+ =0D
+ 1) Superblock=0D
+ The superblock is the structure near (1024 bytes offset from the star=
t)=0D
+ the start of the partition, and describes the filesystem in general.=
=0D
+ Here, we get to know the size of the filesystem's blocks, which featu=
res=0D
+ it supports or not, whether it's been cleanly unmounted, how many blo=
cks=0D
+ we have, etc.=0D
+ =0D
+ 2) Block groups=0D
+ EXT4 filesystems are divided into block groups, and each block group =
covers=0D
+ s_blocks_per_group(8 * Block Size) blocks. Each block group has an=0D
+ associated block group descriptor; these are present directly after t=
he=0D
+ superblock. Each block group descriptor contains the location of the=
=0D
+ inode table, and the inode and block bitmaps (note these bitmaps are =
only=0D
+ a block long, which gets us the 8 * Block Size formula covered previo=
usly).=0D
+ =0D
+ 3) Blocks=0D
+ The ext4 filesystem is divided in blocks, of size s_log_block_size ^ =
1024.=0D
+ Blocks can be allocated using individual block groups's bitmaps. Note=
=0D
+ that block 0 is invalid and its presence on extents/block tables mean=
s=0D
+ it's part of a file hole, and that particular location must be read a=
s=0D
+ a block full of zeros.=0D
+ =0D
+ 4) Inodes=0D
+ The ext4 filesystem divides files/directories into inodes (originally=
=0D
+ index nodes). Each file/socket/symlink/directory/etc (here on out ref=
erred=0D
+ to as a file, since there is no distinction under the ext4 filesystem=
) is=0D
+ stored as a /nameless/ inode, that is stored in some block group's in=
ode=0D
+ table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if it=
's=0D
+ an old filesystem), and holds various metadata about the file. Since =
the=0D
+ largest inode structure right now is ~160 bytes, the rest of the inod=
e=0D
+ contains inline extended attributes. Inodes' data is stored using eit=
her=0D
+ data blocks (under ext2/3) or extents (under ext4).=0D
+ =0D
+ 5) Extents=0D
+ Ext4 inodes store data in extents. These let N contiguous logical blo=
cks=0D
+ that are represented by N contiguous physical blocks be represented b=
y a=0D
+ single extent structure, which minimizes filesystem metadata bloat an=
d=0D
+ speeds up block mapping (particularly due to the fact that high-quali=
ty=0D
+ ext4 implementations like linux's try /really/ hard to make the file=
=0D
+ contiguous, so it's common to have files with almost 0 fragmentation)=
.=0D
+ Inodes that use extents store them in a tree, and the top of the tree=
=0D
+ is stored on i_data. The tree's leaves always start with an=0D
+ EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth !=3D 0 a=
nd=0D
+ EXT4_EXTENT on eh_depth =3D 0; these entries are always sorted by log=
ical=0D
+ block.=0D
+ =0D
+ 6) Directories=0D
+ Ext4 directories are files that store name -> inode mappings for the=
=0D
+ logical directory; this is where files get their names, which means e=
xt4=0D
+ inodes do not themselves have names, since they can be linked (presen=
t)=0D
+ multiple times with different names. Directories can store entries in=
two=0D
+ different ways:=0D
+ 1) Classical linear directories: They store entries as a mostly-lin=
ked=0D
+ mostly-list of EXT4_DIR_ENTRY.=0D
+ 2) Hash tree directories: These are used for larger directories, wi=
th=0D
+ hundreds of entries, and are designed in a backwards compatible =
way.=0D
+ These are not yet implemented in the Ext4Dxe driver.=0D
+ =0D
+ 7) Journal=0D
+ Ext3/4 filesystems have a journal to help protect the filesystem agai=
nst=0D
+ system crashes. This is not yet implemented in Ext4Dxe but is describ=
ed=0D
+ in detail in the Linux kernel's documentation.=0D
+ */=0D
+=0D
+#ifndef _EXT4_DISK_H=0D
+#define _EXT4_DISK_H=0D
+=0D
+#include <Uefi.h>=0D
+=0D
+#define EXT4_SUPERBLOCK_OFFSET 1024U=0D
+=0D
+#define EXT4_SIGNATURE 0xEF53U=0D
+=0D
+#define EXT4_FS_STATE_UNMOUNTED 0x1=0D
+#define EXT4_FS_STATE_ERRORS_DETECTED 0x2=0D
+#define EXT4_FS_STATE_RECOVERING_ORPHANS 0x4=0D
+=0D
+#define EXT4_ERRORS_CONTINUE 1=0D
+#define EXT4_ERRORS_RO 2=0D
+#define EXT4_ERRORS_PANIC 3=0D
+=0D
+#define EXT4_LINUX_ID 0=0D
+#define EXT4_GNU_HURD_ID 1=0D
+#define EXT4_MASIX_ID 2=0D
+#define EXT4_FREEBSD_ID 3=0D
+#define EXT4_LITES_ID 4=0D
+=0D
+#define EXT4_GOOD_OLD_REV 0=0D
+#define EXT4_DYNAMIC_REV 1=0D
+=0D
+#define EXT4_CHECKSUM_CRC32C 0x1=0D
+=0D
+#define EXT4_FEATURE_COMPAT_DIR_PREALLOC 0x01=0D
+#define EXT4_FEATURE_COMPAT_IMAGIC_INODES 0x02=0D
+#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x04=0D
+#define EXT4_FEATURE_COMPAT_EXT_ATTR 0x08=0D
+#define EXT4_FEATURE_COMPAT_RESIZE_INO 0x10=0D
+#define EXT4_FEATURE_COMPAT_DIR_INDEX 0x20=0D
+=0D
+#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x00001=0D
+#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x00002=0D
+#define EXT4_FEATURE_INCOMPAT_RECOVER 0x00004=0D
+#define EXT4_FEATURE_INCOMPAT_JOURNAL_DEV 0x00008=0D
+#define EXT4_FEATURE_INCOMPAT_META_BG 0x00010=0D
+#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x00040=0D
+#define EXT4_FEATURE_INCOMPAT_64BIT 0x00080=0D
+#define EXT4_FEATURE_INCOMPAT_MMP 0x00100=0D
+#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x00200=0D
+#define EXT4_FEATURE_INCOMPAT_EA_INODE 0x00400=0D
+// It's not clear whether or not this feature (below) is used right now=0D
+#define EXT4_FEATURE_INCOMPAT_DIRDATA 0x01000=0D
+#define EXT4_FEATURE_INCOMPAT_CSUM_SEED 0x02000=0D
+#define EXT4_FEATURE_INCOMPAT_LARGEDIR 0x04000=0D
+#define EXT4_FEATURE_INCOMPAT_INLINE_DATA 0x08000=0D
+#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000=0D
+=0D
+#define EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001=0D
+#define EXT4_FEATURE_RO_COMPAT_LARGE_FILE 0x0002=0D
+#define EXT4_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 // Unused=0D
+#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE 0x0008=0D
+#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010=0D
+#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020=0D
+#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040=0D
+#define EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT 0x0080 // Not implemente=
d in ext4=0D
+#define EXT4_FEATURE_RO_COMPAT_QUOTA 0x0100=0D
+#define EXT4_FEATURE_RO_COMPAT_BIGALLOC 0x0200=0D
+#define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400=0D
+#define EXT4_FEATURE_RO_COMPAT_REPLICA 0x0800 // Not used=0D
+=0D
+// We explicitly don't recognise this, so we get read only.=0D
+#define EXT4_FEATURE_RO_COMPAT_READONLY 0x1000=0D
+#define EXT4_FEATURE_RO_COMPAT_PROJECT 0x2000=0D
+=0D
+/* Important notes about the features=0D
+ * Absolutely needed features:=0D
+ * 1) Every incompat, because we might want to mount root filesystems=0D
+ * 2) Relevant RO_COMPATs(I'm not sure of what to do wrt quota, project=
)=0D
+ */=0D
+=0D
+#define EXT4_INO_TYPE_FIFO 0x1000=0D
+#define EXT4_INO_TYPE_CHARDEV 0x2000=0D
+#define EXT4_INO_TYPE_DIR 0x4000=0D
+#define EXT4_INO_TYPE_BLOCKDEV 0x6000=0D
+#define EXT4_INO_TYPE_REGFILE 0x8000=0D
+#define EXT4_INO_TYPE_SYMLINK 0xA000=0D
+#define EXT4_INO_TYPE_UNIX_SOCK 0xC000=0D
+=0D
+/* Inode flags */=0D
+#define EXT4_SECRM_FL 0x00000001=0D
+#define EXT4_UNRM_FL 0x00000002=0D
+#define EXT4_COMPR_FL 0x00000004=0D
+#define EXT4_SYNC_FL 0x00000008=0D
+#define EXT4_IMMUTABLE_FL 0x00000010=0D
+#define EXT4_APPEND_FL 0x00000020=0D
+#define EXT4_NODUMP_FL 0x00000040=0D
+#define EXT4_NOATIME_FL 0x00000080=0D
+#define EXT4_DIRTY_FL 0x00000100=0D
+#define EXT4_COMPRBLK_FL 0x00000200=0D
+#define EXT4_NOCOMPR_FL 0x00000400=0D
+#define EXT4_ECOMPR_FL 0x00000800=0D
+#define EXT4_BTREE_FL 0x00001000=0D
+#define EXT4_INDEX_FL 0x00002000=0D
+#define EXT4_JOURNAL_DATA_FL 0x00004000=0D
+#define EXT4_NOTAIL_FL 0x00008000=0D
+#define EXT4_DIRSYNC_FL 0x00010000=0D
+#define EXT4_TOPDIR_FL 0x00020000=0D
+#define EXT4_HUGE_FILE_FL 0x00040000=0D
+#define EXT4_EXTENTS_FL 0x00080000=0D
+#define EXT4_VERITY_FL 0x00100000=0D
+#define EXT4_EA_INODE_FL 0x00200000=0D
+#define EXT4_RESERVED_FL 0x80000000=0D
+=0D
+/* File type flags that are stored in the directory entries */=0D
+#define EXT4_FT_UNKNOWN 0=0D
+#define EXT4_FT_REG_FILE 1=0D
+#define EXT4_FT_DIR 2=0D
+#define EXT4_FT_CHRDEV 3=0D
+#define EXT4_FT_BLKDEV 4=0D
+#define EXT4_FT_FIFO 5=0D
+#define EXT4_FT_SOCK 6=0D
+#define EXT4_FT_SYMLINK 7=0D
+=0D
+typedef struct {=0D
+ UINT32 s_inodes_count;=0D
+ UINT32 s_blocks_count;=0D
+ UINT32 s_r_blocks_count;=0D
+ UINT32 s_free_blocks_count;=0D
+ UINT32 s_free_inodes_count;=0D
+ UINT32 s_first_data_block;=0D
+ UINT32 s_log_block_size;=0D
+ UINT32 s_log_frag_size;=0D
+ UINT32 s_blocks_per_group;=0D
+ UINT32 s_frags_per_group;=0D
+ UINT32 s_inodes_per_group;=0D
+ UINT32 s_mtime;=0D
+ UINT32 s_wtime;=0D
+ UINT16 s_mnt_count;=0D
+ UINT16 s_max_mnt_count;=0D
+ UINT16 s_magic;=0D
+ UINT16 s_state;=0D
+ UINT16 s_errors;=0D
+ UINT16 s_minor_rev_level;=0D
+ UINT32 s_lastcheck;=0D
+ UINT32 s_check_interval;=0D
+ UINT32 s_creator_os;=0D
+ UINT32 s_rev_level;=0D
+ UINT16 s_def_resuid;=0D
+ UINT16 s_def_resgid;=0D
+=0D
+ /* Every field after this comment is revision >=3D 1 */=0D
+=0D
+ UINT32 s_first_ino;=0D
+ UINT16 s_inode_size;=0D
+ UINT16 s_block_group_nr;=0D
+ UINT32 s_feature_compat;=0D
+ UINT32 s_feature_incompat;=0D
+ UINT32 s_feature_ro_compat;=0D
+ UINT8 s_uuid[16];=0D
+ UINT8 s_volume_name[16];=0D
+ UINT8 s_last_mounted[64];=0D
+ UINT32 s_algo_bitmap;=0D
+ UINT8 s_prealloc_blocks;=0D
+ UINT8 s_prealloc_dir_blocks;=0D
+ UINT16 unused;=0D
+ UINT8 s_journal_uuid[16];=0D
+ UINT32 s_journal_inum;=0D
+ UINT32 s_journal_dev;=0D
+ UINT32 s_last_orphan;=0D
+ UINT32 s_hash_seed[4];=0D
+ UINT8 s_def_hash_version;=0D
+ UINT8 s_jnl_backup_type;=0D
+ UINT16 s_desc_size;=0D
+ UINT32 s_default_mount_options;=0D
+ UINT32 s_first_meta_bg;=0D
+ UINT32 s_mkfs_time;=0D
+ UINT32 s_jnl_blocks[17];=0D
+ UINT32 s_blocks_count_hi;=0D
+ UINT32 s_r_blocks_count_hi;=0D
+ UINT32 s_free_blocks_count_hi;=0D
+ UINT16 s_min_extra_isize;=0D
+ UINT16 s_want_extra_isize;=0D
+ UINT32 s_flags;=0D
+ UINT16 s_raid_stride;=0D
+ UINT16 s_mmp_interval;=0D
+ UINT64 s_mmp_block;=0D
+ UINT32 s_raid_stride_width;=0D
+ UINT8 s_log_groups_per_flex;=0D
+ UINT8 s_checksum_type; // Only valid value is 1 - CRC32C=0D
+ UINT16 s_reserved_pad;=0D
+ UINT64 s_kbytes_written;=0D
+=0D
+ // Snapshot stuff isn't used in Linux and isn't implemented here=0D
+ UINT32 s_snapshot_inum;=0D
+ UINT32 s_snapshot_id;=0D
+ UINT64 s_snapshot_r_blocks_count;=0D
+ UINT32 s_snapshot_list;=0D
+ UINT32 s_error_count;=0D
+ UINT32 s_first_error_time;=0D
+ UINT32 s_first_error_ino;=0D
+ UINT64 s_first_error_block;=0D
+ UINT8 s_first_error_func[32];=0D
+ UINT32 s_first_error_line;=0D
+ UINT32 s_last_error_time;=0D
+ UINT32 s_last_error_ino;=0D
+ UINT32 s_last_error_line;=0D
+ UINT64 s_last_error_block;=0D
+ UINT8 s_last_error_func[32];=0D
+ UINT8 s_mount_opts[64];=0D
+ UINT32 s_usr_quota_inum;=0D
+ UINT32 s_grp_quota_inum;=0D
+ UINT32 s_overhead_blocks;=0D
+ UINT32 s_backup_bgs[2]; // sparse_super2=0D
+ UINT8 s_encrypt_algos[4];=0D
+ UINT8 s_encrypt_pw_salt[16];=0D
+ UINT32 s_lpf_ino;=0D
+ UINT32 s_prj_quota_inum;=0D
+ UINT32 s_checksum_seed;=0D
+ UINT32 s_reserved[98];=0D
+ UINT32 s_checksum;=0D
+} EXT4_SUPERBLOCK;=0D
+=0D
+STATIC_ASSERT (sizeof (EXT4_SUPERBLOCK) =3D=3D 1024, "ext4 superblock stru=
ct has incorrect size");=0D
+=0D
+typedef struct {=0D
+ UINT32 bg_block_bitmap_lo;=0D
+ UINT32 bg_inode_bitmap_lo;=0D
+ UINT32 bg_inode_table_lo;=0D
+ UINT16 bg_free_blocks_count_lo;=0D
+ UINT16 bg_free_inodes_count_lo;=0D
+ UINT16 bg_used_dirs_count_lo;=0D
+ UINT16 bg_flags;=0D
+ UINT32 bg_exclude_bitmap_lo;=0D
+ UINT16 bg_block_bitmap_csum_lo;=0D
+ UINT16 bg_inode_bitmap_csum_lo;=0D
+ UINT16 bg_itable_unused_lo;=0D
+ UINT16 bg_checksum;=0D
+ UINT32 bg_block_bitmap_hi;=0D
+ UINT32 bg_inode_bitmap_hi;=0D
+ UINT32 bg_inode_table_hi;=0D
+ UINT16 bg_free_blocks_count_hi;=0D
+ UINT16 bg_free_inodes_count_hi;=0D
+ UINT16 bg_used_dirs_count_hi;=0D
+ UINT16 bg_itable_unused_hi;=0D
+ UINT32 bg_exclude_bitmap_hi;=0D
+ UINT16 bg_block_bitmap_csum_hi;=0D
+ UINT16 bg_inode_bitmap_csum_hi;=0D
+ UINT32 bg_reserved;=0D
+} EXT4_BLOCK_GROUP_DESC;=0D
+=0D
+#define EXT4_OLD_BLOCK_DESC_SIZE 32=0D
+#define EXT4_64BIT_BLOCK_DESC_SIZE 64=0D
+=0D
+STATIC_ASSERT (=0D
+ sizeof (EXT4_BLOCK_GROUP_DESC) =3D=3D EXT4_64BIT_BLOCK_DESC_SIZE,=0D
+ "ext4 block group descriptor struct has incorrect size"=0D
+ );=0D
+=0D
+#define EXT4_DBLOCKS 12=0D
+#define EXT4_IND_BLOCK 12=0D
+#define EXT4_DIND_BLOCK 13=0D
+#define EXT4_TIND_BLOCK 14=0D
+#define EXT4_NR_BLOCKS 15=0D
+=0D
+#define EXT4_GOOD_OLD_INODE_SIZE 128=0D
+=0D
+typedef struct _Ext4Inode {=0D
+ UINT16 i_mode;=0D
+ UINT16 i_uid;=0D
+ UINT32 i_size_lo;=0D
+ UINT32 i_atime;=0D
+ UINT32 i_ctime;=0D
+ UINT32 i_mtime;=0D
+ UINT32 i_dtime;=0D
+ UINT16 i_gid;=0D
+ UINT16 i_links;=0D
+ UINT32 i_blocks;=0D
+ UINT32 i_flags;=0D
+ UINT32 i_os_spec;=0D
+ UINT32 i_data[EXT4_NR_BLOCKS];=0D
+ UINT32 i_generation;=0D
+ UINT32 i_file_acl;=0D
+ UINT32 i_size_hi;=0D
+ UINT32 i_faddr;=0D
+ union {=0D
+ // Note: Toolchain-specific defines (such as "linux") stops us from us=
ing simpler names down here.=0D
+ struct _Ext4_I_OSD2_Linux {=0D
+ UINT16 l_i_blocks_high;=0D
+ UINT16 l_i_file_acl_high;=0D
+ UINT16 l_i_uid_high;=0D
+ UINT16 l_i_gid_high;=0D
+ UINT16 l_i_checksum_lo;=0D
+ UINT16 l_i_reserved;=0D
+ } data_linux;=0D
+=0D
+ struct _Ext4_I_OSD2_Hurd {=0D
+ UINT16 h_i_reserved1;=0D
+ UINT16 h_i_mode_high;=0D
+ UINT16 h_i_uid_high;=0D
+ UINT16 h_i_gid_high;=0D
+ UINT32 h_i_author;=0D
+ } data_hurd;=0D
+ } i_osd2;=0D
+=0D
+ UINT16 i_extra_isize;=0D
+ UINT16 i_checksum_hi;=0D
+ UINT32 i_ctime_extra;=0D
+ UINT32 i_mtime_extra;=0D
+ UINT32 i_atime_extra;=0D
+ UINT32 i_crtime;=0D
+ UINT32 i_crtime_extra;=0D
+ UINT32 i_version_hi;=0D
+ UINT32 i_projid;=0D
+} EXT4_INODE;=0D
+=0D
+typedef struct {=0D
+ UINT32 inode;=0D
+ UINT16 rec_len;=0D
+ UINT8 name_len;=0D
+ UINT8 file_type;=0D
+ CHAR8 name[255];=0D
+} EXT4_DIR_ENTRY;=0D
+=0D
+#define EXT4_MIN_DIR_ENTRY_LEN 8=0D
+=0D
+// This on-disk structure is present at the bottom of the extent tree=0D
+typedef struct {=0D
+ // First logical block=0D
+ UINT32 ee_block;=0D
+ // Length of the extent, in blocks=0D
+ UINT16 ee_len;=0D
+ // The physical (filesystem-relative) block is split between the high 16=
bits=0D
+ // and the low 32 bits - this forms a 48-bit block number=0D
+ UINT16 ee_start_hi;=0D
+ UINT32 ee_start_lo;=0D
+} EXT4_EXTENT;=0D
+=0D
+// This on-disk structure is present at all levels except the bottom=0D
+typedef struct {=0D
+ // This index covers logical blocks from 'ei_block'=0D
+ UINT32 ei_block;=0D
+ // Block of the next level of the extent tree, similarly split in a high=
and low portion.=0D
+ UINT32 ei_leaf_lo;=0D
+ UINT16 ei_leaf_hi;=0D
+=0D
+ UINT16 ei_unused;=0D
+} EXT4_EXTENT_INDEX;=0D
+=0D
+typedef struct {=0D
+ // Needs to be EXT4_EXTENT_HEADER_MAGIC=0D
+ UINT16 eh_magic;=0D
+ // Number of entries=0D
+ UINT16 eh_entries;=0D
+ // Maximum number of entries that could follow this header=0D
+ UINT16 eh_max;=0D
+ // Depth of this node in the tree - the tree can be at most 5 levels dee=
p=0D
+ UINT16 eh_depth;=0D
+ // Unused by standard ext4=0D
+ UINT32 eh_generation;=0D
+} EXT4_EXTENT_HEADER;=0D
+=0D
+#define EXT4_EXTENT_HEADER_MAGIC 0xF30A=0D
+=0D
+// Specified by ext4 docs and backed by a bunch of math=0D
+#define EXT4_EXTENT_TREE_MAX_DEPTH 5=0D
+=0D
+typedef struct {=0D
+ // CRC32C of UUID + inode number + igeneration + extent block=0D
+ UINT32 eb_checksum;=0D
+} EXT4_EXTENT_TAIL;=0D
+=0D
+typedef UINT64 EXT4_BLOCK_NR;=0D
+typedef UINT32 EXT4_INO_NR;=0D
+=0D
+#define EXT4_INODE_SIZE(ino) (((UINT64)ino->i_size_hi << 32) | ino->i_siz=
e_lo)=0D
+=0D
+#endif=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c b/Features/Ext4Pkg/Ext4Dxe/=
Ext4Dxe.c
new file mode 100644
index 0000000000..d1e289f8fa
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
@@ -0,0 +1,454 @@
+/**=0D
+ @file Driver entry point=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4DriverNameTab=
le[] =3D {=0D
+ {=0D
+ "eng;en",=0D
+ L"Ext4 File System Driver"=0D
+ },=0D
+ {=0D
+ NULL,=0D
+ NULL=0D
+ }=0D
+};=0D
+=0D
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_UNICODE_STRING_TABLE mExt4ControllerNam=
eTable[] =3D {=0D
+ {=0D
+ "eng;en",=0D
+ L"Ext4 File System"=0D
+ },=0D
+ {=0D
+ NULL,=0D
+ NULL=0D
+ }=0D
+};=0D
+=0D
+// Needed by gExt4ComponentName*=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4ComponentNameGetDriverName (=0D
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D
+ IN CHAR8 *Language,=0D
+ OUT CHAR16 **DriverName=0D
+ );=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4ComponentNameGetControllerName (=0D
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_HANDLE ChildHandle O=
PTIONAL,=0D
+ IN CHAR8 *Language,=0D
+ OUT CHAR16 **ControllerName=0D
+ );=0D
+=0D
+extern EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentName;=0D
+=0D
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME_PROTOCOL gExt4ComponentN=
ame =3D {=0D
+ Ext4ComponentNameGetDriverName,=0D
+ Ext4ComponentNameGetControllerName,=0D
+ "eng"=0D
+};=0D
+=0D
+//=0D
+// EFI Component Name 2 Protocol=0D
+//=0D
+GLOBAL_REMOVE_IF_UNREFERENCED EFI_COMPONENT_NAME2_PROTOCOL gExt4Component=
Name2 =3D {=0D
+ (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)Ext4ComponentNameGetDriverName,=0D
+ (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)Ext4ComponentNameGetControllerN=
ame,=0D
+ "en"=0D
+};=0D
+=0D
+// Needed by gExt4BindingProtocol=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4IsBindingSupported (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D
+ );=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Bind (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D
+ );=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Stop (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN UINTN NumberOfChildren,=0D
+ IN EFI_HANDLE *ChildHandleBuffer OPTIONAL=0D
+ );=0D
+=0D
+EFI_DRIVER_BINDING_PROTOCOL gExt4BindingProtocol =3D=0D
+{=0D
+ Ext4IsBindingSupported,=0D
+ Ext4Bind,=0D
+ Ext4Stop,=0D
+ EXT4_DRIVER_VERSION=0D
+};=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4ComponentNameGetControllerName (=0D
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_HANDLE ChildHandle O=
PTIONAL,=0D
+ IN CHAR8 *Language,=0D
+ OUT CHAR16 **ControllerName=0D
+ )=0D
+{=0D
+ // TODO: Do we need to test whether we're managing the handle, like FAT =
does?=0D
+ return LookupUnicodeString2 (=0D
+ Language,=0D
+ This->SupportedLanguages,=0D
+ mExt4ControllerNameTable,=0D
+ ControllerName,=0D
+ (BOOLEAN)(This =3D=3D &gExt4ComponentName)=0D
+ );=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4ComponentNameGetDriverName (=0D
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,=0D
+ IN CHAR8 *Language,=0D
+ OUT CHAR16 **DriverName=0D
+ )=0D
+{=0D
+ return LookupUnicodeString2 (=0D
+ Language,=0D
+ This->SupportedLanguages,=0D
+ mExt4DriverNameTable,=0D
+ DriverName,=0D
+ (BOOLEAN)(This =3D=3D &gExt4ComponentName)=0D
+ );=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4IsBindingSupported (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D
+ );=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Bind (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D
+ );=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Stop (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN UINTN NumberOfChildren,=0D
+ IN EFI_HANDLE *ChildHandleBuffer OPTIONAL=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Sfs;=0D
+ EXT4_PARTITION *Partition;=0D
+ BOOLEAN HasDiskIo2;=0D
+=0D
+ Status =3D gBS->OpenProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiSimpleFileSystemProtocolGuid,=0D
+ (VOID **)&Sfs,=0D
+ This->DriverBindingHandle,=0D
+ ControllerHandle,=0D
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ Partition =3D (EXT4_PARTITION *)Sfs;=0D
+=0D
+ HasDiskIo2 =3D Ext4DiskIo2 (Partition) !=3D NULL;=0D
+=0D
+ Status =3D Ext4UnmountAndFreePartition (Partition);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ Status =3D gBS->UninstallMultipleProtocolInterfaces (=0D
+ ControllerHandle,=0D
+ &gEfiSimpleFileSystemProtocolGuid,=0D
+ &Partition->Interface,=0D
+ NULL=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ // Close all open protocols (DiskIo, DiskIo2, BlockIo)=0D
+=0D
+ Status =3D gBS->CloseProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIoProtocolGuid,=0D
+ This->DriverBindingHandle,=0D
+ ControllerHandle=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ Status =3D gBS->CloseProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiBlockIoProtocolGuid,=0D
+ This->DriverBindingHandle,=0D
+ ControllerHandle=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ if(HasDiskIo2) {=0D
+ Status =3D gBS->CloseProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIo2ProtocolGuid,=0D
+ This->DriverBindingHandle,=0D
+ ControllerHandle=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+ }=0D
+=0D
+ return Status;=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4EntryPoint (=0D
+ IN EFI_HANDLE ImageHandle,=0D
+ IN EFI_SYSTEM_TABLE *SystemTable=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+=0D
+ Status =3D EfiLibInstallAllDriverProtocols2 (=0D
+ ImageHandle,=0D
+ SystemTable,=0D
+ &gExt4BindingProtocol,=0D
+ ImageHandle,=0D
+ &gExt4ComponentName,=0D
+ &gExt4ComponentName2,=0D
+ NULL,=0D
+ NULL,=0D
+ NULL,=0D
+ NULL=0D
+ );=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ return Ext4InitialiseUnicodeCollation (ImageHandle);=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4Unload (=0D
+ IN EFI_HANDLE ImageHandle=0D
+ )=0D
+{=0D
+ EFI_STATUS Status;=0D
+ EFI_HANDLE *DeviceHandleBuffer;=0D
+ UINTN DeviceHandleCount;=0D
+ UINTN Index;=0D
+=0D
+ Status =3D gBS->LocateHandleBuffer (=0D
+ AllHandles,=0D
+ NULL,=0D
+ NULL,=0D
+ &DeviceHandleCount,=0D
+ &DeviceHandleBuffer=0D
+ );=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ for(Index =3D 0; Index < DeviceHandleCount; Index++) {=0D
+ EFI_HANDLE Handle;=0D
+=0D
+ Handle =3D DeviceHandleBuffer[Index];=0D
+=0D
+ Status =3D EfiTestManagedDevice (Handle, ImageHandle, &gEfiDiskIoProto=
colGuid);=0D
+=0D
+ if(Status =3D=3D EFI_SUCCESS) {=0D
+ Status =3D gBS->DisconnectController (Handle, ImageHandle, NULL);=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ break;=0D
+ }=0D
+ }=0D
+ }=0D
+=0D
+ FreePool (DeviceHandleBuffer);=0D
+=0D
+ Status =3D EfiLibUninstallAllDriverProtocols2 (=0D
+ &gExt4BindingProtocol,=0D
+ &gExt4ComponentName,=0D
+ &gExt4ComponentName2,=0D
+ NULL,=0D
+ NULL,=0D
+ NULL,=0D
+ NULL=0D
+ );=0D
+=0D
+ return Status;=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4IsBindingSupported (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D
+ )=0D
+{=0D
+ // Note to self: EFI_OPEN_PROTOCOL_TEST_PROTOCOL lets us not close the=0D
+ // protocol and ignore the output argument entirely=0D
+=0D
+ EFI_STATUS Status;=0D
+=0D
+ Status =3D gBS->OpenProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIoProtocolGuid,=0D
+ NULL,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle,=0D
+ EFI_OPEN_PROTOCOL_TEST_PROTOCOL=0D
+ );=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ Status =3D gBS->OpenProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiBlockIoProtocolGuid,=0D
+ NULL,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle,=0D
+ EFI_OPEN_PROTOCOL_TEST_PROTOCOL=0D
+ );=0D
+ return Status;=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Bind (=0D
+ IN EFI_DRIVER_BINDING_PROTOCOL *BindingProtocol,=0D
+ IN EFI_HANDLE ControllerHandle,=0D
+ IN EFI_DEVICE_PATH *RemainingDevicePath OPTIONAL=0D
+ )=0D
+{=0D
+ EFI_DISK_IO_PROTOCOL *DiskIo;=0D
+ EFI_DISK_IO2_PROTOCOL *DiskIo2;=0D
+ EFI_BLOCK_IO_PROTOCOL *blockIo;=0D
+ EFI_STATUS Status;=0D
+=0D
+ DiskIo2 =3D NULL;=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[Ext4] Binding to controller\n"));=0D
+=0D
+ Status =3D gBS->OpenProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIoProtocolGuid,=0D
+ (VOID **)&DiskIo,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle,=0D
+ EFI_OPEN_PROTOCOL_BY_DRIVER=0D
+ );=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO\n"));=0D
+=0D
+ Status =3D gBS->OpenProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIo2ProtocolGuid,=0D
+ (VOID **)&DiskIo2,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle,=0D
+ EFI_OPEN_PROTOCOL_BY_DRIVER=0D
+ );=0D
+ // It's okay to not support DISK_IO2=0D
+=0D
+ if(DiskIo2 !=3D NULL) {=0D
+ DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO2\n"));=0D
+ }=0D
+=0D
+ Status =3D gBS->OpenProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiBlockIoProtocolGuid,=0D
+ (VOID **)&blockIo,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle,=0D
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL=0D
+ );=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ goto Error;=0D
+ }=0D
+=0D
+ DEBUG ((EFI_D_INFO, "Opening partition\n"));=0D
+=0D
+ Status =3D Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, blockIo=
);=0D
+=0D
+ if(!EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Error mounting %x\n", Status));=0D
+=0D
+Error:=0D
+ if(DiskIo) {=0D
+ gBS->CloseProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIoProtocolGuid,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle=0D
+ );=0D
+ }=0D
+=0D
+ if(DiskIo2) {=0D
+ gBS->CloseProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiDiskIo2ProtocolGuid,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle=0D
+ );=0D
+ }=0D
+=0D
+ if(blockIo) {=0D
+ gBS->CloseProtocol (=0D
+ ControllerHandle,=0D
+ &gEfiBlockIoProtocolGuid,=0D
+ BindingProtocol->ImageHandle,=0D
+ ControllerHandle=0D
+ );=0D
+ }=0D
+=0D
+ return Status;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/=
Ext4Dxe.h
new file mode 100644
index 0000000000..f6875c919e
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
@@ -0,0 +1,942 @@
+/**=0D
+ @file Common header for the driver=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#ifndef _EXT4_H=0D
+#define _EXT4_H=0D
+=0D
+#include <Uefi.h>=0D
+=0D
+#include <Guid/FileInfo.h>=0D
+#include <Guid/FileSystemInfo.h>=0D
+#include <Guid/FileSystemVolumeLabelInfo.h>=0D
+#include <Protocol/BlockIo.h>=0D
+#include <Protocol/DiskIo.h>=0D
+#include <Protocol/DiskIo2.h>=0D
+#include <Protocol/SimpleFileSystem.h>=0D
+=0D
+#include <Library/PcdLib.h>=0D
+#include <Library/DebugLib.h>=0D
+#include <Library/UefiLib.h>=0D
+#include <Library/BaseLib.h>=0D
+#include <Library/BaseMemoryLib.h>=0D
+#include <Library/MemoryAllocationLib.h>=0D
+#include <Library/UefiDriverEntryPoint.h>=0D
+#include <Library/UefiBootServicesTableLib.h>=0D
+#include <Library/UefiRuntimeServicesTableLib.h>=0D
+#include <Library/OrderedCollectionLib.h>=0D
+=0D
+#include "Ext4Disk.h"=0D
+=0D
+#define EXT4_NAME_MAX 255=0D
+=0D
+#define EXT4_DRIVER_VERSION 0x0000=0D
+=0D
+/**=0D
+ Opens an ext4 partition and installs the Simple File System protocol.=0D
+=0D
+ @param[in] DeviceHandle Handle to the block device.=0D
+ @param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.=
=0D
+ @param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL,=
if supported.=0D
+ @param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.=
=0D
+=0D
+ @retval EFI_SUCCESS The opening was successful.=0D
+ !EFI_SUCCESS Opening failed.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4OpenPartition (=0D
+ IN EFI_HANDLE DeviceHandle,=0D
+ IN EFI_DISK_IO_PROTOCOL *DiskIo,=0D
+ IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,=0D
+ IN EFI_BLOCK_IO_PROTOCOL *BlockIo=0D
+ );=0D
+=0D
+typedef struct _Ext4File EXT4_FILE;=0D
+=0D
+typedef struct _Ext4_PARTITION {=0D
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL Interface;=0D
+ EFI_DISK_IO_PROTOCOL *DiskIo;=0D
+ EFI_DISK_IO2_PROTOCOL *DiskIo2;=0D
+ EFI_BLOCK_IO_PROTOCOL *BlockIo;=0D
+=0D
+ EXT4_SUPERBLOCK SuperBlock;=0D
+ BOOLEAN Unmounting;=0D
+=0D
+ UINT32 FeaturesIncompat;=0D
+ UINT32 FeaturesCompat;=0D
+ UINT32 FeaturesRoCompat;=0D
+ UINT32 InodeSize;=0D
+ UINT32 BlockSize;=0D
+ BOOLEAN ReadOnly;=0D
+ UINT64 NumberBlockGroups;=0D
+ EXT4_BLOCK_NR NumberBlocks;=0D
+=0D
+ EXT4_BLOCK_GROUP_DESC *BlockGroups;=0D
+ UINT32 DescSize;=0D
+ EXT4_FILE *Root;=0D
+=0D
+ UINT32 InitialSeed;=0D
+=0D
+ LIST_ENTRY OpenFiles;=0D
+} EXT4_PARTITION;=0D
+=0D
+/**=0D
+ Opens and parses the superblock.=0D
+=0D
+ @param[out] Partition Partition structure to fill with filesystem d=
etails.=0D
+ @retval EFI_SUCCESS Parsing was succesful and the partition is a=
=0D
+ valid ext4 partition.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4OpenSuperblock (=0D
+ OUT EXT4_PARTITION *Partition=0D
+ );=0D
+=0D
+/**=0D
+ Retrieves the EFI_BLOCK_IO_PROTOCOL of the partition.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @return The Block IO protocol associated with the partition.=0D
+ */=0D
+STATIC inline=0D
+EFI_BLOCK_IO_PROTOCOL *=0D
+Ext4BlockIo (=0D
+ EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Partition->BlockIo;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves the EFI_DISK_IO_PROTOCOL of the partition.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @return The Disk IO protocol associated with the partition.=0D
+ */=0D
+STATIC inline=0D
+EFI_DISK_IO_PROTOCOL *=0D
+Ext4DiskIo (=0D
+ EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Partition->DiskIo;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves the EFI_DISK_IO2_PROTOCOL of the partition.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @return The Disk IO 2 protocol associated with the partition, or NULL i=
f=0D
+ not supported.=0D
+ */=0D
+STATIC inline=0D
+EFI_DISK_IO2_PROTOCOL *=0D
+Ext4DiskIo2 (=0D
+ EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Partition->DiskIo2;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves the media ID of the partition.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @return The media ID associated with the partition.=0D
+ */=0D
+STATIC inline=0D
+UINT32=0D
+Ext4MediaId (=0D
+ EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Partition->BlockIo->Media->MediaId;=0D
+}=0D
+=0D
+/**=0D
+ Reads from the partition's disk using the DISK_IO protocol.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[out] Buffer Pointer to a destination buffer.=0D
+ @param[in] Length Length of the destination buffer.=0D
+ @param[in] Offset Offset, in bytes, of the location to read.=0D
+=0D
+ @return Success status of the disk read.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4ReadDiskIo (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ OUT VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT64 Offset=0D
+ );=0D
+=0D
+/**=0D
+ Reads blocks from the partition's disk using the DISK_IO protocol.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[out] Buffer Pointer to a destination buffer.=0D
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D
+ @param[in] BlockNumber Starting block number.=0D
+=0D
+ @return Success status of the read.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4ReadBlocks (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ OUT VOID *Buffer,=0D
+ IN UINTN NumberBlocks,=0D
+ IN EXT4_BLOCK_NR BlockNumber=0D
+ );=0D
+=0D
+/**=0D
+ Allocates a buffer and reads blocks from the partition's disk using the=
DISK_IO protocol.=0D
+ This function is deprecated and will be removed in the future.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[in] NumberBlocks Length of the read, in filesystem blocks.=0D
+ @param[in] BlockNumber Starting block number.=0D
+=0D
+ @return Buffer allocated by AllocatePool, or NULL if some part of the p=
rocess=0D
+ failed.=0D
+ */=0D
+VOID *=0D
+Ext4AllocAndReadBlocks (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINTN NumberBlocks,=0D
+ IN EXT4_BLOCK_NR BlockNumber=0D
+ );=0D
+=0D
+/**=0D
+ Checks if the opened partition has the 64-bit feature (see EXT4_FEATURE=
_INCOMPAT_64BIT).=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+=0D
+ @return TRUE if EXT4_FEATURE_INCOMPAT_64BIT is enabled, else FALSE.=0D
+ */=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4Is64Bit (=0D
+ IN CONST EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_64BIT;=0D
+}=0D
+=0D
+/**=0D
+ Composes an EXT4_BLOCK_NR safely, from two halfs.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[in] Low Low half of the block number.=0D
+ @param[in] High High half of the block number.=0D
+=0D
+ @return The block number formed by Low, and if 64 bit is enabled, High.=
=0D
+ */=0D
+STATIC inline=0D
+EXT4_BLOCK_NR=0D
+Ext4MakeBlockNumberFromHalfs (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN UINT32 Low,=0D
+ IN UINT32 High=0D
+ )=0D
+{=0D
+ // High might have garbage if it's not a 64 bit filesystem=0D
+ return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves a block group descriptor of the ext4 filesystem.=0D
+=0D
+ @param[in] Partition Pointer to the opened ext4 partition.=0D
+ @param[in] BlockGroup Block group number.=0D
+=0D
+ @return A pointer to the block group descriptor.=0D
+ */=0D
+STATIC inline=0D
+EXT4_BLOCK_GROUP_DESC *=0D
+Ext4GetBlockGroupDesc (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINT32 BlockGroup=0D
+ )=0D
+{=0D
+ // Maybe assert that the block group nr isn't a nonsense number?=0D
+ return (EXT4_BLOCK_GROUP_DESC *)((CHAR8 *)Partition->BlockGroups + Block=
Group * Partition->DescSize);=0D
+}=0D
+=0D
+/**=0D
+ Reads an inode from disk.=0D
+=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+ @param[in] InodeNum Number of the desired Inode=0D
+ @param[out] OutIno Pointer to where it will be stored a pointer t=
o the read inode.=0D
+=0D
+ @return Status of the inode read.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4ReadInode (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_INO_NR InodeNum,=0D
+ OUT EXT4_INODE **OutIno=0D
+ );=0D
+=0D
+/**=0D
+ Converts blocks to bytes.=0D
+=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+ @param[in] Block Block number/number of blocks.=0D
+=0D
+ @return The number of bytes.=0D
+ */=0D
+STATIC inline=0D
+UINT64=0D
+Ext4BlockToByteOffset (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN EXT4_BLOCK_NR Block=0D
+ )=0D
+{=0D
+ return Partition->BlockSize * Block;=0D
+}=0D
+=0D
+/**=0D
+ Reads from an EXT4 inode.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Buffer Pointer to the buffer.=0D
+ @param[in] Offset Offset of the read.=0D
+ @param[in out] Length Pointer to the length of the buffer, in b=
ytes.=0D
+ After a succesful read, it's updated to t=
he number of read bytes.=0D
+=0D
+ @return Status of the read operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4Read (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_FILE *File,=0D
+ OUT VOID *Buffer,=0D
+ IN UINT64 Offset,=0D
+ IN OUT UINTN *Length=0D
+ );=0D
+=0D
+/**=0D
+ Retrieves the size of the inode.=0D
+=0D
+ @param[in] Inode Pointer to the ext4 inode.=0D
+=0D
+ @return The size of the inode, in bytes.=0D
+ */=0D
+STATIC inline=0D
+UINT64=0D
+Ext4InodeSize (=0D
+ CONST EXT4_INODE *Inode=0D
+ )=0D
+{=0D
+ return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves an extent from an EXT4 inode.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[in] LogicalBlock Block number which the returned extent mu=
st cover.=0D
+ @param[out] Extent Pointer to the output buffer, where the e=
xtent will be copied to.=0D
+=0D
+ @retval EFI_SUCCESS Retrieval was succesful.=0D
+ @retval EFI_NO_MAPPING Block has no mapping.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4GetExtent (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_FILE *File,=0D
+ IN EXT4_BLOCK_NR LogicalBlock,=0D
+ OUT EXT4_EXTENT *Extent=0D
+ );=0D
+=0D
+struct _Ext4File {=0D
+ EFI_FILE_PROTOCOL Protocol;=0D
+ EXT4_INODE *Inode;=0D
+ EXT4_INO_NR InodeNum;=0D
+=0D
+ UINT64 OpenMode;=0D
+ UINT64 Position;=0D
+=0D
+ EXT4_PARTITION *Partition;=0D
+ CHAR16 *FileName;=0D
+=0D
+ ORDERED_COLLECTION *ExtentsMap;=0D
+=0D
+ LIST_ENTRY OpenFilesListNode;=0D
+};=0D
+=0D
+#define Ext4FileFromOpenFileNode(Node) BASE_CR (Node, EXT4_FILE, OpenFile=
sListNode)=0D
+=0D
+/**=0D
+ Retrieves a directory entry.=0D
+=0D
+ @param[in] Directory Pointer to the opened directory.=0D
+ @param[in] NameUnicode Pointer to the UCS-2 formatted filename.=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[out] Result Pointer to the destination directory entry.=
=0D
+=0D
+ @return The result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4RetrieveDirent (=0D
+ IN EXT4_FILE *Directory,=0D
+ IN CONST CHAR16 *NameUnicode,=0D
+ IN EXT4_PARTITION *Partition,=0D
+ OUT EXT4_DIR_ENTRY *Result=0D
+ );=0D
+=0D
+/**=0D
+ Opens a file.=0D
+=0D
+ @param[in] Directory Pointer to the opened directory.=0D
+ @param[in] Name Pointer to the UCS-2 formatted filename.=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[in] OpenMode Mode in which the file is supposed to be op=
en.=0D
+ @param[out] OutFile Pointer to the newly opened file.=0D
+=0D
+ @return Result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4OpenFile (=0D
+ IN EXT4_FILE *Directory,=0D
+ IN CONST CHAR16 *Name,=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINT64 OpenMode,=0D
+ OUT EXT4_FILE **OutFile=0D
+ );=0D
+=0D
+/**=0D
+ Opens a file using a directory entry.=0D
+=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[in] OpenMode Mode in which the file is supposed to be op=
en.=0D
+ @param[out] OutFile Pointer to the newly opened file.=0D
+ @param[in] Entry Directory entry to be used.=0D
+=0D
+ @retval EFI_STATUS Result of the operation=0D
+*/=0D
+EFI_STATUS=0D
+Ext4OpenDirent (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN UINT64 OpenMode,=0D
+ OUT EXT4_FILE **OutFile,=0D
+ IN EXT4_DIR_ENTRY *Entry=0D
+ );=0D
+=0D
+/**=0D
+ Allocates a zeroed inode structure.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+=0D
+ @return Pointer to the allocated structure, from the pool,=0D
+ with size Partition->InodeSize.=0D
+*/=0D
+EXT4_INODE *=0D
+Ext4AllocateInode (=0D
+ IN EXT4_PARTITION *Partition=0D
+ );=0D
+=0D
+// Part of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4OpenVolume (=0D
+ IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Partition,=0D
+ IN EFI_FILE_PROTOCOL **Root=0D
+ );=0D
+=0D
+// End of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL=0D
+=0D
+/**=0D
+ Sets up the protocol and metadata of a file that is being opened.=0D
+=0D
+ @param[in out] File Pointer to the file.=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+ */=0D
+VOID=0D
+Ext4SetupFile (=0D
+ IN OUT EXT4_FILE *File,=0D
+ IN EXT4_PARTITION *Partition=0D
+ );=0D
+=0D
+/**=0D
+ Closes a file.=0D
+=0D
+ @param[in] File Pointer to the file.=0D
+=0D
+ @return Status of the closing of the file.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4CloseInternal (=0D
+ IN EXT4_FILE *File=0D
+ );=0D
+=0D
+// Part of the EFI_FILE_PROTOCOL=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Open (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ OUT EFI_FILE_PROTOCOL **NewHandle,=0D
+ IN CHAR16 *FileName,=0D
+ IN UINT64 OpenMode,=0D
+ IN UINT64 Attributes=0D
+ );=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Close (=0D
+ IN EFI_FILE_PROTOCOL *This=0D
+ );=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Delete (=0D
+ IN EFI_FILE_PROTOCOL *This=0D
+ );=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4ReadFile (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN OUT UINTN *BufferSize,=0D
+ OUT VOID *Buffer=0D
+ );=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4WriteFile (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN OUT UINTN *BufferSize,=0D
+ IN VOID *Buffer=0D
+ );=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4GetPosition (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ OUT UINT64 *Position=0D
+ );=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4SetPosition (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN UINT64 Position=0D
+ );=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4GetInfo (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN EFI_GUID *InformationType,=0D
+ IN OUT UINTN *BufferSize,=0D
+ OUT VOID *Buffer=0D
+ );=0D
+=0D
+// EFI_FILE_PROTOCOL implementation ends here.=0D
+=0D
+/**=0D
+ Checks if a file is a directory.=0D
+ @param[in] File Pointer to the opened file.=0D
+=0D
+ @return TRUE if file is a directory.=0D
+*/=0D
+BOOLEAN=0D
+Ext4FileIsDir (=0D
+ IN CONST EXT4_FILE *File=0D
+ );=0D
+=0D
+/**=0D
+ Checks if a file is a regular file.=0D
+ @param[in] File Pointer to the opened file.=0D
+=0D
+ @return BOOLEAN TRUE if file is a regular file.=0D
+*/=0D
+BOOLEAN=0D
+Ext4FileIsReg (=0D
+ IN CONST EXT4_FILE *File=0D
+ );=0D
+=0D
+// In EFI we can't open FIFO pipes, UNIX sockets, character/block devices =
since these concepts are=0D
+// at the kernel level and are OS dependent.=0D
+=0D
+/**=0D
+ Checks if a file is openable.=0D
+ @param[in] File Pointer to the file trying to be opened.=0D
+=0D
+=0D
+ @return TRUE if file is openable. A file is considered openable if=0D
+ it's a regular file or a directory, since most other file types=
=0D
+ don't make sense under UEFI.=0D
+*/=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4FileIsOpenable (=0D
+ IN CONST EXT4_FILE *File=0D
+ )=0D
+{=0D
+ return Ext4FileIsReg (File) || Ext4FileIsDir (File);=0D
+}=0D
+=0D
+#define Ext4InodeHasField(Inode, \=0D
+ Field) (Inode->i_extra_isize + EXT4_GOOD_OLD_IN=
ODE_SIZE >=3D OFFSET_OF (EXT4_INODE, Field) + \=0D
+ sizeof (((EXT4_INODE *)NULL)->Field))=0D
+=0D
+/**=0D
+ Calculates the physical space used by a file.=0D
+ @param[in] File Pointer to the opened file.=0D
+=0D
+ @return Physical space used by a file, in bytes.=0D
+*/=0D
+UINT64=0D
+Ext4FilePhysicalSpace (=0D
+ EXT4_FILE *File=0D
+ );=0D
+=0D
+/**=0D
+ Gets the file's last access time.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+*/=0D
+VOID=0D
+Ext4FileATime (=0D
+ IN EXT4_FILE *File,=0D
+ OUT EFI_TIME *Time=0D
+ );=0D
+=0D
+/**=0D
+ Gets the file's last (data) modification time.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+*/=0D
+VOID=0D
+Ext4FileMTime (=0D
+ IN EXT4_FILE *File,=0D
+ OUT EFI_TIME *Time=0D
+ );=0D
+=0D
+/**=0D
+ Gets the file's creation time, if possible.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+ In the case where the the creation time isn't re=
corded,=0D
+ Time is zeroed.=0D
+*/=0D
+VOID=0D
+Ext4FileCreateTime (=0D
+ IN EXT4_FILE *File, OUT EFI_TIME *Time=0D
+ );=0D
+=0D
+/**=0D
+ Initialises Unicode collation, which is needed for case-insensitive str=
ing comparisons=0D
+ within the driver (a good example of an application of this is filename=
comparison).=0D
+=0D
+ @param[in] DriverHandle Handle to the driver image.=0D
+=0D
+ @retval EFI_SUCCESS Unicode collation was successfully initialised.=0D
+ @retval !EFI_SUCCESS Failure.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4InitialiseUnicodeCollation (=0D
+ EFI_HANDLE DriverHandle=0D
+ );=0D
+=0D
+/**=0D
+ Does a case-insensitive string comparison. Refer to EFI_UNICODE_COLLATI=
ON_PROTOCOL's StriColl=0D
+ for more details.=0D
+=0D
+ @param[in] Str1 Pointer to a null terminated string.=0D
+ @param[in] Str2 Pointer to a null terminated string.=0D
+=0D
+ @retval 0 Str1 is equivalent to Str2.=0D
+ @retval >0 Str1 is lexically greater than Str2.=0D
+ @retval <0 Str1 is lexically less than Str2.=0D
+*/=0D
+INTN=0D
+Ext4StrCmpInsensitive (=0D
+ IN CHAR16 *Str1,=0D
+ IN CHAR16 *Str2=0D
+ );=0D
+=0D
+/**=0D
+ Retrieves the filename of the directory entry and converts it to UTF-16=
/UCS-2=0D
+=0D
+ @param[in] Entry Pointer to a EXT4_DIR_ENTRY.=0D
+ @param[out] Ucs2FileName Pointer to an array of CHAR16's, of siz=
e EXT4_NAME_MAX + 1.=0D
+=0D
+ @retval EFI_SUCCESS Unicode collation was successfully initialised.=0D
+ @retval !EFI_SUCCESS Failure.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4GetUcs2DirentName (=0D
+ IN EXT4_DIR_ENTRY *Entry,=0D
+ OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]=0D
+ );=0D
+=0D
+/**=0D
+ Retrieves information about the file and stores it in the EFI_FILE_INFO=
format.=0D
+=0D
+ @param[in] File Pointer to an opened file.=0D
+ @param[out] Info Pointer to a EFI_FILE_INFO.=0D
+ @param[in out] BufferSize Pointer to the buffer size=0D
+=0D
+ @return Status of the file information request.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4GetFileInfo (=0D
+ IN EXT4_FILE *File,=0D
+ OUT EFI_FILE_INFO *Info,=0D
+ IN OUT UINTN *BufferSize=0D
+ );=0D
+=0D
+/**=0D
+ Reads a directory entry.=0D
+=0D
+ @param[in] Partition Pointer to the ext4 partition.=0D
+ @param[in] File Pointer to the open directory.=0D
+ @param[out] Buffer Pointer to the output buffer.=0D
+ @param[in] Offset Initial directory position.=0D
+ @param[in out] OutLength Pointer to a UINTN that contains the length=
of the buffer,=0D
+ and the length of the actual EFI_FILE_INFO =
after the call.=0D
+=0D
+ @return Result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4ReadDir (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_FILE *File,=0D
+ OUT VOID *Buffer,=0D
+ IN UINT64 Offset,=0D
+ IN OUT UINTN *OutLength=0D
+ );=0D
+=0D
+/**=0D
+ Initialises the (empty) extents map, that will work as a cache of exten=
ts.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+=0D
+ @return Result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4InitExtentsMap (=0D
+ IN EXT4_FILE *File=0D
+ );=0D
+=0D
+/**=0D
+ Frees the extents map, deleting every extent stored.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+*/=0D
+VOID=0D
+Ext4FreeExtentsMap (=0D
+ IN EXT4_FILE *File=0D
+ );=0D
+=0D
+/**=0D
+ Calculates the CRC32c checksum of the given buffer.=0D
+=0D
+ @param[in] Buffer Pointer to the buffer.=0D
+ @param[in] Length Length of the buffer, in bytes.=0D
+ @param[in] InitialValue Initial value of the CRC.=0D
+=0D
+ @return The CRC32c checksum.=0D
+*/=0D
+UINT32=0D
+CalculateCrc32c (=0D
+ IN CONST VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT32 InitialValue=0D
+ );=0D
+=0D
+/**=0D
+ Calculates the CRC16 checksum of the given buffer.=0D
+=0D
+ @param[in] Buffer Pointer to the buffer.=0D
+ @param[in] Length Length of the buffer, in bytes.=0D
+ @param[in] InitialValue Initial value of the CRC.=0D
+=0D
+ @return The CRC16 checksum.=0D
+*/=0D
+UINT16=0D
+CalculateCrc16 (=0D
+ IN CONST VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT16 InitialValue=0D
+ );=0D
+=0D
+/**=0D
+ Calculates the checksum of the given buffer.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] Buffer Pointer to the buffer.=0D
+ @param[in] Length Length of the buffer, in bytes.=0D
+ @param[in] InitialValue Initial value of the CRC.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT32=0D
+Ext4CalculateChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST VOID *Buffer,=0D
+ IN UINTN Length,=0D
+ IN UINT32 InitialValue=0D
+ );=0D
+=0D
+/**=0D
+ Calculates the checksum of the given inode.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] Inode Pointer to the inode.=0D
+ @param[in] InodeNum Inode number.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT32=0D
+Ext4CalculateInodeChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_INODE *Inode,=0D
+ IN EXT4_INO_NR InodeNum=0D
+ );=0D
+=0D
+/**=0D
+ Checks if the checksum of the inode is correct.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] Inode Pointer to the inode.=0D
+ @param[in] InodeNum Inode number.=0D
+=0D
+ @return TRUE if checksum is correct, FALSE if there is corruption.=0D
+*/=0D
+BOOLEAN=0D
+Ext4CheckInodeChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_INODE *Inode,=0D
+ IN EXT4_INO_NR InodeNum=0D
+ );=0D
+=0D
+/**=0D
+ Unmounts and frees an ext4 partition.=0D
+=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+=0D
+ @return Status of the unmount.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4UnmountAndFreePartition (=0D
+ IN EXT4_PARTITION *Partition=0D
+ );=0D
+=0D
+/**=0D
+ Checks if the checksum of the block group descriptor is correct.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.=
=0D
+ @param[in] BlockGroupNum Number of the block group.=0D
+=0D
+ @return TRUE if checksum is correct, FALSE if there is corruption.=0D
+*/=0D
+BOOLEAN=0D
+Ext4VerifyBlockGroupDescChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D
+ IN UINT32 BlockGroupNum=0D
+ );=0D
+=0D
+/**=0D
+ Calculates the checksum of the block group descriptor.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] BlockGroupDesc Pointer to the block group descriptor.=
=0D
+ @param[in] BlockGroupNum Number of the block group.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT16=0D
+Ext4CalculateBlockGroupDescChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_BLOCK_GROUP_DESC *BlockGroupDesc,=0D
+ IN UINT32 BlockGroupNum=0D
+ );=0D
+=0D
+/**=0D
+ Verifies the existance of a particular RO compat feature set.=0D
+ @param[in] Partition Pointer to the opened EXT4 partitio=
n.=0D
+ @param[in] RoCompatFeatureSet Feature set to test.=0D
+=0D
+ @return TRUE if all features are supported, else FALSE.=0D
+*/=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4HasRoCompat (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN UINT32 RoCompatFeatureSet=0D
+ )=0D
+{=0D
+ return (Partition->FeaturesRoCompat & RoCompatFeatureSet) =3D=3D RoCompa=
tFeatureSet;=0D
+}=0D
+=0D
+/**=0D
+ Verifies the existance of a particular compat feature set.=0D
+ @param[in] Partition Pointer to the opened EXT4 partitio=
n.=0D
+ @param[in] RoCompatFeatureSet Feature set to test.=0D
+=0D
+ @return TRUE if all features are supported, else FALSE.=0D
+*/=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4HasCompat (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN UINT32 CompatFeatureSet=0D
+ )=0D
+{=0D
+ return (Partition->FeaturesCompat & CompatFeatureSet) =3D=3D CompatFeatu=
reSet;=0D
+}=0D
+=0D
+/**=0D
+ Verifies the existance of a particular compat feature set.=0D
+ @param[in] Partition Pointer to the opened EXT4 partitio=
n.=0D
+ @param[in] RoCompatFeatureSet Feature set to test.=0D
+=0D
+ @return TRUE if all features are supported, else FALSE.=0D
+*/=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4HasIncompat (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN UINT32 IncompatFeatureSet=0D
+ )=0D
+{=0D
+ return (Partition->FeaturesIncompat & IncompatFeatureSet) =3D=3D Incompa=
tFeatureSet;=0D
+}=0D
+=0D
+// Note: Might be a good idea to provide generic Ext4Has$feature() through=
macros.=0D
+=0D
+/**=0D
+ Checks if metadata_csum is enabled on the partition.=0D
+ @param[in] Partition Pointer to the opened EXT4 partitio=
n.=0D
+ @param[in] RoCompatFeatureSet Feature set to test.=0D
+=0D
+ @return TRUE if the feature is supported, else FALSE.=0D
+*/=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4HasMetadataCsum (=0D
+ IN CONST EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)=
;=0D
+}=0D
+=0D
+/**=0D
+ Checks if gdt_csum is enabled on the partition.=0D
+ @param[in] Partition Pointer to the opened EXT4 partitio=
n.=0D
+ @param[in] RoCompatFeatureSet Feature set to test.=0D
+=0D
+ @return TRUE if the feature is supported, else FALSE.=0D
+*/=0D
+STATIC inline=0D
+BOOLEAN=0D
+Ext4HasGdtCsum (=0D
+ IN CONST EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ return Ext4HasRoCompat (Partition, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)=
;=0D
+}=0D
+=0D
+#endif=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dx=
e/Ext4Dxe.inf
new file mode 100644
index 0000000000..102b12d613
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
@@ -0,0 +1,147 @@
+## @file=0D
+# Ext4 Package=0D
+#=0D
+# UEFI Driver that produces the Simple File System Protocol for a partiti=
on that is formatted=0D
+# with the EXT4 file system.=0D
+# More details are available at: https://www.kernel.org/doc/html/v5.4/fil=
esystems/ext4/index.html=0D
+#=0D
+# Copyright (c) 2021 Pedro Falcato=0D
+# SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+#=0D
+# Layout of an EXT2/3/4 filesystem:=0D
+# (note: this driver has been developed using=0D
+# https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html as=
=0D
+# documentation).=0D
+# =0D
+# An ext2/3/4 filesystem (here on out referred to as simply an ext4 file=
system,=0D
+# due to the similarities) is composed of various concepts:=0D
+# =0D
+# 1) Superblock=0D
+# The superblock is the structure near (1024 bytes offset from the st=
art)=0D
+# the start of the partition, and describes the filesystem in general=
.=0D
+# Here, we get to know the size of the filesystem's blocks, which fea=
tures=0D
+# it supports or not, whether it's been cleanly unmounted, how many b=
locks=0D
+# we have, etc.=0D
+# =0D
+# 2) Block groups=0D
+# EXT4 filesystems are divided into block groups, and each block grou=
p covers=0D
+# s_blocks_per_group(8 * Block Size) blocks. Each block group has an=
=0D
+# associated block group descriptor; these are present directly after=
the=0D
+# superblock. Each block group descriptor contains the location of th=
e=0D
+# inode table, and the inode and block bitmaps (note these bitmaps ar=
e only=0D
+# a block long, which gets us the 8 * Block Size formula covered prev=
iously).=0D
+# =0D
+# 3) Blocks=0D
+# The ext4 filesystem is divided in blocks, of size s_log_block_size =
^ 1024.=0D
+# Blocks can be allocated using individual block groups's bitmaps. No=
te=0D
+# that block 0 is invalid and its presence on extents/block tables me=
ans=0D
+# it's part of a file hole, and that particular location must be read=
as=0D
+# a block full of zeros.=0D
+# =0D
+# 4) Inodes=0D
+# The ext4 filesystem divides files/directories into inodes (original=
ly=0D
+# index nodes). Each file/socket/symlink/directory/etc (here on out r=
eferred=0D
+# to as a file, since there is no distinction under the ext4 filesyst=
em) is=0D
+# stored as a /nameless/ inode, that is stored in some block group's =
inode=0D
+# table. Each inode has s_inode_size size (or GOOD_OLD_INODE_SIZE if =
it's=0D
+# an old filesystem), and holds various metadata about the file. Sinc=
e the=0D
+# largest inode structure right now is ~160 bytes, the rest of the in=
ode=0D
+# contains inline extended attributes. Inodes' data is stored using e=
ither=0D
+# data blocks (under ext2/3) or extents (under ext4).=0D
+# =0D
+# 5) Extents=0D
+# Ext4 inodes store data in extents. These let N contiguous logical b=
locks=0D
+# that are represented by N contiguous physical blocks be represented=
by a=0D
+# single extent structure, which minimizes filesystem metadata bloat =
and=0D
+# speeds up block mapping (particularly due to the fact that high-qua=
lity=0D
+# ext4 implementations like linux's try /really/ hard to make the fil=
e=0D
+# contiguous, so it's common to have files with almost 0 fragmentatio=
n).=0D
+# Inodes that use extents store them in a tree, and the top of the tr=
ee=0D
+# is stored on i_data. The tree's leaves always start with an=0D
+# EXT4_EXTENT_HEADER and contain EXT4_EXTENT_INDEX on eh_depth !=3D 0=
and=0D
+# EXT4_EXTENT on eh_depth =3D 0; these entries are always sorted by l=
ogical=0D
+# block.=0D
+# =0D
+# 6) Directories=0D
+# Ext4 directories are files that store name -> inode mappings for th=
e=0D
+# logical directory; this is where files get their names, which means=
ext4=0D
+# inodes do not themselves have names, since they can be linked (pres=
ent)=0D
+# multiple times with different names. Directories can store entries =
in two=0D
+# different ways:=0D
+# 1) Classical linear directories: They store entries as a mostly-l=
inked=0D
+# mostly-list of EXT4_DIR_ENTRY.=0D
+# 2) Hash tree directories: These are used for larger directories, =
with=0D
+# hundreds of entries, and are designed in a backwards compatibl=
e way.=0D
+# These are not yet implemented in the Ext4Dxe driver.=0D
+# =0D
+# 7) Journal=0D
+# Ext3/4 filesystems have a journal to help protect the filesystem ag=
ainst=0D
+# system crashes. This is not yet implemented in Ext4Dxe but is descr=
ibed=0D
+# in detail in the Linux kernel's documentation.=0D
+##=0D
+=0D
+=0D
+[Defines]=0D
+ INF_VERSION =3D 0x00010005=0D
+ BASE_NAME =3D Ext4=0D
+ MODULE_UNI_FILE =3D Ext4Dxe.uni=0D
+ FILE_GUID =3D 75F2B676-D73B-45CB-B7C1-303C7F4E6FD6=
=0D
+ MODULE_TYPE =3D UEFI_DRIVER=0D
+ VERSION_STRING =3D 1.0=0D
+=0D
+ ENTRY_POINT =3D Ext4EntryPoint=0D
+ UNLOAD_IMAGE =3D Ext4Unload=0D
+=0D
+#=0D
+# The following information is for reference only and not required by the =
build tools.=0D
+#=0D
+# VALID_ARCHITECTURES =3D IA32 X64 EBC=0D
+#=0D
+=0D
+[Sources]=0D
+ Ext4Dxe.c=0D
+ Partition.c=0D
+ DiskUtil.c=0D
+ Superblock.c=0D
+ BlockGroup.c=0D
+ Inode.c=0D
+ Directory.c=0D
+ Extents.c=0D
+ File.c=0D
+ Collation.c=0D
+ Crc32c.c=0D
+ Crc16.c=0D
+=0D
+[Packages]=0D
+ MdePkg/MdePkg.dec=0D
+ RedfishPkg/RedfishPkg.dec=0D
+=0D
+[LibraryClasses]=0D
+ UefiRuntimeServicesTableLib=0D
+ UefiBootServicesTableLib=0D
+ MemoryAllocationLib=0D
+ BaseMemoryLib=0D
+ BaseLib=0D
+ UefiLib=0D
+ UefiDriverEntryPoint=0D
+ DebugLib=0D
+ PcdLib=0D
+ OrderedCollectionLib=0D
+ BaseUcs2Utf8Lib=0D
+=0D
+[Guids]=0D
+ gEfiFileInfoGuid ## SOMETIMES_CONSUMES ## UNDEFIN=
ED=0D
+ gEfiFileSystemInfoGuid ## SOMETIMES_CONSUMES ## UNDEFIN=
ED=0D
+ gEfiFileSystemVolumeLabelInfoIdGuid ## SOMETIMES_CONSUMES ## UNDEFIN=
ED=0D
+=0D
+[Protocols]=0D
+ gEfiDiskIoProtocolGuid ## TO_START=0D
+ gEfiDiskIo2ProtocolGuid ## TO_START=0D
+ gEfiBlockIoProtocolGuid ## TO_START=0D
+ gEfiSimpleFileSystemProtocolGuid ## BY_START=0D
+ gEfiUnicodeCollationProtocolGuid ## TO_START=0D
+ gEfiUnicodeCollation2ProtocolGuid ## TO_START=0D
+=0D
+[Pcd]=0D
+ gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultLang ## SOMETIM=
ES_CONSUMES=0D
+ gEfiMdePkgTokenSpaceGuid.PcdUefiVariableDefaultPlatformLang ## SOMETIM=
ES_CONSUMES=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni b/Features/Ext4Pkg/Ext4Dx=
e/Ext4Dxe.uni
new file mode 100644
index 0000000000..7476fbf9bd
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
@@ -0,0 +1,15 @@
+## @file=0D
+# Ext4 Package=0D
+#=0D
+# UEFI Driver that produces the Simple File System Protocol for a partiti=
on that is formatted=0D
+# with the EXT4 file system.=0D
+# More details are available at: https://www.kernel.org/doc/html/v5.4/fil=
esystems/ext4/index.html=0D
+#=0D
+# Copyright (c) 2021 Pedro Falcato=0D
+# SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+#=0D
+##=0D
+=0D
+#string STR_MODULE_ABSTRACT #language en-US "UEFI driver for th=
e EXT4 file system."=0D
+=0D
+#string STR_MODULE_DESCRIPTION #language en-US "Produces the EFI S=
imple File System protocol."=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/=
Extents.c
new file mode 100644
index 0000000000..db4bf5aa3f
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
@@ -0,0 +1,616 @@
+/**=0D
+ @file Extent related routines=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+/**=0D
+ Checks if the checksum of the extent data block is correct.=0D
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D
+ @param[in] File Pointer to the file.=0D
+=0D
+ @return TRUE if the checksum is correct, FALSE if there is corruption.=
=0D
+*/=0D
+BOOLEAN=0D
+Ext4CheckExtentChecksum (=0D
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D
+ IN CONST EXT4_FILE *File=0D
+ );=0D
+=0D
+/**=0D
+ Calculates the checksum of the extent data block.=0D
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D
+ @param[in] File Pointer to the file.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT32=0D
+Ext4CalculateExtentChecksum (=0D
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D
+ IN CONST EXT4_FILE *File=0D
+ );=0D
+=0D
+/**=0D
+ Caches a range of extents, by allocating pool memory for each extent an=
d adding it to the tree.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+ @param[in] Extents Pointer to an array of extents.=0D
+ @param[in] NumberExtents Length of the array.=0D
+*/=0D
+VOID=0D
+Ext4CacheExtents (=0D
+ IN EXT4_FILE *File,=0D
+ IN CONST EXT4_EXTENT *Extents,=0D
+ IN UINT16 NumberExtents=0D
+ );=0D
+=0D
+/**=0D
+ Gets an extent from the extents cache of the file.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+ @param[in] Block Block we want to grab.=0D
+=0D
+ @return Pointer to the extent, or NULL if it was not found.=0D
+*/=0D
+EXT4_EXTENT *=0D
+Ext4GetExtentFromMap (=0D
+ IN EXT4_FILE *File,=0D
+ IN UINT32 Block=0D
+ );=0D
+=0D
+/**=0D
+ Retrieves the pointer to the top of the extent tree.=0D
+ @param[in] Inode Pointer to the inode structure.=0D
+=0D
+ @return Pointer to an EXT4_EXTENT_HEADER. This pointer is inside=0D
+ the inode and must not be freed.=0D
+*/=0D
+STATIC=0D
+EXT4_EXTENT_HEADER *=0D
+Ext4GetInoExtentHeader (=0D
+ IN EXT4_INODE *Inode=0D
+ )=0D
+{=0D
+ return (EXT4_EXTENT_HEADER *)Inode->i_data;=0D
+}=0D
+=0D
+/**=0D
+ Checks if an extent header is valid.=0D
+ @param[in] Header Pointer to the EXT4_EXTENT_HEADER struct=
ure.=0D
+=0D
+ @return TRUE if valid, FALSE if not.=0D
+*/=0D
+STATIC=0D
+BOOLEAN=0D
+Ext4ExtentHeaderValid (=0D
+ IN CONST EXT4_EXTENT_HEADER *Header=0D
+ )=0D
+{=0D
+ if(Header->eh_depth > EXT4_EXTENT_TREE_MAX_DEPTH) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header depth %u\n", Header=
->eh_depth));=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ if(Header->eh_magic !=3D EXT4_EXTENT_HEADER_MAGIC) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header magic %x\n", Header=
->eh_magic));=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ if(Header->eh_max < Header->eh_entries) {=0D
+ DEBUG ((=0D
+ EFI_D_ERROR,=0D
+ "[ext4] Invalid extent header num entries %u max entries %u\n",=0D
+ Header->eh_entries,=0D
+ Header->eh_max=0D
+ ));=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ return TRUE;=0D
+}=0D
+=0D
+/**=0D
+ Performs a binary search for a EXT4_EXTENT_INDEX that corresponds to a=
=0D
+ logical block in a given extent tree node.=0D
+=0D
+ @param[in] Header Pointer to the EXT4_EXTENT_HEADER struct=
ure.=0D
+ @param[in] LogicalBlock Block that will be searched=0D
+=0D
+ @return Pointer to the found EXT4_EXTENT_INDEX.=0D
+*/=0D
+STATIC=0D
+EXT4_EXTENT_INDEX *=0D
+Ext4BinsearchExtentIndex (=0D
+ IN EXT4_EXTENT_HEADER *Header,=0D
+ IN EXT4_BLOCK_NR LogicalBlock=0D
+ )=0D
+{=0D
+ EXT4_EXTENT_INDEX *l;=0D
+ EXT4_EXTENT_INDEX *r;=0D
+ EXT4_EXTENT_INDEX *m;=0D
+=0D
+ l =3D ((EXT4_EXTENT_INDEX *)(Header + 1)) + 1;=0D
+ r =3D ((EXT4_EXTENT_INDEX *)(Header + 1)) + Header->eh_entries - 1;=0D
+=0D
+ // Perform a mostly-standard binary search on the array=0D
+ // This works very nicely because the extents arrays are always sorted.=
=0D
+=0D
+ while(l <=3D r) {=0D
+ m =3D l + (r - l) / 2;=0D
+=0D
+ if(LogicalBlock < m->ei_block) {=0D
+ r =3D m - 1;=0D
+ } else {=0D
+ l =3D m + 1;=0D
+ }=0D
+ }=0D
+=0D
+ return l - 1;=0D
+}=0D
+=0D
+/**=0D
+ Performs a binary search for a EXT4_EXTENT that corresponds to a=0D
+ logical block in a given extent tree node.=0D
+=0D
+ @param[in] Header Pointer to the EXT4_EXTENT_HEADER struct=
ure.=0D
+ @param[in] LogicalBlock Block that will be searched=0D
+=0D
+ @return Pointer to the found EXT4_EXTENT_INDEX, else NULL if the array =
is empty.=0D
+ Note: The caller must check if the logical block=0D
+ is actually mapped under the given extent.=0D
+*/=0D
+STATIC=0D
+EXT4_EXTENT *=0D
+Ext4BinsearchExtentExt (=0D
+ IN EXT4_EXTENT_HEADER *Header,=0D
+ IN EXT4_BLOCK_NR LogicalBlock=0D
+ )=0D
+{=0D
+ EXT4_EXTENT *l;=0D
+ EXT4_EXTENT *r;=0D
+ EXT4_EXTENT *m;=0D
+=0D
+ l =3D ((EXT4_EXTENT *)(Header + 1)) + 1;=0D
+ r =3D ((EXT4_EXTENT *)(Header + 1)) + Header->eh_entries - 1;=0D
+ // Perform a mostly-standard binary search on the array=0D
+ // This works very nicely because the extents arrays are always sorted.=
=0D
+=0D
+ // Empty array=0D
+ if(Header->eh_entries =3D=3D 0) {=0D
+ return NULL;=0D
+ }=0D
+=0D
+ while(l <=3D r) {=0D
+ m =3D l + (r - l) / 2;=0D
+=0D
+ if(LogicalBlock < m->ee_block) {=0D
+ r =3D m - 1;=0D
+ } else {=0D
+ l =3D m + 1;=0D
+ }=0D
+ }=0D
+=0D
+ return l - 1;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves the leaf block from an EXT4_EXTENT_INDEX.=0D
+=0D
+ @param[in] Index Pointer to the EXT4_EXTENT_INDEX structu=
re.=0D
+=0D
+ @return Block number of the leaf node.=0D
+*/=0D
+STATIC=0D
+EXT4_BLOCK_NR=0D
+Ext4ExtentIdxLeafBlock (=0D
+ IN EXT4_EXTENT_INDEX *Index=0D
+ )=0D
+{=0D
+ return ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;=0D
+}=0D
+=0D
+STATIC UINTN GetExtentRequests =3D 0;=0D
+STATIC UINTN GetExtentCacheHits =3D 0;=0D
+=0D
+/**=0D
+ Retrieves an extent from an EXT4 inode.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[in] LogicalBlock Block number which the returned extent mu=
st cover.=0D
+ @param[out] Extent Pointer to the output buffer, where the e=
xtent will be copied to.=0D
+=0D
+ @retval EFI_SUCCESS Retrieval was succesful.=0D
+ @retval EFI_NO_MAPPING Block has no mapping.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4GetExtent (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_FILE *File,=0D
+ IN EXT4_BLOCK_NR LogicalBlock,=0D
+ OUT EXT4_EXTENT *Extent=0D
+ )=0D
+{=0D
+ EXT4_INODE *Inode;=0D
+ VOID *Buffer;=0D
+ EXT4_EXTENT *Ext;=0D
+ UINT32 CurrentDepth;=0D
+ EXT4_EXTENT_HEADER *ExtHeader;=0D
+=0D
+ Inode =3D File->Inode;=0D
+ Ext =3D NULL;=0D
+ Buffer =3D NULL;=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Looking up extent for block %lu\n", LogicalB=
lock));=0D
+=0D
+ if (!(Inode->i_flags & EXT4_EXTENTS_FL)) {=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ // ext4 does not have support for logical block numbers bigger than UINT=
32_MAX=0D
+ if (LogicalBlock > (UINT32)- 1) {=0D
+ return EFI_NO_MAPPING;=0D
+ }=0D
+=0D
+ GetExtentRequests++;=0D
+ #if DEBUG_EXTENT_CACHE=0D
+ DEBUG ((=0D
+ EFI_D_INFO,=0D
+ "[ext4] Requests %lu, hits %lu, misses %lu\n",=0D
+ GetExtentRequests,=0D
+ GetExtentCacheHits,=0D
+ GetExtentRequests - GetExtentCacheHits=0D
+ ));=0D
+ #endif=0D
+=0D
+ // Note: Right now, holes are the single biggest reason for cache misses=
=0D
+ // We should find a way to get (or cache) holes=0D
+ if ((Ext =3D Ext4GetExtentFromMap (File, (UINT32)LogicalBlock)) !=3D NUL=
L) {=0D
+ *Extent =3D *Ext;=0D
+ GetExtentCacheHits++;=0D
+=0D
+ return EFI_SUCCESS;=0D
+ }=0D
+=0D
+ // Slow path, we'll need to read from disk and (try to) cache those exte=
nts.=0D
+=0D
+ ExtHeader =3D Ext4GetInoExtentHeader (Inode);=0D
+=0D
+ if(!Ext4ExtentHeaderValid (ExtHeader)) {=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ CurrentDepth =3D ExtHeader->eh_depth;=0D
+=0D
+ while(ExtHeader->eh_depth !=3D 0) {=0D
+ EXT4_EXTENT_INDEX *Index;=0D
+ EFI_STATUS Status;=0D
+=0D
+ CurrentDepth--;=0D
+ // While depth !=3D 0, we're traversing the tree itself and not any le=
aves=0D
+ // As such, every entry is an EXT4_EXTENT_INDEX entry=0D
+ // Note: Entries after the extent header, either index or actual exten=
t, are always sorted.=0D
+ // Therefore, we can use binary search, and it's actually the standard=
for doing so=0D
+ // (see FreeBSD).=0D
+=0D
+ Index =3D Ext4BinsearchExtentIndex (ExtHeader, LogicalBlock);=0D
+=0D
+ if(Buffer =3D=3D NULL) {=0D
+ Buffer =3D AllocatePool (Partition->BlockSize);=0D
+ if(Buffer =3D=3D NULL) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+ }=0D
+=0D
+ // Read the leaf block onto the previously-allocated buffer.=0D
+=0D
+ Status =3D Ext4ReadBlocks (Partition, Buffer, 1, Ext4ExtentIdxLeafBloc=
k (Index));=0D
+ if(EFI_ERROR (Status)) {=0D
+ FreePool (Buffer);=0D
+ return Status;=0D
+ }=0D
+=0D
+ ExtHeader =3D Buffer;=0D
+=0D
+ if(!Ext4ExtentHeaderValid (ExtHeader)) {=0D
+ FreePool (Buffer);=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ if(!Ext4CheckExtentChecksum (ExtHeader, File)) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent checksum\n"));=0D
+ FreePool (Buffer);=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ if(ExtHeader->eh_depth !=3D CurrentDepth) {=0D
+ FreePool (Buffer);=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+ }=0D
+=0D
+ /* We try to cache every extent under a single leaf, since it's quite li=
kely that we=0D
+ * may need to access things sequentially. Furthermore, ext4 block alloc=
ation as done=0D
+ * by linux (and possibly other systems) is quite fancy and usually it r=
esults in a small number of extents.=0D
+ * Therefore, we shouldn't have any memory issues.=0D
+ */=0D
+ Ext4CacheExtents (File, (EXT4_EXTENT *)(ExtHeader + 1), ExtHeader->eh_en=
tries);=0D
+=0D
+ Ext =3D Ext4BinsearchExtentExt (ExtHeader, LogicalBlock);=0D
+=0D
+ if(!Ext) {=0D
+ if(Buffer !=3D NULL) {=0D
+ FreePool (Buffer);=0D
+ }=0D
+=0D
+ return EFI_NO_MAPPING;=0D
+ }=0D
+=0D
+ if(!(LogicalBlock >=3D Ext->ee_block && Ext->ee_block + Ext->ee_len > Lo=
gicalBlock)) {=0D
+ // This extent does not cover the block=0D
+ if(Buffer !=3D NULL) {=0D
+ FreePool (Buffer);=0D
+ }=0D
+=0D
+ return EFI_NO_MAPPING;=0D
+ }=0D
+=0D
+ *Extent =3D *Ext;=0D
+=0D
+ if(Buffer !=3D NULL) {=0D
+ FreePool (Buffer);=0D
+ }=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Compare two EXT4_EXTENT structs.=0D
+ Used in the extent map's ORDERED_COLLECTION.=0D
+=0D
+ @param[in] UserStruct1 Pointer to the first user structure.=0D
+=0D
+ @param[in] UserStruct2 Pointer to the second user structure.=0D
+=0D
+ @retval <0 If UserStruct1 compares less than UserStruct2.=0D
+=0D
+ @retval 0 If UserStruct1 compares equal to UserStruct2.=0D
+=0D
+ @retval >0 If UserStruct1 compares greater than UserStruct2.=0D
+**/=0D
+STATIC=0D
+INTN EFIAPI=0D
+Ext4ExtentsMapStructCompare (=0D
+ IN CONST VOID *UserStruct1,=0D
+ IN CONST VOID *UserStruct2=0D
+ )=0D
+{=0D
+ CONST EXT4_EXTENT *Extent1 =3D UserStruct1;=0D
+ CONST EXT4_EXTENT *Extent2 =3D UserStruct2;=0D
+=0D
+ // TODO: Detect extent overlaps? in case of corruption.=0D
+=0D
+ /* DEBUG((EFI_D_INFO, "[ext4] extent 1 %u extent 2 %u =3D %ld\n", Extent=
1->ee_block,=0D
+ Extent2->ee_block, Extent1->ee_block - Extent2->ee_block)); */=0D
+ return Extent1->ee_block < Extent2->ee_block ? - 1 :=0D
+ Extent1->ee_block > Extent2->ee_block ? 1 : 0;=0D
+}=0D
+=0D
+/**=0D
+ Compare a standalone key against a EXT4_EXTENT containing an embedded ke=
y.=0D
+ Used in the extent map's ORDERED_COLLECTION.=0D
+=0D
+ @param[in] StandaloneKey Pointer to the bare key.=0D
+=0D
+ @param[in] UserStruct Pointer to the user structure with the embedde=
d=0D
+ key.=0D
+=0D
+ @retval <0 If StandaloneKey compares less than UserStruct's key.=0D
+=0D
+ @retval 0 If StandaloneKey compares equal to UserStruct's key.=0D
+=0D
+ @retval >0 If StandaloneKey compares greater than UserStruct's key.=0D
+**/=0D
+STATIC=0D
+INTN EFIAPI=0D
+Ext4ExtentsMapKeyCompare (=0D
+ IN CONST VOID *StandaloneKey,=0D
+ IN CONST VOID *UserStruct=0D
+ )=0D
+{=0D
+ CONST EXT4_EXTENT *Extent =3D UserStruct;=0D
+=0D
+ // Note that logical blocks are 32-bits in size so no truncation can hap=
pen here=0D
+ // with regards to 32-bit architectures.=0D
+ UINT32 Block =3D (UINT32)(UINTN)StandaloneKey;=0D
+=0D
+ // DEBUG((EFI_D_INFO, "[ext4] comparing %u %u\n", Block, Extent->ee_bloc=
k));=0D
+ if(Block >=3D Extent->ee_block && Block < Extent->ee_block + Extent->ee_=
len) {=0D
+ return 0;=0D
+ }=0D
+=0D
+ return Block < Extent->ee_block ? - 1 :=0D
+ Block > Extent->ee_block ? 1 : 0;=0D
+}=0D
+=0D
+/**=0D
+ Initialises the (empty) extents map, that will work as a cache of exten=
ts.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+=0D
+ @return Result of the operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4InitExtentsMap (=0D
+ IN EXT4_FILE *File=0D
+ )=0D
+{=0D
+ File->ExtentsMap =3D OrderedCollectionInit (Ext4ExtentsMapStructCompare,=
Ext4ExtentsMapKeyCompare);=0D
+ if (!File->ExtentsMap) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Frees the extents map, deleting every extent stored.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+*/=0D
+VOID=0D
+Ext4FreeExtentsMap (=0D
+ IN EXT4_FILE *File=0D
+ )=0D
+{=0D
+ // Keep calling Min(), so we get an arbitrary node we can delete.=0D
+ // If Min() returns NULL, it's empty.=0D
+=0D
+ ORDERED_COLLECTION_ENTRY *MinEntry =3D NULL;=0D
+=0D
+ while ((MinEntry =3D OrderedCollectionMin (File->ExtentsMap)) !=3D NULL)=
{=0D
+ EXT4_EXTENT *Ext;=0D
+ OrderedCollectionDelete (File->ExtentsMap, MinEntry, (VOID **)&Ext);=0D
+ FreePool (Ext);=0D
+ }=0D
+=0D
+ ASSERT (OrderedCollectionIsEmpty (File->ExtentsMap));=0D
+=0D
+ OrderedCollectionUninit (File->ExtentsMap);=0D
+ File->ExtentsMap =3D NULL;=0D
+}=0D
+=0D
+/**=0D
+ Caches a range of extents, by allocating pool memory for each extent an=
d adding it to the tree.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+ @param[in] Extents Pointer to an array of extents.=0D
+ @param[in] NumberExtents Length of the array.=0D
+*/=0D
+VOID=0D
+Ext4CacheExtents (=0D
+ IN EXT4_FILE *File, IN CONST EXT4_EXTENT *Extents, IN UINT16 NumberExten=
ts=0D
+ )=0D
+{=0D
+ UINT16 i;=0D
+=0D
+ /* Note that any out of memory condition might mean we don't get to cach=
e a whole leaf of extents=0D
+ * in which case, future insertions might fail.=0D
+ */=0D
+=0D
+ for(i =3D 0; i < NumberExtents; i++, Extents++) {=0D
+ EXT4_EXTENT *Extent;=0D
+ EFI_STATUS Status;=0D
+=0D
+ Extent =3D AllocatePool (sizeof (EXT4_EXTENT));=0D
+=0D
+ if (Extent =3D=3D NULL) {=0D
+ return;=0D
+ }=0D
+=0D
+ CopyMem (Extent, Extents, sizeof (EXT4_EXTENT));=0D
+ Status =3D OrderedCollectionInsert (File->ExtentsMap, NULL, Extent);=0D
+=0D
+ // EFI_ALREADY_STARTED =3D already exists in the tree.=0D
+ if (EFI_ERROR (Status)) {=0D
+ FreePool (Extent);=0D
+=0D
+ if(Status =3D=3D EFI_ALREADY_STARTED) {=0D
+ continue;=0D
+ }=0D
+=0D
+ return;=0D
+ }=0D
+=0D
+ #if DEBUG_EXTENT_CACHE=0D
+ DEBUG ((=0D
+ EFI_D_INFO,=0D
+ "[ext4] Cached extent [%lu, %lu]\n",=0D
+ Extent->ee_block,=0D
+ Extent->ee_block + Extent->ee_len - 1=0D
+ ));=0D
+ #endif=0D
+ }=0D
+}=0D
+=0D
+/**=0D
+ Gets an extent from the extents cache of the file.=0D
+=0D
+ @param[in] File Pointer to the open file.=0D
+ @param[in] Block Block we want to grab.=0D
+=0D
+ @return Pointer to the extent, or NULL if it was not found.=0D
+*/=0D
+EXT4_EXTENT *=0D
+Ext4GetExtentFromMap (=0D
+ IN EXT4_FILE *File,=0D
+ IN UINT32 Block=0D
+ )=0D
+{=0D
+ ORDERED_COLLECTION_ENTRY *Entry;=0D
+=0D
+ Entry =3D OrderedCollectionFind (File->ExtentsMap, (CONST VOID *)(UINTN)=
Block);=0D
+=0D
+ if (Entry =3D=3D NULL) {=0D
+ return NULL;=0D
+ }=0D
+=0D
+ return OrderedCollectionUserStruct (Entry);=0D
+}=0D
+=0D
+/**=0D
+ Calculates the checksum of the extent data block.=0D
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D
+ @param[in] File Pointer to the file.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT32=0D
+Ext4CalculateExtentChecksum (=0D
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D
+ IN CONST EXT4_FILE *File=0D
+ )=0D
+{=0D
+ UINT32 Csum;=0D
+ EXT4_PARTITION *Partition;=0D
+ EXT4_INODE *Inode;=0D
+=0D
+ Partition =3D File->Partition;=0D
+ Inode =3D File->Inode;=0D
+=0D
+ Csum =3D Ext4CalculateChecksum (Partition, &File->InodeNum, sizeof (EXT4=
_INO_NR), Partition->InitialSeed);=0D
+ Csum =3D Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof =
(Inode->i_generation), Csum);=0D
+ Csum =3D Ext4CalculateChecksum (Partition, ExtHeader, Partition->BlockSi=
ze - sizeof (EXT4_EXTENT_TAIL), Csum);=0D
+=0D
+ return Csum;=0D
+}=0D
+=0D
+/**=0D
+ Checks if the checksum of the extent data block is correct.=0D
+ @param[in] ExtHeader Pointer to the EXT4_EXTENT_HEADER.=0D
+ @param[in] File Pointer to the file.=0D
+=0D
+ @return TRUE if the checksum is correct, FALSE if there is corruption.=
=0D
+*/=0D
+BOOLEAN=0D
+Ext4CheckExtentChecksum (=0D
+ IN CONST EXT4_EXTENT_HEADER *ExtHeader,=0D
+ IN CONST EXT4_FILE *File=0D
+ )=0D
+{=0D
+ EXT4_PARTITION *Partition;=0D
+ EXT4_EXTENT_TAIL *Tail;=0D
+=0D
+ Partition =3D File->Partition;=0D
+=0D
+ if(!Ext4HasMetadataCsum (Partition)) {=0D
+ return TRUE;=0D
+ }=0D
+=0D
+ Tail =3D (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->Blo=
ckSize - 4));=0D
+=0D
+ return Tail->eb_checksum =3D=3D Ext4CalculateExtentChecksum (ExtHeader, =
File);=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/Fil=
e.c
new file mode 100644
index 0000000000..10dda64b16
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/File.c
@@ -0,0 +1,583 @@
+/**=0D
+ @file EFI_FILE_PROTOCOL implementation for EXT4=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+#include <Library/BaseUcs2Utf8Lib.h>=0D
+=0D
+/**=0D
+ Duplicates a file structure.=0D
+=0D
+ @param[in] Original Pointer to the original file.=0D
+=0D
+ @return Pointer to the new file structure.=0D
+ */=0D
+STATIC=0D
+EXT4_FILE *=0D
+Ext4DuplicateFile (=0D
+ IN CONST EXT4_FILE *Original=0D
+ );=0D
+=0D
+/**=0D
+ Gets the next path segment.=0D
+=0D
+ @param[in] Path Pointer to the rest of the path.=0D
+ @param[out] PathSegment Pointer to the buffer that will hold the =
path segment.=0D
+ Note: It's necessarily EXT4_NAME_MAX +1 l=
ong.=0D
+ @param[out] Length Pointer to the UINTN that will hold the l=
ength of the path segment.=0D
+=0D
+ @retval !EFI_SUCCESS The path segment is too large(> EXT4_NAME=
_MAX).=0D
+ */=0D
+STATIC=0D
+EFI_STATUS=0D
+GetPathSegment (=0D
+ IN CONST CHAR16 *Path, OUT CHAR16 *PathSegment, OUT UINTN *Length=0D
+ )=0D
+{=0D
+ CONST CHAR16 *Start =3D Path;=0D
+ CONST CHAR16 *End =3D Path;=0D
+=0D
+ // The path segment ends on a backslash or a null terminator=0D
+ for( ; *End !=3D L'\0' && *End !=3D L'\\'; End++) {=0D
+ }=0D
+=0D
+ *Length =3D End - Start;=0D
+=0D
+ return StrnCpyS (PathSegment, EXT4_NAME_MAX, Start, End - Start);=0D
+}=0D
+=0D
+/**=0D
+ Detects if we have more path segments on the path.=0D
+=0D
+ @param[in] Path Pointer to the rest of the path.=0D
+ @return True if we're on the last segment, false if there are=
more=0D
+ segments.=0D
+ */=0D
+STATIC=0D
+BOOLEAN=0D
+Ext4IsLastPathSegment (=0D
+ IN CONST CHAR16 *Path=0D
+ )=0D
+{=0D
+ while(Path[0] =3D=3D L'\\') {=0D
+ Path++;=0D
+ }=0D
+=0D
+ return Path[0] =3D=3D '\0';=0D
+}=0D
+=0D
+#define EXT4_INO_PERM_READ_OWNER 0400=0D
+#define EXT4_INO_PERM_WRITE_OWNER 0200=0D
+#define EXT4_INO_PERM_EXEC_OWNER 0100=0D
+=0D
+/**=0D
+ Detects if we have permissions to open the file on the desired mode.=0D
+=0D
+ @param[in out] File Pointer to the file we're opening.=0D
+ @param[in] OpenMode Mode in which to open the file.=0D
+=0D
+ @return True if the open was succesful, false if we don't hav=
e=0D
+ enough permissions.=0D
+ */=0D
+STATIC=0D
+BOOLEAN=0D
+Ext4ApplyPermissions (=0D
+ IN OUT EXT4_FILE *File, IN UINT64 OpenMode=0D
+ )=0D
+{=0D
+ UINT16 NeededPerms =3D 0;=0D
+=0D
+ if((OpenMode & EFI_FILE_MODE_READ) !=3D 0) {=0D
+ NeededPerms |=3D EXT4_INO_PERM_READ_OWNER;=0D
+ }=0D
+=0D
+ if((OpenMode & EFI_FILE_MODE_WRITE) !=3D 0) {=0D
+ NeededPerms |=3D EXT4_INO_PERM_WRITE_OWNER;=0D
+ }=0D
+=0D
+ if((File->Inode->i_mode & NeededPerms) !=3D NeededPerms) {=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ File->OpenMode =3D OpenMode;=0D
+=0D
+ return TRUE;=0D
+}=0D
+=0D
+/**=0D
+ Detects if we have permissions to search on the directory.=0D
+=0D
+ @param[in out] File Pointer to the open directory.=0D
+=0D
+ @return True if we have permission to search, else false.=0D
+ */=0D
+STATIC=0D
+BOOLEAN=0D
+Ext4DirCanLookup (=0D
+ IN CONST EXT4_FILE *File=0D
+ )=0D
+{=0D
+ // In UNIX, executable permission on directories means that we have perm=
ission to look up=0D
+ // files in a directory.=0D
+ return (File->Inode->i_mode & EXT4_INO_PERM_EXEC_OWNER) =3D=3D EXT4_INO_=
PERM_EXEC_OWNER;=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Open (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ OUT EFI_FILE_PROTOCOL **NewHandle,=0D
+ IN CHAR16 *FileName,=0D
+ IN UINT64 OpenMode,=0D
+ IN UINT64 Attributes=0D
+ )=0D
+{=0D
+ EXT4_FILE *Current;=0D
+ EXT4_PARTITION *Partition;=0D
+ UINTN Level;=0D
+=0D
+ Current =3D (EXT4_FILE *)This;=0D
+ Partition =3D Current->Partition;=0D
+ Level =3D 0;=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Ext4Open %s\n", FileName));=0D
+ // If the path starts with a backslash, we treat the root directory as t=
he base directory=0D
+ if(FileName[0] =3D=3D L'\\') {=0D
+ FileName++;=0D
+ Current =3D Partition->Root;=0D
+ }=0D
+=0D
+ while(FileName[0] !=3D L'\0') {=0D
+ CHAR16 PathSegment[EXT4_NAME_MAX + 1];=0D
+ UINTN Length;=0D
+ EXT4_FILE *File;=0D
+ EFI_STATUS Status;=0D
+=0D
+ // Discard leading path separators=0D
+ while(FileName[0] =3D=3D L'\\') {=0D
+ FileName++;=0D
+ }=0D
+=0D
+ if(GetPathSegment (FileName, PathSegment, &Length) !=3D EFI_SUCCESS) {=
=0D
+ return EFI_BUFFER_TOO_SMALL;=0D
+ }=0D
+=0D
+ // Reached the end of the path=0D
+ if(Length =3D=3D 0) {=0D
+ break;=0D
+ }=0D
+=0D
+ FileName +=3D Length;=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Opening %s\n", PathSegment));=0D
+=0D
+ // TODO: What to do with symlinks? They're nonsense when absolute but =
may=0D
+ // be useful when they're relative.=0D
+=0D
+ if (!Ext4FileIsDir (Current)) {=0D
+ return EFI_INVALID_PARAMETER;=0D
+ }=0D
+=0D
+ if (!Ext4IsLastPathSegment (FileName)) {=0D
+ if (!Ext4DirCanLookup (Current)) {=0D
+ return EFI_ACCESS_DENIED;=0D
+ }=0D
+ }=0D
+=0D
+ Status =3D Ext4OpenFile (Current, PathSegment, Partition, EFI_FILE_MOD=
E_READ, &File);=0D
+=0D
+ if(EFI_ERROR (Status) && Status !=3D EFI_NOT_FOUND) {=0D
+ return Status;=0D
+ } else if(Status =3D=3D EFI_NOT_FOUND) {=0D
+ // WRITE SUPPORT: Handle file creation when write support is added=0D
+ return Status;=0D
+ }=0D
+=0D
+ // Check if this is a valid file to open in EFI=0D
+ if(!Ext4FileIsOpenable (File)) {=0D
+ Ext4CloseInternal (File);=0D
+ // This looks like an /okay/ status to return.=0D
+ return EFI_ACCESS_DENIED;=0D
+ }=0D
+=0D
+ if(Level !=3D 0) {=0D
+ // Careful not to close the base directory=0D
+ Ext4CloseInternal (Current);=0D
+ }=0D
+=0D
+ Level++;=0D
+=0D
+ Current =3D File;=0D
+ }=0D
+=0D
+ if (Level =3D=3D 0) {=0D
+ // We opened the base directory again, so we need to duplicate the fil=
e structure=0D
+ Current =3D Ext4DuplicateFile (Current);=0D
+ if (Current =3D=3D NULL) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+ }=0D
+=0D
+ if(!Ext4ApplyPermissions (Current, OpenMode)) {=0D
+ Ext4CloseInternal (Current);=0D
+ return EFI_ACCESS_DENIED;=0D
+ }=0D
+=0D
+ *NewHandle =3D &Current->Protocol;=0D
+=0D
+ DEBUG ((EFI_D_INFO, "Open successful\n"));=0D
+ DEBUG ((EFI_D_INFO, "Opened filename %s\n", Current->FileName));=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Close (=0D
+ IN EFI_FILE_PROTOCOL *This=0D
+ )=0D
+{=0D
+ return Ext4CloseInternal ((EXT4_FILE *)This);=0D
+}=0D
+=0D
+/**=0D
+ Closes a file.=0D
+=0D
+ @param[in] File Pointer to the file.=0D
+=0D
+ @return Status of the closing of the file.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4CloseInternal (=0D
+ IN EXT4_FILE *File=0D
+ )=0D
+{=0D
+ if(File =3D=3D File->Partition->Root && !File->Partition->Unmounting) {=
=0D
+ return EFI_SUCCESS;=0D
+ }=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Closed file %p (inode %lu)\n", File, File->I=
nodeNum));=0D
+ RemoveEntryList (&File->OpenFilesListNode);=0D
+ FreePool (File->FileName);=0D
+ FreePool (File->Inode);=0D
+ Ext4FreeExtentsMap (File);=0D
+ FreePool (File);=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+EFI_STATUS EFIAPI=0D
+Ext4Delete (=0D
+ IN EFI_FILE_PROTOCOL *This=0D
+ )=0D
+{=0D
+ // Write support=0D
+ Ext4Close (This);=0D
+ return EFI_WARN_DELETE_FAILURE;=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4ReadFile (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN OUT UINTN *BufferSize,=0D
+ OUT VOID *Buffer=0D
+ )=0D
+{=0D
+ EXT4_FILE *File;=0D
+ EXT4_PARTITION *Partition;=0D
+ EFI_STATUS Status;=0D
+=0D
+ File =3D (EXT4_FILE *)This;=0D
+ Partition =3D File->Partition;=0D
+=0D
+ ASSERT (Ext4FileIsOpenable (File));=0D
+=0D
+ if(Ext4FileIsReg (File)) {=0D
+ Status =3D Ext4Read (Partition, File, Buffer, File->Position, BufferSi=
ze);=0D
+ if(Status =3D=3D EFI_SUCCESS) {=0D
+ File->Position +=3D *BufferSize;=0D
+ }=0D
+=0D
+ return Status;=0D
+ } else if(Ext4FileIsDir (File)) {=0D
+ Status =3D Ext4ReadDir (Partition, File, Buffer, File->Position, Buffe=
rSize);=0D
+ DEBUG ((EFI_D_INFO, "[ext4] ReadDir status %lx\n", Status));=0D
+=0D
+ if(Status =3D=3D EFI_SUCCESS) {=0D
+ DEBUG ((EFI_D_INFO, "[ext4] ReadDir retlen %lu\n", *BufferSize));=0D
+ }=0D
+=0D
+ return Status;=0D
+ }=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4WriteFile (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN OUT UINTN *BufferSize,=0D
+ IN VOID *Buffer=0D
+ )=0D
+{=0D
+ EXT4_FILE *File =3D (EXT4_FILE *)This;=0D
+=0D
+ if(!(File->OpenMode & EFI_FILE_MODE_WRITE)) {=0D
+ return EFI_ACCESS_DENIED;=0D
+ }=0D
+=0D
+ // TODO: Add write support=0D
+ return EFI_WRITE_PROTECTED;=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4GetPosition (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ OUT UINT64 *Position=0D
+ )=0D
+{=0D
+ EXT4_FILE *File =3D (EXT4_FILE *)This;=0D
+=0D
+ if(Ext4FileIsDir (File)) {=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ *Position =3D File->Position;=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4SetPosition (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN UINT64 Position=0D
+ )=0D
+{=0D
+ EXT4_FILE *File =3D (EXT4_FILE *)This;=0D
+=0D
+ // Only seeks to 0 (so it resets the ReadDir operation) are allowed=0D
+ if(Ext4FileIsDir (File) && Position !=3D 0) {=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ // -1 (0xffffff.......) seeks to the end of the file=0D
+ if(Position =3D=3D (UINT64)- 1) {=0D
+ Position =3D EXT4_INODE_SIZE (File->Inode);=0D
+ }=0D
+=0D
+ File->Position =3D Position;=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Retrieves information about the file and stores it in the EFI_FILE_INFO=
format.=0D
+=0D
+ @param[in] File Pointer to an opened file.=0D
+ @param[out] Info Pointer to a EFI_FILE_INFO.=0D
+ @param[in out] BufferSize Pointer to the buffer size=0D
+=0D
+ @return Status of the file information request.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4GetFileInfo (=0D
+ IN EXT4_FILE *File, OUT EFI_FILE_INFO *Info, IN OUT UINTN *BufferSize=0D
+ )=0D
+{=0D
+ // TODO: Get a way to set the directory entry for SetFileInfo=0D
+ UINTN FileNameLen =3D StrLen (File->FileName);=0D
+ UINTN FileNameSize =3D StrSize (File->FileName);=0D
+=0D
+ UINTN NeededLength =3D SIZE_OF_EFI_FILE_INFO + FileNameSize;=0D
+=0D
+ if(*BufferSize < NeededLength) {=0D
+ *BufferSize =3D NeededLength;=0D
+ return EFI_BUFFER_TOO_SMALL;=0D
+ }=0D
+=0D
+ Info->FileSize =3D EXT4_INODE_SIZE (File->Inode);=0D
+ Info->PhysicalSize =3D Ext4FilePhysicalSpace (File);=0D
+ Ext4FileATime (File, &Info->LastAccessTime);=0D
+ Ext4FileMTime (File, &Info->ModificationTime);=0D
+ Ext4FileCreateTime (File, &Info->LastAccessTime);=0D
+ Info->Attribute =3D 0;=0D
+ Info->Size =3D NeededLength;=0D
+=0D
+ if(Ext4FileIsDir (File)) {=0D
+ Info->Attribute |=3D EFI_FILE_DIRECTORY;=0D
+ }=0D
+=0D
+ *BufferSize =3D NeededLength;=0D
+=0D
+ return StrCpyS (Info->FileName, FileNameLen + 1, File->FileName);=0D
+}=0D
+=0D
+/**=0D
+ Retrieves information about the filesystem and stores it in the EFI_FIL=
E_SYSTEM_INFO format.=0D
+=0D
+ @param[in] File Pointer to an opened file.=0D
+ @param[out] Info Pointer to a EFI_FILE_SYSTEM_INFO.=0D
+ @param[in out] BufferSize Pointer to the buffer size=0D
+=0D
+ @return Status of the file information request.=0D
+*/=0D
+STATIC=0D
+EFI_STATUS=0D
+Ext4GetFilesystemInfo (=0D
+ IN EXT4_PARTITION *Part, OUT EFI_FILE_SYSTEM_INFO *Info, IN OUT UINTN *B=
ufferSize=0D
+ )=0D
+{=0D
+ // Length of s_volume_name + null terminator=0D
+ CHAR8 TempVolName[16 + 1];=0D
+ CHAR16 *VolumeName;=0D
+ UINTN VolNameLength;=0D
+ EFI_STATUS Status;=0D
+ UINTN NeededLength;=0D
+ EXT4_BLOCK_NR TotalBlocks;=0D
+ EXT4_BLOCK_NR FreeBlocks;=0D
+=0D
+ VolNameLength =3D 0;=0D
+ VolumeName =3D NULL;=0D
+=0D
+ // s_volume_name is only valid on dynamic revision; old filesystems don'=
t support this=0D
+ if(Part->SuperBlock.s_rev_level =3D=3D EXT4_DYNAMIC_REV) {=0D
+ CopyMem (TempVolName, (CONST CHAR8 *)Part->SuperBlock.s_volume_name, 1=
6);=0D
+ TempVolName[16] =3D '\0';=0D
+=0D
+ Status =3D UTF8StrToUCS2 (TempVolName, &VolumeName);=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ VolNameLength =3D StrLen (VolumeName);=0D
+ }=0D
+=0D
+ NeededLength =3D SIZE_OF_EFI_FILE_SYSTEM_INFO;=0D
+=0D
+ if (VolumeName !=3D NULL) {=0D
+ NeededLength +=3D StrSize (VolumeName);=0D
+ } else {=0D
+ // If we don't have a volume name, we set VolumeLabel to a single null=
terminator=0D
+ NeededLength +=3D sizeof (CHAR16);=0D
+ }=0D
+=0D
+ if(*BufferSize < NeededLength) {=0D
+ *BufferSize =3D NeededLength;=0D
+=0D
+ if (VolumeName !=3D NULL) {=0D
+ FreePool (VolumeName);=0D
+ }=0D
+=0D
+ return EFI_BUFFER_TOO_SMALL;=0D
+ }=0D
+=0D
+ TotalBlocks =3D Part->NumberBlocks;=0D
+=0D
+ FreeBlocks =3D Ext4MakeBlockNumberFromHalfs (=0D
+ Part,=0D
+ Part->SuperBlock.s_free_blocks_count,=0D
+ Part->SuperBlock.s_free_blocks_count_hi=0D
+ );=0D
+=0D
+ Info->BlockSize =3D Part->BlockSize;=0D
+ Info->Size =3D NeededLength;=0D
+ Info->ReadOnly =3D Part->ReadOnly;=0D
+ Info->VolumeSize =3D TotalBlocks * Part->BlockSize;=0D
+ Info->FreeSpace =3D FreeBlocks * Part->BlockSize;=0D
+=0D
+ if (VolumeName !=3D NULL) {=0D
+ StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);=0D
+ } else {=0D
+ Info->VolumeLabel[0] =3D L'\0';=0D
+ }=0D
+=0D
+ if (VolumeName !=3D NULL) {=0D
+ FreePool (VolumeName);=0D
+ }=0D
+=0D
+ *BufferSize =3D NeededLength;=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+EFI_STATUS=0D
+EFIAPI=0D
+Ext4GetInfo (=0D
+ IN EFI_FILE_PROTOCOL *This,=0D
+ IN EFI_GUID *InformationType,=0D
+ IN OUT UINTN *BufferSize,=0D
+ OUT VOID *Buffer=0D
+ )=0D
+{=0D
+ if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {=0D
+ return Ext4GetFileInfo ((EXT4_FILE *)This, Buffer, BufferSize);=0D
+ }=0D
+=0D
+ if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {=0D
+ return Ext4GetFilesystemInfo (((EXT4_FILE *)This)->Partition, Buffer, =
BufferSize);=0D
+ }=0D
+=0D
+ return EFI_UNSUPPORTED;=0D
+}=0D
+=0D
+/**=0D
+ Duplicates a file structure.=0D
+=0D
+ @param[in] Original Pointer to the original file.=0D
+=0D
+ @return Pointer to the new file structure.=0D
+ */=0D
+STATIC=0D
+EXT4_FILE *=0D
+Ext4DuplicateFile (=0D
+ IN CONST EXT4_FILE *Original=0D
+ )=0D
+{=0D
+ EXT4_PARTITION *Partition;=0D
+ EXT4_FILE *File;=0D
+=0D
+ Partition =3D Original->Partition;=0D
+ File =3D AllocateZeroPool (sizeof (EXT4_FILE));=0D
+=0D
+ if (File =3D=3D NULL) {=0D
+ return NULL;=0D
+ }=0D
+=0D
+ File->Inode =3D Ext4AllocateInode (Partition);=0D
+ if (File->Inode =3D=3D NULL) {=0D
+ FreePool (File);=0D
+ return NULL;=0D
+ }=0D
+=0D
+ CopyMem (File->Inode, Original->Inode, Partition->InodeSize);=0D
+=0D
+ File->FileName =3D AllocateZeroPool (StrSize (Original->FileName));=0D
+ if (File->FileName =3D=3D NULL) {=0D
+ FreePool (File->Inode);=0D
+ FreePool (File);=0D
+ return NULL;=0D
+ }=0D
+=0D
+ StrCpyS (File->FileName, StrLen (Original->FileName) + 1, Original->File=
Name);=0D
+=0D
+ File->Position =3D 0;=0D
+ Ext4SetupFile (File, Partition);=0D
+ File->InodeNum =3D Original->InodeNum;=0D
+ File->OpenMode =3D 0; // Will be filled by other code=0D
+=0D
+ if (!Ext4InitExtentsMap (File)) {=0D
+ FreePool (File->FileName);=0D
+ FreePool (File->Inode);=0D
+ FreePool (File);=0D
+ return NULL;=0D
+ }=0D
+=0D
+ InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);=0D
+=0D
+ return File;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/In=
ode.c
new file mode 100644
index 0000000000..304bf0c4a9
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
@@ -0,0 +1,468 @@
+/**=0D
+ @file Inode related routines=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+=0D
+ EpochToEfiTime copied from EmbeddedPkg/Library/TimeBaseLib.c=0D
+ Copyright (c) 2016, Hisilicon Limited. All rights reserved.=0D
+ Copyright (c) 2016-2019, Linaro Limited. All rights reserved.=0D
+ Copyright (c) 2021, Ampere Computing LLC. All rights reserved.=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+#include <Uefi.h>=0D
+=0D
+/**=0D
+ Calculates the checksum of the given inode.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] Inode Pointer to the inode.=0D
+ @param[in] InodeNum Inode number.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT32=0D
+Ext4CalculateInodeChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_INODE *Inode,=0D
+ IN EXT4_INO_NR InodeNum=0D
+ )=0D
+{=0D
+ UINT32 Crc;=0D
+ UINT16 Dummy;=0D
+ BOOLEAN HasSecondChecksumField;=0D
+ CONST VOID *RestOfInode;=0D
+ UINTN RestOfInodeLength;=0D
+=0D
+ HasSecondChecksumField =3D Ext4InodeHasField (Inode, i_checksum_hi);=0D
+=0D
+ Dummy =3D 0;=0D
+=0D
+ Crc =3D Ext4CalculateChecksum (Partition, &InodeNum, sizeof (InodeNum), =
Partition->InitialSeed);=0D
+ Crc =3D Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (=
Inode->i_generation), Crc);=0D
+=0D
+ Crc =3D Ext4CalculateChecksum (=0D
+ Partition,=0D
+ Inode,=0D
+ OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_checksum_lo),=0D
+ Crc=0D
+ );=0D
+=0D
+ Crc =3D Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);=
=0D
+=0D
+ RestOfInode =3D &Inode->i_osd2.data_linux.l_i_reserved;=0D
+ RestOfInodeLength =3D Partition->InodeSize - OFFSET_OF (EXT4_INODE, i_os=
d2.data_linux.l_i_reserved);=0D
+=0D
+ if (HasSecondChecksumField) {=0D
+ UINTN Length =3D OFFSET_OF (EXT4_INODE, i_checksum_hi) - OFFSET_OF (E=
XT4_INODE, i_osd2.data_linux.l_i_reserved);=0D
+=0D
+ Crc =3D Ext4CalculateChecksum (Partition, &Inode->i_osd2.data_linux.l_=
i_reserved, Length, Crc);=0D
+ Crc =3D Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc)=
;=0D
+=0D
+ // 4 is the size of the i_extra_size field + the size of i_checksum_hi=
=0D
+ RestOfInodeLength =3D Partition->InodeSize - EXT4_GOOD_OLD_INODE_SIZE =
- 4;=0D
+ RestOfInode =3D &Inode->i_ctime_extra;=0D
+ }=0D
+=0D
+ Crc =3D Ext4CalculateChecksum (Partition, RestOfInode, RestOfInodeLength=
, Crc);=0D
+=0D
+ return Crc;=0D
+}=0D
+=0D
+/**=0D
+ Reads from an EXT4 inode.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Buffer Pointer to the buffer.=0D
+ @param[in] Offset Offset of the read.=0D
+ @param[in out] Length Pointer to the length of the buffer, in b=
ytes.=0D
+ After a succesful read, it's updated to t=
he number of read bytes.=0D
+=0D
+ @return Status of the read operation.=0D
+*/=0D
+EFI_STATUS=0D
+Ext4Read (=0D
+ IN EXT4_PARTITION *Partition,=0D
+ IN EXT4_FILE *File,=0D
+ OUT VOID *Buffer,=0D
+ IN UINT64 Offset,=0D
+ IN OUT UINTN *Length=0D
+ )=0D
+{=0D
+ // DEBUG((EFI_D_INFO, "Ext4Read[Offset %lu, Length %lu]\n", Offset, *Len=
gth));=0D
+ EXT4_INODE *Inode;=0D
+ UINT64 InodeSize;=0D
+ UINT64 CurrentSeek;=0D
+ UINTN RemainingRead;=0D
+ UINTN BeenRead;=0D
+=0D
+ Inode =3D File->Inode;=0D
+ InodeSize =3D Ext4InodeSize (Inode);=0D
+ CurrentSeek =3D Offset;=0D
+ RemainingRead =3D *Length;=0D
+ BeenRead =3D 0;=0D
+=0D
+ if(Offset > InodeSize) {=0D
+ return EFI_DEVICE_ERROR;=0D
+ }=0D
+=0D
+ if (RemainingRead > InodeSize - Offset) {=0D
+ RemainingRead =3D (UINTN)(InodeSize - Offset);=0D
+ }=0D
+=0D
+ while(RemainingRead !=3D 0) {=0D
+ UINTN WasRead;=0D
+ EXT4_EXTENT Extent;=0D
+ UINT32 BlockOff;=0D
+ EFI_STATUS Status;=0D
+ BOOLEAN HasBackingExtent;=0D
+=0D
+ WasRead =3D 0;=0D
+=0D
+ // The algorithm here is to get the extent corresponding to the curren=
t block=0D
+ // and then read as much as we can from the current extent.=0D
+=0D
+ Status =3D Ext4GetExtent (=0D
+ Partition,=0D
+ File,=0D
+ DivU64x32Remainder (CurrentSeek, Partition->BlockSize, &Blo=
ckOff),=0D
+ &Extent=0D
+ );=0D
+=0D
+ if(Status !=3D EFI_SUCCESS && Status !=3D EFI_NO_MAPPING) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ HasBackingExtent =3D Status !=3D EFI_NO_MAPPING;=0D
+=0D
+ if(!HasBackingExtent) {=0D
+ UINT32 HoleOff;=0D
+ UINTN HoleLen;=0D
+=0D
+ HoleOff =3D BlockOff;=0D
+ HoleLen =3D Partition->BlockSize - HoleOff;=0D
+ WasRead =3D HoleLen > RemainingRead ? RemainingRead : HoleLen;=0D
+ // TODO: Get the hole size and memset all that=0D
+ SetMem (Buffer, WasRead, 0);=0D
+ } else {=0D
+ UINT64 ExtentStartBytes;=0D
+ UINT64 ExtentLengthBytes;=0D
+ UINT64 ExtentLogicalBytes;=0D
+=0D
+ // Our extent offset is the difference between CurrentSeek and Exten=
tLogicalBytes=0D
+ UINT64 ExtentOffset;=0D
+ UINTN ExtentMayRead;=0D
+=0D
+ ExtentStartBytes =3D (((UINT64)Extent.ee_start_hi << 32) | Extent.=
ee_start_lo) * Partition->BlockSize;=0D
+ ExtentLengthBytes =3D Extent.ee_len * Partition->BlockSize;=0D
+ ExtentLogicalBytes =3D (UINT64)Extent.ee_block * Partition->BlockSiz=
e;=0D
+ ExtentOffset =3D CurrentSeek - ExtentLogicalBytes;=0D
+ ExtentMayRead =3D (UINTN)(ExtentLengthBytes - ExtentOffset);=0D
+=0D
+ WasRead =3D ExtentMayRead > RemainingRead ? RemainingRead : ExtentMa=
yRead;=0D
+=0D
+ // DEBUG((EFI_D_INFO, "[ext4] may read %lu, remaining %lu\n", Extent=
MayRead, RemainingRead));=0D
+ // DEBUG((EFI_D_INFO, "[ext4] Reading block %lu\n", (ExtentStartByte=
s + ExtentOffset) / Partition->BlockSize));=0D
+ Status =3D Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBy=
tes + ExtentOffset);=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ DEBUG ((=0D
+ EFI_D_ERROR,=0D
+ "[ext4] Error %x reading [%lu, %lu]\n",=0D
+ Status,=0D
+ ExtentStartBytes + ExtentOffset,=0D
+ ExtentStartBytes + ExtentOffset + WasRead - 1=0D
+ ));=0D
+ return Status;=0D
+ }=0D
+ }=0D
+=0D
+ RemainingRead -=3D WasRead;=0D
+ Buffer =3D (VOID *)((CHAR8 *)Buffer + WasRead);=0D
+ BeenRead +=3D WasRead;=0D
+ CurrentSeek +=3D WasRead;=0D
+ }=0D
+=0D
+ *Length =3D BeenRead;=0D
+=0D
+ // DEBUG((EFI_D_INFO, "File length %lu crc %x\n", BeenRead, CalculateCrc=
32(original, BeenRead)));=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Allocates a zeroed inode structure.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+=0D
+ @return Pointer to the allocated structure, from the pool,=0D
+ with size Partition->InodeSize.=0D
+*/=0D
+EXT4_INODE *=0D
+Ext4AllocateInode (=0D
+ IN EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ BOOLEAN NeedsToZeroRest;=0D
+ UINT32 InodeSize;=0D
+ EXT4_INODE *Inode;=0D
+=0D
+ NeedsToZeroRest =3D FALSE;=0D
+ InodeSize =3D Partition->InodeSize;=0D
+=0D
+ // HACK!: We allocate a structure of at least sizeof(EXT4_INODE), but in=
the future, when=0D
+ // write support is added and we need to flush inodes to disk, we could =
have a bit better=0D
+ // distinction between the on-disk inode and a separate, nicer to work w=
ith inode struct.=0D
+ if (InodeSize < sizeof (EXT4_INODE)) {=0D
+ InodeSize =3D sizeof (EXT4_INODE);=0D
+ NeedsToZeroRest =3D TRUE;=0D
+ }=0D
+=0D
+ Inode =3D AllocateZeroPool (InodeSize);=0D
+=0D
+ if (!Inode) {=0D
+ return NULL;=0D
+ }=0D
+=0D
+ if (NeedsToZeroRest) {=0D
+ Inode->i_extra_isize =3D 0;=0D
+ }=0D
+=0D
+ return Inode;=0D
+}=0D
+=0D
+/**=0D
+ Checks if a file is a directory.=0D
+ @param[in] File Pointer to the opened file.=0D
+=0D
+ @return TRUE if file is a directory.=0D
+*/=0D
+BOOLEAN=0D
+Ext4FileIsDir (=0D
+ IN CONST EXT4_FILE *File=0D
+ )=0D
+{=0D
+ return (File->Inode->i_mode & EXT4_INO_TYPE_DIR) =3D=3D EXT4_INO_TYPE_DI=
R;=0D
+}=0D
+=0D
+/**=0D
+ Checks if a file is a regular file.=0D
+ @param[in] File Pointer to the opened file.=0D
+=0D
+ @return BOOLEAN TRUE if file is a regular file.=0D
+*/=0D
+BOOLEAN=0D
+Ext4FileIsReg (=0D
+ IN CONST EXT4_FILE *File=0D
+ )=0D
+{=0D
+ return (File->Inode->i_mode & EXT4_INO_TYPE_REGFILE) =3D=3D EXT4_INO_TYP=
E_REGFILE;=0D
+}=0D
+=0D
+/**=0D
+ Calculates the physical space used by a file.=0D
+ @param[in] File Pointer to the opened file.=0D
+=0D
+ @return Physical space used by a file, in bytes.=0D
+*/=0D
+UINT64=0D
+Ext4FilePhysicalSpace (=0D
+ EXT4_FILE *File=0D
+ )=0D
+{=0D
+ BOOLEAN HugeFile;=0D
+ UINT64 Blocks;=0D
+=0D
+ HugeFile =3D Ext4HasRoCompat (File->Partition, EXT4_FEATURE_RO_COMPAT_HU=
GE_FILE);=0D
+ Blocks =3D File->Inode->i_blocks;=0D
+=0D
+ if(HugeFile) {=0D
+ Blocks |=3D ((UINT64)File->Inode->i_osd2.data_linux.l_i_blocks_high) <=
< 32;=0D
+=0D
+ // If HUGE_FILE is enabled and EXT4_HUGE_FILE_FL is set in the inode's=
flags, each unit=0D
+ // in i_blocks corresponds to an actual filesystem block=0D
+ if(File->Inode->i_flags & EXT4_HUGE_FILE_FL) {=0D
+ return Blocks * File->Partition->BlockSize;=0D
+ }=0D
+ }=0D
+=0D
+ // Else, each i_blocks unit corresponds to 512 bytes=0D
+ return Blocks * 512;=0D
+}=0D
+=0D
+// Copied from EmbeddedPkg at my mentor's request.=0D
+// The lack of comments and good variable names is frightening...=0D
+=0D
+/**=0D
+ Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to =
EFI_TIME.=0D
+=0D
+ @param EpochSeconds Epoch seconds.=0D
+ @param Time The time converted to UEFI format.=0D
+=0D
+**/=0D
+STATIC=0D
+VOID=0D
+EFIAPI=0D
+EpochToEfiTime (=0D
+ IN UINTN EpochSeconds,=0D
+ OUT EFI_TIME *Time=0D
+ )=0D
+{=0D
+ UINTN a;=0D
+ UINTN b;=0D
+ UINTN c;=0D
+ UINTN d;=0D
+ UINTN g;=0D
+ UINTN j;=0D
+ UINTN m;=0D
+ UINTN y;=0D
+ UINTN da;=0D
+ UINTN db;=0D
+ UINTN dc;=0D
+ UINTN dg;=0D
+ UINTN hh;=0D
+ UINTN mm;=0D
+ UINTN ss;=0D
+ UINTN J;=0D
+=0D
+ J =3D (EpochSeconds / 86400) + 2440588;=0D
+ j =3D J + 32044;=0D
+ g =3D j / 146097;=0D
+ dg =3D j % 146097;=0D
+ c =3D (((dg / 36524) + 1) * 3) / 4;=0D
+ dc =3D dg - (c * 36524);=0D
+ b =3D dc / 1461;=0D
+ db =3D dc % 1461;=0D
+ a =3D (((db / 365) + 1) * 3) / 4;=0D
+ da =3D db - (a * 365);=0D
+ y =3D (g * 400) + (c * 100) + (b * 4) + a;=0D
+ m =3D (((da * 5) + 308) / 153) - 2;=0D
+ d =3D da - (((m + 4) * 153) / 5) + 122;=0D
+=0D
+ Time->Year =3D (UINT16)(y - 4800 + ((m + 2) / 12));=0D
+ Time->Month =3D ((m + 2) % 12) + 1;=0D
+ Time->Day =3D (UINT8)(d + 1);=0D
+=0D
+ ss =3D EpochSeconds % 60;=0D
+ a =3D (EpochSeconds - ss) / 60;=0D
+ mm =3D a % 60;=0D
+ b =3D (a - mm) / 60;=0D
+ hh =3D b % 24;=0D
+=0D
+ Time->Hour =3D (UINT8)hh;=0D
+ Time->Minute =3D (UINT8)mm;=0D
+ Time->Second =3D (UINT8)ss;=0D
+ Time->Nanosecond =3D 0;=0D
+=0D
+}=0D
+=0D
+// The time format used to (de/en)code timestamp and timestamp_extra is do=
cumented on=0D
+// the ext4 docs page in kernel.org=0D
+#define EXT4_EXTRA_TIMESTAMP_MASK ((1 << 2) - 1)=0D
+=0D
+#define EXT4_FILE_GET_TIME_GENERIC(Name, Field) \=0D
+ VOID \=0D
+ Ext4File ## Name (IN EXT4_FILE *File, OUT EFI_TIME *Time) \=0D
+ { \=0D
+ EXT4_INODE *Inode =3D File->Inode; \=0D
+ UINT64 SecondsEpoch =3D Inode->Field; \=0D
+ UINT32 Nanoseconds =3D 0; \=0D
+ \=0D
+ if (Ext4InodeHasField (Inode, Field ## _extra)) { \=0D
+ SecondsEpoch |=3D ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIME=
STAMP_MASK)) << 32; \=0D
+ Nanoseconds =3D Inode->Field ## _extra >> 2; =
\=0D
+ } =
\=0D
+ EpochToEfiTime ((UINTN)SecondsEpoch, Time); =
\=0D
+ Time->Nanosecond =3D Nanoseconds; =
\=0D
+ }=0D
+=0D
+// Note: EpochToEfiTime should be adjusted to take in a UINT64 instead of =
a UINTN, in order to avoid Y2038=0D
+// on 32-bit systems.=0D
+=0D
+/**=0D
+ Gets the file's last access time.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+*/=0D
+EXT4_FILE_GET_TIME_GENERIC (ATime, i_atime);=0D
+=0D
+/**=0D
+ Gets the file's last (data) modification time.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+*/=0D
+EXT4_FILE_GET_TIME_GENERIC (MTime, i_mtime);=0D
+=0D
+/**=0D
+ Gets the file's creation time.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+*/=0D
+STATIC=0D
+EXT4_FILE_GET_TIME_GENERIC (=0D
+ CrTime, i_crtime=0D
+ );=0D
+=0D
+/**=0D
+ Gets the file's creation time, if possible.=0D
+ @param[in] File Pointer to the opened file.=0D
+ @param[out] Time Pointer to an EFI_TIME structure.=0D
+ In the case where the the creation time isn't re=
corded,=0D
+ Time is zeroed.=0D
+*/=0D
+VOID=0D
+Ext4FileCreateTime (=0D
+ IN EXT4_FILE *File,=0D
+ OUT EFI_TIME *Time=0D
+ )=0D
+{=0D
+ EXT4_INODE *Inode;=0D
+=0D
+ Inode =3D File->Inode;=0D
+=0D
+ if (!Ext4InodeHasField (Inode, i_crtime)) {=0D
+ SetMem (Time, sizeof (EFI_TIME), 0);=0D
+ return;=0D
+ }=0D
+=0D
+ Ext4FileCrTime (File, Time);=0D
+}=0D
+=0D
+/**=0D
+ Checks if the checksum of the inode is correct.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] Inode Pointer to the inode.=0D
+ @param[in] InodeNum Inode number.=0D
+=0D
+ @return TRUE if checksum is correct, FALSE if there is corruption.=0D
+*/=0D
+BOOLEAN=0D
+Ext4CheckInodeChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition,=0D
+ IN CONST EXT4_INODE *Inode,=0D
+ IN EXT4_INO_NR InodeNum=0D
+ )=0D
+{=0D
+ UINT32 Csum;=0D
+ UINT32 DiskCsum;=0D
+=0D
+ if(!Ext4HasMetadataCsum (Partition)) {=0D
+ return TRUE;=0D
+ }=0D
+=0D
+ Csum =3D Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);=0D
+=0D
+ DiskCsum =3D Inode->i_osd2.data_linux.l_i_checksum_lo;=0D
+=0D
+ if (Ext4InodeHasField (Inode, i_checksum_hi)) {=0D
+ DiskCsum |=3D ((UINT32)Inode->i_checksum_hi) << 16;=0D
+ } else {=0D
+ // Only keep the lower bits for the comparison if the checksum is 16 b=
its.=0D
+ Csum &=3D 0xffff;=0D
+ }=0D
+=0D
+ #if 0=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Inode %d csum %x vs %x\n", InodeNum, Csum,=
DiskCsum));=0D
+ #endif=0D
+=0D
+ return Csum =3D=3D DiskCsum;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Partition.c b/Features/Ext4Pkg/Ext4Dx=
e/Partition.c
new file mode 100644
index 0000000000..a2c4c57e78
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Partition.c
@@ -0,0 +1,120 @@
+/**=0D
+ @file Driver entry point=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+/**=0D
+ Opens an ext4 partition and installs the Simple File System protocol.=0D
+=0D
+ @param[in] DeviceHandle Handle to the block device.=0D
+ @param[in] DiskIo Pointer to an EFI_DISK_IO_PROTOCOL.=
=0D
+ @param[in opt] DiskIo2 Pointer to an EFI_DISK_IO2_PROTOCOL,=
if supported.=0D
+ @param[in] BlockIo Pointer to an EFI_BLOCK_IO_PROTOCOL.=
=0D
+=0D
+ @retval EFI_SUCCESS The opening was successful.=0D
+ !EFI_SUCCESS Opening failed.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4OpenPartition (=0D
+ IN EFI_HANDLE DeviceHandle,=0D
+ IN EFI_DISK_IO_PROTOCOL *DiskIo,=0D
+ IN OPTIONAL EFI_DISK_IO2_PROTOCOL *DiskIo2,=0D
+ IN EFI_BLOCK_IO_PROTOCOL *BlockIo=0D
+ )=0D
+{=0D
+ EXT4_PARTITION *Part;=0D
+ EFI_STATUS Status;=0D
+=0D
+ Part =3D AllocateZeroPool (sizeof (*Part));=0D
+=0D
+ if(Part =3D=3D NULL) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ InitializeListHead(&Part->OpenFiles);=0D
+=0D
+ Part->BlockIo =3D BlockIo;=0D
+ Part->DiskIo =3D DiskIo;=0D
+ Part->DiskIo2 =3D DiskIo2;=0D
+=0D
+ Status =3D Ext4OpenSuperblock (Part);=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ FreePool (Part);=0D
+ return Status;=0D
+ }=0D
+=0D
+ Part->Interface.Revision =3D EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;=
=0D
+ Part->Interface.OpenVolume =3D Ext4OpenVolume;=0D
+ Status =3D gBS->InstallMultipleProtocolInterfaces (=0D
+ &DeviceHandle,=0D
+ &gEfiSimpleFileSystemProtocolGuid,=0D
+ &Part->Interface,=0D
+ NULL=0D
+ );=0D
+=0D
+ if(EFI_ERROR (Status)) {=0D
+ FreePool (Part);=0D
+ return Status;=0D
+ }=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
+=0D
+/**=0D
+ Sets up the protocol and metadata of a file that is being opened.=0D
+=0D
+ @param[in out] File Pointer to the file.=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+ */=0D
+VOID=0D
+Ext4SetupFile (=0D
+ IN OUT EXT4_FILE *File, EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ // TODO: Support revision 2 (needs DISK_IO2 + asynchronous IO)=0D
+ File->Protocol.Revision =3D EFI_FILE_PROTOCOL_REVISION;=0D
+ File->Protocol.Open =3D Ext4Open;=0D
+ File->Protocol.Close =3D Ext4Close;=0D
+ File->Protocol.Delete =3D Ext4Delete;=0D
+ File->Protocol.Read =3D Ext4ReadFile;=0D
+ File->Protocol.Write =3D Ext4WriteFile;=0D
+ File->Protocol.SetPosition =3D Ext4SetPosition;=0D
+ File->Protocol.GetPosition =3D Ext4GetPosition;=0D
+ File->Protocol.GetInfo =3D Ext4GetInfo;=0D
+=0D
+ File->Partition =3D Partition;=0D
+}=0D
+=0D
+/**=0D
+ Unmounts and frees an ext4 partition.=0D
+=0D
+ @param[in] Partition Pointer to the opened partition.=0D
+=0D
+ @retval Status of the unmount.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4UnmountAndFreePartition (=0D
+ IN EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ LIST_ENTRY *Entry;=0D
+ LIST_ENTRY *NextEntry;=0D
+ Partition->Unmounting =3D TRUE;=0D
+ Ext4CloseInternal (Partition->Root);=0D
+=0D
+ BASE_LIST_FOR_EACH_SAFE(Entry, NextEntry, &Partition->OpenFiles) {=0D
+ EXT4_FILE *File =3D Ext4FileFromOpenFileNode(Entry);=0D
+=0D
+ Ext4CloseInternal(File);=0D
+ }=0D
+=0D
+ FreePool (Partition->BlockGroups);=0D
+ FreePool (Partition);=0D
+=0D
+ return EFI_SUCCESS;=0D
+}=0D
diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4D=
xe/Superblock.c
new file mode 100644
index 0000000000..18d8295a1f
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
@@ -0,0 +1,257 @@
+/**=0D
+ @file Superblock managing routines=0D
+=0D
+ Copyright (c) 2021 Pedro Falcato All rights reserved.=0D
+ SPDX-License-Identifier: BSD-2-Clause-Patent=0D
+ */=0D
+=0D
+#include "Ext4Dxe.h"=0D
+=0D
+STATIC CONST UINT32 gSupportedCompatFeat =3D EXT4_FEATURE_COMPAT_EXT_ATTR=
;=0D
+=0D
+STATIC CONST UINT32 gSupportedRoCompatFeat =3D=0D
+ EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE |=
=0D
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE | EXT4_FEATURE_RO_COMPAT_LARGE_FILE |=0D
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM |=
EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER;=0D
+// TODO: Add btree support=0D
+STATIC CONST UINT32 gSupportedIncompatFeat =3D=0D
+ EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_DIRDATA |=0D
+ EXT4_FEATURE_INCOMPAT_FLEX_BG | EXT4_FEATURE_INCOMPAT_FILETYPE |=0D
+ EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_LARGEDIR |=0D
+ EXT4_FEATURE_INCOMPAT_MMP;=0D
+=0D
+// TODO: Add meta_bg support=0D
+=0D
+// Note: We ignore MMP because it's impossible that it's mapped elsewhere,=
=0D
+// I think (unless there's some sort of network setup where we're accessin=
g a remote partition).=0D
+=0D
+BOOLEAN=0D
+Ext4SuperblockValidate (=0D
+ EXT4_SUPERBLOCK *sb=0D
+ )=0D
+{=0D
+ if(sb->s_magic !=3D EXT4_SIGNATURE) {=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ // TODO: We should try to support EXT2/3 partitions too=0D
+ if(sb->s_rev_level !=3D EXT4_DYNAMIC_REV && sb->s_rev_level !=3D EXT4_GO=
OD_OLD_REV) {=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ // TODO: Is this correct behaviour? Imagine the power cuts out, should t=
he system fail to boot because=0D
+ // we're scared of touching something corrupt?=0D
+ if((sb->s_state & EXT4_FS_STATE_UNMOUNTED) =3D=3D 0) {=0D
+ return FALSE;=0D
+ }=0D
+=0D
+ return TRUE;=0D
+}=0D
+=0D
+STATIC UINT32=0D
+Ext4CalculateSuperblockChecksum (=0D
+ EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb=0D
+ )=0D
+{=0D
+ // Most checksums require us to go through a dummy 0 as part of the requ=
irement=0D
+ // that the checksum is done over a structure with its checksum field =
=3D 0.=0D
+ UINT32 Checksum =3D Ext4CalculateChecksum (=0D
+ Partition,=0D
+ sb,=0D
+ OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),=0D
+ ~0U=0D
+ );=0D
+=0D
+ return Checksum;=0D
+}=0D
+=0D
+STATIC BOOLEAN=0D
+Ext4VerifySuperblockChecksum (=0D
+ EXT4_PARTITION *Partition, CONST EXT4_SUPERBLOCK *sb=0D
+ )=0D
+{=0D
+ if(!Ext4HasMetadataCsum (Partition)) {=0D
+ return TRUE;=0D
+ }=0D
+=0D
+ return sb->s_checksum =3D=3D Ext4CalculateSuperblockChecksum (Partition,=
sb);=0D
+}=0D
+=0D
+/**=0D
+ Opens and parses the superblock.=0D
+=0D
+ @param[out] Partition Partition structure to fill with filesystem d=
etails.=0D
+ @retval EFI_SUCCESS Parsing was succesful and the partition is a=
=0D
+ valid ext4 partition.=0D
+ */=0D
+EFI_STATUS=0D
+Ext4OpenSuperblock (=0D
+ OUT EXT4_PARTITION *Partition=0D
+ )=0D
+{=0D
+ UINT32 Index;=0D
+ EFI_STATUS Status;=0D
+ EXT4_SUPERBLOCK *Sb;=0D
+ UINT32 NrBlocksRem;=0D
+ UINTN NrBlocks;=0D
+ UINT32 UnsupportedRoCompat;=0D
+=0D
+ Status =3D Ext4ReadDiskIo (=0D
+ Partition,=0D
+ &Partition->SuperBlock,=0D
+ sizeof (EXT4_SUPERBLOCK),=0D
+ EXT4_SUPERBLOCK_OFFSET=0D
+ );=0D
+=0D
+ if (EFI_ERROR (Status)) {=0D
+ return Status;=0D
+ }=0D
+=0D
+ Sb =3D &Partition->SuperBlock;=0D
+=0D
+ if (!Ext4SuperblockValidate (Sb)) {=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ if (Sb->s_rev_level =3D=3D EXT4_DYNAMIC_REV) {=0D
+ Partition->FeaturesCompat =3D Sb->s_feature_compat;=0D
+ Partition->FeaturesIncompat =3D Sb->s_feature_incompat;=0D
+ Partition->FeaturesRoCompat =3D Sb->s_feature_ro_compat;=0D
+ Partition->InodeSize =3D Sb->s_inode_size;=0D
+ } else {=0D
+ // GOOD_OLD_REV=0D
+ Partition->FeaturesCompat =3D Partition->FeaturesIncompat =3D Partitio=
n->FeaturesRoCompat =3D 0;=0D
+ Partition->InodeSize =3D EXT4_GOOD_OLD_INODE_SIZE;=0D
+ }=0D
+=0D
+ // Now, check for the feature set of the filesystem=0D
+ // It's essential to check for this to avoid filesystem corruption and t=
o avoid=0D
+ // accidentally opening an ext2/3/4 filesystem we don't understand, whic=
h would be disasterous.=0D
+=0D
+ if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {=0D
+ DEBUG ((EFI_D_INFO, "[Ext4] Unsupported %lx\n", Partition->FeaturesInc=
ompat & ~gSupportedIncompatFeat));=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ // This should be removed once we add ext2/3 support in the future.=0D
+ if ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_EXTENTS) =3D=3D=
0) {=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ // At the time of writing, it's the only supported checksum.=0D
+ if (Partition->FeaturesCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM &&=
=0D
+ Sb->s_checksum_type !=3D EXT4_CHECKSUM_CRC32C) {=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ if (Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_CSUM_SEED) {=0D
+ Partition->InitialSeed =3D Sb->s_checksum_seed;=0D
+ } else {=0D
+ Partition->InitialSeed =3D Ext4CalculateChecksum (Partition, Sb->s_uui=
d, 16, ~0U);=0D
+ }=0D
+=0D
+ UnsupportedRoCompat =3D Partition->FeaturesRoCompat & ~gSupportedRoCompa=
tFeat;=0D
+=0D
+ if (UnsupportedRoCompat !=3D 0) {=0D
+ DEBUG ((EFI_D_INFO, "[Ext4] Unsupported ro compat %x\n", UnsupportedRo=
Compat));=0D
+ Partition->ReadOnly =3D TRUE;=0D
+ }=0D
+=0D
+ (VOID)gSupportedCompatFeat;=0D
+=0D
+ DEBUG ((EFI_D_INFO, "Read only =3D %u\n", Partition->ReadOnly));=0D
+=0D
+ Partition->BlockSize =3D 1024 << Sb->s_log_block_size;=0D
+=0D
+ // The size of a block group can also be calculated as 8 * Partition->Bl=
ockSize=0D
+ if(Sb->s_blocks_per_group !=3D 8 * Partition->BlockSize) {=0D
+ return EFI_UNSUPPORTED;=0D
+ }=0D
+=0D
+ Partition->NumberBlocks =3D Ext4MakeBlockNumberFromHalfs (Partition, Sb-=
s_blocks_count, Sb->s_blocks_count_hi);=0D
+ Partition->NumberBlockGroups =3D DivU64x32 (Partition->NumberBlocks, Sb-=
s_blocks_per_group);=0D
+=0D
+ DEBUG ((=0D
+ EFI_D_INFO,=0D
+ "[ext4] Number of blocks =3D %lu\n[ext4] Number of block groups: %lu\n=
",=0D
+ Partition->NumberBlocks,=0D
+ Partition->NumberBlockGroups=0D
+ ));=0D
+=0D
+ if (Ext4Is64Bit (Partition)) {=0D
+ Partition->DescSize =3D Sb->s_desc_size;=0D
+ } else {=0D
+ Partition->DescSize =3D EXT4_OLD_BLOCK_DESC_SIZE;=0D
+ }=0D
+=0D
+ if (Partition->DescSize < EXT4_64BIT_BLOCK_DESC_SIZE && Ext4Is64Bit (Par=
tition)) {=0D
+ // 64 bit filesystems need DescSize to be 64 bytes=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {=0D
+ DEBUG ((EFI_D_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4Calcu=
lateSuperblockChecksum (Partition, Sb)));=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+=0D
+ NrBlocks =3D (UINTN)DivU64x32Remainder (=0D
+ Partition->NumberBlockGroups * Partition->De=
scSize,=0D
+ Partition->BlockSize,=0D
+ &NrBlocksRem=0D
+ );=0D
+=0D
+ if(NrBlocksRem !=3D 0) {=0D
+ NrBlocks++;=0D
+ }=0D
+=0D
+ Partition->BlockGroups =3D Ext4AllocAndReadBlocks (Partition, NrBlocks, =
Partition->BlockSize =3D=3D 1024 ? 2 : 1);=0D
+=0D
+ if (!Partition->BlockGroups) {=0D
+ return EFI_OUT_OF_RESOURCES;=0D
+ }=0D
+=0D
+ for (Index =3D 0; Index < Partition->NumberBlockGroups; Index++) {=0D
+ EXT4_BLOCK_GROUP_DESC *Desc;=0D
+ =0D
+ Desc =3D Ext4GetBlockGroupDesc (Partition, Index);=0D
+ if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Block group descriptor %u has an invalid=
checksum\n", Index));=0D
+ return EFI_VOLUME_CORRUPTED;=0D
+ }=0D
+ }=0D
+=0D
+ // Note that the cast below is completely safe, because EXT4_FILE is a s=
pecialisation of EFI_FILE_PROTOCOL=0D
+ Status =3D Ext4OpenVolume (&Partition->Interface, (EFI_FILE_PROTOCOL **)=
&Partition->Root);=0D
+=0D
+ DEBUG ((EFI_D_INFO, "[ext4] Root File %p\n", Partition->Root));=0D
+ return Status;=0D
+}=0D
+=0D
+/**=0D
+ Calculates the checksum of the given buffer.=0D
+ @param[in] Partition Pointer to the opened EXT4 partition.=0D
+ @param[in] Buffer Pointer to the buffer.=0D
+ @param[in] Length Length of the buffer, in bytes.=0D
+ @param[in] InitialValue Initial value of the CRC.=0D
+=0D
+ @return The checksum.=0D
+*/=0D
+UINT32=0D
+Ext4CalculateChecksum (=0D
+ IN CONST EXT4_PARTITION *Partition, IN CONST VOID *Buffer, IN UINTN Leng=
th,=0D
+ IN UINT32 InitialValue=0D
+ )=0D
+{=0D
+ if(!Ext4HasMetadataCsum (Partition)) {=0D
+ return 0;=0D
+ }=0D
+=0D
+ switch(Partition->SuperBlock.s_checksum_type) {=0D
+ case EXT4_CHECKSUM_CRC32C:=0D
+ // For some reason, EXT4 really likes non-inverted CRC32C checksums,=
so we stick to that here.=0D
+ return ~CalculateCrc32c(Buffer, Length, ~InitialValue);=0D
+ default:=0D
+ UNREACHABLE ();=0D
+ return 0;=0D
+ }=0D
+}=0D
--=20
2.32.0

3781 - 3800 of 82171