public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [Patch 0/3] Ext4Pkg: Add Ext4Pkg
@ 2021-07-30 16:16 Pedro Falcato
  2021-07-30 16:16 ` [Patch 1/3] Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni Pedro Falcato
                   ` (5 more replies)
  0 siblings, 6 replies; 11+ messages in thread
From: Pedro Falcato @ 2021-07-30 16:16 UTC (permalink / raw)
  To: devel; +Cc: Pedro Falcato, Leif Lindholm, Michael D Kinney, Bret Barkelew

This patch-set adds Ext4Pkg, a package designed to hold various drivers and
utilities related to the EXT4 filesystem.

Right now, it holds a single read-only UEFI EXT4 driver (Ext4Dxe), which consumes the
DISK_IO, BLOCK_IO and DISK_IO2 protocols and produce EFI_FILE_PROTOCOL and
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; this driver allows the mounting of EXT4 partitions and
the reading of their contents.

Relevant RFC discussion, which includes a more in-depth walkthrough of EXT4 internals and
driver limitations is available at https://edk2.groups.io/g/devel/topic/84368561.

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

Pedro Falcato (3):
  Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
  Ext4Pkg: Add Ext4Dxe driver.
  Ext4Pkg: Add .DSC file.

 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 +++++++
 Features/Ext4Pkg/Ext4Pkg.dec          |  17 +
 Features/Ext4Pkg/Ext4Pkg.dsc          |  68 ++
 Features/Ext4Pkg/Ext4Pkg.uni          |  14 +
 19 files changed, 5250 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
 create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
 create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc
 create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni

-- 
2.32.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Patch 1/3] Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
  2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
@ 2021-07-30 16:16 ` Pedro Falcato
  2021-07-30 16:16 ` [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver Pedro Falcato
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Pedro Falcato @ 2021-07-30 16:16 UTC (permalink / raw)
  To: devel; +Cc: Pedro Falcato, Leif Lindholm, Michael D Kinney, Bret Barkelew

These files are needed to build Ext4Pkg.

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/Ext4Pkg.dec | 17 +++++++++++++++++
 Features/Ext4Pkg/Ext4Pkg.uni | 14 ++++++++++++++
 2 files changed, 31 insertions(+)
 create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
 create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni

diff --git a/Features/Ext4Pkg/Ext4Pkg.dec b/Features/Ext4Pkg/Ext4Pkg.dec
new file mode 100644
index 0000000000..f1f8b39c3c
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Pkg.dec
@@ -0,0 +1,17 @@
+## @file
+#  Ext4 Package
+#
+#  This package provides libraries and drivers related to the ext4 filesystem implementation.
+#  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
+#
+##
+
+[Defines]
+  DEC_SPECIFICATION              = 0x00010005
+  PACKAGE_NAME                   = Ext4Pkg
+  PACKAGE_UNI_FILE               = Ext4Pkg.uni
+  PACKAGE_GUID                   = 6B4BF998-668B-46D3-BCFA-971F99F8708C
+  PACKAGE_VERSION                = 0.1
diff --git a/Features/Ext4Pkg/Ext4Pkg.uni b/Features/Ext4Pkg/Ext4Pkg.uni
new file mode 100644
index 0000000000..abeadd8fd9
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Pkg.uni
@@ -0,0 +1,14 @@
+## @file
+#  Ext4 Package
+#
+#  This package provides libraries and drivers related to the ext4 filesystem implementation.
+#  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_PACKAGE_ABSTRACT            #language en-US "Module implementations for the EXT4 file system"
+
+#string STR_PACKAGE_DESCRIPTION         #language en-US "This package contains UEFI drivers and libraries for the EXT4 file system."
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver.
  2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
  2021-07-30 16:16 ` [Patch 1/3] Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni Pedro Falcato
@ 2021-07-30 16:16 ` Pedro Falcato
  2021-07-30 20:28   ` [edk2-devel] " Marvin Häuser
  2021-07-30 16:16 ` [Patch 3/3] Ext4Pkg: Add .DSC file Pedro Falcato
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 11+ messages in thread
From: Pedro Falcato @ 2021-07-30 16:16 UTC (permalink / raw)
  To: devel; +Cc: Pedro Falcato, Leif Lindholm, Michael D Kinney, Bret Barkelew

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);
+
+  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.
+  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.
+
+  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;
+
+    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);
+
+      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.
+        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];
+
+      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;
+
+        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);
+
+  File->InodeNum = Entry->inode;
+
+  Ext4SetupFile (File, (EXT4_PARTITION *)Partition);
+
+  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))) {
+    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);
+
+  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));
+    return FALSE;
+  }
+
+  // Dirent sizes need to be 4 byte aligned
+  if (Dirent->rec_len % 4) {
+    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"..")) {
+      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,
+  OUT VOID *Buffer,
+  IN UINTN NumberBlocks,
+  IN EXT4_BLOCK_NR BlockNumber
+  )
+{
+  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, BlockNumber * Partition->BlockSize);
+}
+
+/**
+   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;
+  }
+
+  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));
+
+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
+  );
+
+/**
+   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;
+
+/**
+   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);
+}
+
+/**
+   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;
+}
+
+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) {
+    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 ();
+      return 0;
+  }
+}
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [Patch 3/3] Ext4Pkg: Add .DSC file.
  2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
  2021-07-30 16:16 ` [Patch 1/3] Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni Pedro Falcato
  2021-07-30 16:16 ` [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver Pedro Falcato
@ 2021-07-30 16:16 ` Pedro Falcato
  2021-08-05 15:36 ` [Patch 0/3] Ext4Pkg: Add Ext4Pkg Michael D Kinney
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Pedro Falcato @ 2021-07-30 16:16 UTC (permalink / raw)
  To: devel; +Cc: Pedro Falcato, Leif Lindholm, Michael D Kinney, Bret Barkelew

This file is required to build Ext4Pkg.

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/Ext4Pkg.dsc | 68 ++++++++++++++++++++++++++++++++++++
 1 file changed, 68 insertions(+)
 create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc

diff --git a/Features/Ext4Pkg/Ext4Pkg.dsc b/Features/Ext4Pkg/Ext4Pkg.dsc
new file mode 100644
index 0000000000..62cb4e69cf
--- /dev/null
+++ b/Features/Ext4Pkg/Ext4Pkg.dsc
@@ -0,0 +1,68 @@
+## @file
+#  Ext4 Package
+#
+#  This package provides libraries and drivers related to the ext4 filesystem implementation.
+#  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
+#
+##
+
+
+[Defines]
+  PLATFORM_NAME                  = Ext4
+  PLATFORM_GUID                  = 6B4BF998-668B-46D3-BCFA-971F99F8708C
+  PLATFORM_VERSION               = 0.1
+  DSC_SPECIFICATION              = 0x00010005
+  SUPPORTED_ARCHITECTURES        = IA32|X64|EBC|ARM|AARCH64|RISCV64
+  OUTPUT_DIRECTORY               = Build/Ext4Pkg
+  BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
+  SKUID_IDENTIFIER               = DEFAULT
+
+[BuildOptions]
+  *_*_*_CC_FLAGS                       = -D DISABLE_NEW_DEPRECATED_INTERFACES
+
+[LibraryClasses]
+  #
+  # Entry Point Libraries
+  #
+  UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
+  #
+  # Common Libraries
+  #
+  BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
+  BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
+  UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
+  PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
+  PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
+  MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
+  UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
+  UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
+  DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
+  DebugPrintErrorLevelLib|MdePkg/Library/BaseDebugPrintErrorLevelLib/BaseDebugPrintErrorLevelLib.inf
+  DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
+  OrderedCollectionLib|MdePkg/Library/BaseOrderedCollectionRedBlackTreeLib/BaseOrderedCollectionRedBlackTreeLib.inf
+  BaseUcs2Utf8Lib|RedfishPkg/Library/BaseUcs2Utf8Lib/BaseUcs2Utf8Lib.inf
+
+###################################################################################################
+#
+# Components Section - list of the modules and components that will be processed by compilation
+#                      tools and the EDK II tools to generate PE32/PE32+/Coff image files.
+#
+# Note: The EDK II DSC file is not used to specify how compiled binary images get placed
+#       into firmware volume images. This section is just a list of modules to compile from
+#       source into UEFI-compliant binaries.
+#       It is the FDF file that contains information on combining binary files into firmware
+#       volume images, whose concept is beyond UEFI and is described in PI specification.
+#       Binary modules do not need to be listed in this section, as they should be
+#       specified in the FDF file. For example: Shell binary (Shell_Full.efi), FAT binary (Fat.efi),
+#       Logo (Logo.bmp), and etc.
+#       There may also be modules listed in this section that are not required in the FDF file,
+#       When a module listed here is excluded from FDF file, then UEFI-compliant binary will be
+#       generated for it, but the binary will not be put into any firmware volume.
+#
+###################################################################################################
+
+[Components]
+  Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [edk2-devel] [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver.
  2021-07-30 16:16 ` [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver Pedro Falcato
@ 2021-07-30 20:28   ` Marvin Häuser
  2021-08-02 15:56     ` Pedro Falcato
  0 siblings, 1 reply; 11+ messages in thread
From: Marvin Häuser @ 2021-07-30 20:28 UTC (permalink / raw)
  To: devel, pedro.falcato; +Cc: Leif Lindholm, Michael D Kinney, Bret Barkelew

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;
> +  }
> +}


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [edk2-devel] [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver.
  2021-07-30 20:28   ` [edk2-devel] " Marvin Häuser
@ 2021-08-02 15:56     ` Pedro Falcato
  0 siblings, 0 replies; 11+ messages in thread
From: Pedro Falcato @ 2021-08-02 15:56 UTC (permalink / raw)
  To: Marvin Häuser
  Cc: edk2-devel-groups-io, Leif Lindholm, Michael D Kinney,
	Bret Barkelew

Hi Marvin,

Thanks for the quick feedback! Most of your criticism seems valid.
I'll take it into consideration for v2 of the patch-set.

As for the nitpick in Collation.c, while I do understand what you mean
by that, large parts of that file were mostly borrowed from FatPkg
(such that I kept the original Intel copyright header); I think
separating that code into a library of some sorts would be a good
idea, I was completely lost when
trying to figure out all the tiny Unicode/language details that don't
seem to be documented anywhere (I think!). As for the
EFI_OPEN_PROTOCOL_GET_PROTOCOL,
I should have RTFM'd harder :)

Best regards,

Pedro

On Fri, 30 Jul 2021 at 21:28, Marvin Häuser <mhaeuser@posteo.de> wrote:
>
> 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;
> > +  }
> > +}
>


-- 
Pedro Falcato

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
  2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
                   ` (2 preceding siblings ...)
  2021-07-30 16:16 ` [Patch 3/3] Ext4Pkg: Add .DSC file Pedro Falcato
@ 2021-08-05 15:36 ` Michael D Kinney
  2021-08-05 16:02 ` Michael D Kinney
  2021-08-05 18:33 ` Michael D Kinney
  5 siblings, 0 replies; 11+ messages in thread
From: Michael D Kinney @ 2021-08-05 15:36 UTC (permalink / raw)
  To: Pedro Falcato, devel@edk2.groups.io, Kinney, Michael D
  Cc: Leif Lindholm, Bret Barkelew

Hi Pedro,

When I run BaseTools/Scripts/Patchcheck.py on this patch series, I see the following issues:


Checking git commit: 5f0db07586
Ext4Pkg: Add Ext4Dxe driver.
The commit message format passed all checks.
Code format is not valid:
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:     DEBUG ((EFI_D_ERROR, "[ext4] Could not open root inode - status %x\n", Status));
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:     DEBUG ((EFI_D_ERROR, "[ext4] dirent size %lu too small (compared to %lu)\n", Dirent->rec_len, RequiredSize));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:   DEBUG ((EFI_D_INFO, "[ext4] Ext4ReadDir offset %lu\n", Offset));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:       DEBUG ((EFI_D_INFO, "[ext4] Length read %lu, offset %lu\n", Len, Offset));
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:       DEBUG ((EFI_D_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:     DEBUG ((EFI_D_INFO, "[ext4] dirent size %lu\n", Entry.rec_len));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Directory.c
   Line:       DEBUG ((EFI_D_INFO, "[ext4] Listing file %s\n", TempFile->FileName));
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
   Line:
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
   Line:   DEBUG ((EFI_D_INFO, "[Ext4] Binding to controller\n"));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
   Line:   DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO\n"));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
   Line:     DEBUG ((EFI_D_INFO, "[Ext4] Controller supports DISK_IO2\n"));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
   Line:   DEBUG ((EFI_D_INFO, "Opening partition\n"));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
   Line:   DEBUG ((EFI_D_INFO, "[ext4] Error mounting %x\n", Status));
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
   Line: #
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Extents.c
   Line:     DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header depth %u\n", Header->eh_depth));
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Extents.c
   Line:     DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent header magic %x\n", Header->eh_magic));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Extents.c
   Line:   DEBUG ((EFI_D_INFO, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Extents.c
   Line:       DEBUG ((EFI_D_ERROR, "[ext4] Invalid extent checksum\n"));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Extents.c
   Line:   /* DEBUG((EFI_D_INFO, "[ext4] extent 1 %u extent 2 %u = %ld\n", Extent1->ee_block,
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Extents.c
   Line:   // DEBUG((EFI_D_INFO, "[ext4] comparing %u %u\n", Block, Extent->ee_block));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:   DEBUG ((EFI_D_INFO, "[ext4] Ext4Open %s\n", FileName));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:     DEBUG ((EFI_D_INFO, "[ext4] Opening %s\n", PathSegment));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:   DEBUG ((EFI_D_INFO, "Open successful\n"));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:   DEBUG ((EFI_D_INFO, "Opened filename %s\n", Current->FileName));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:   DEBUG ((EFI_D_INFO, "[ext4] Closed file %p (inode %lu)\n", File, File->InodeNum));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:     DEBUG ((EFI_D_INFO, "[ext4] ReadDir status %lx\n", Status));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/File.c
   Line:       DEBUG ((EFI_D_INFO, "[ext4] ReadDir retlen %lu\n", *BufferSize));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Inode.c
   Line:   // DEBUG((EFI_D_INFO, "Ext4Read[Offset %lu, Length %lu]\n", Offset, *Length));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Inode.c
   Line:       // DEBUG((EFI_D_INFO, "[ext4] may read %lu, remaining %lu\n", ExtentMayRead, RemainingRead));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Inode.c
   Line:       // DEBUG((EFI_D_INFO, "[ext4] Reading block %lu\n", (ExtentStartBytes + ExtentOffset) / Partition->BlockSize));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Inode.c
   Line:   // DEBUG((EFI_D_INFO, "File length %lu crc %x\n", BeenRead, CalculateCrc32(original, BeenRead)));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Inode.c
   Line:     DEBUG ((EFI_D_INFO, "[ext4] Inode %d csum %x vs %x\n", InodeNum, Csum, DiskCsum));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:     DEBUG ((EFI_D_INFO, "[Ext4] Unsupported %lx\n", Partition->FeaturesIncompat & ~gSupportedIncompatFeat));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:     DEBUG ((EFI_D_INFO, "[Ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:   DEBUG ((EFI_D_INFO, "Read only = %u\n", Partition->ReadOnly));
 * EFI_D_ERROR was used, but DEBUG_ERROR is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:     DEBUG ((EFI_D_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
 * Trailing whitespace found
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:       DEBUG ((EFI_D_INFO, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
 * EFI_D_INFO was used, but DEBUG_INFO is now recommended
   File: Features/Ext4Pkg/Ext4Dxe/Superblock.c
   Line:   DEBUG ((EFI_D_INFO, "[ext4] Root File %p\n", Partition->Root));

Best regards,

Mike


> -----Original Message-----
> From: Pedro Falcato <pedro.falcato@gmail.com>
> Sent: Friday, July 30, 2021 9:17 AM
> To: devel@edk2.groups.io
> Cc: Pedro Falcato <pedro.falcato@gmail.com>; Leif Lindholm <leif@nuviainc.com>; Kinney, Michael D
> <michael.d.kinney@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
> Subject: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
> 
> This patch-set adds Ext4Pkg, a package designed to hold various drivers and
> utilities related to the EXT4 filesystem.
> 
> Right now, it holds a single read-only UEFI EXT4 driver (Ext4Dxe), which consumes the
> DISK_IO, BLOCK_IO and DISK_IO2 protocols and produce EFI_FILE_PROTOCOL and
> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; this driver allows the mounting of EXT4 partitions and
> the reading of their contents.
> 
> Relevant RFC discussion, which includes a more in-depth walkthrough of EXT4 internals and
> driver limitations is available at https://edk2.groups.io/g/devel/topic/84368561.
> 
> Cc: Leif Lindholm <leif@nuviainc.com>
> Cc: Michael D Kinney <michael.d.kinney@intel.com>
> Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
> 
> Pedro Falcato (3):
>   Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
>   Ext4Pkg: Add Ext4Dxe driver.
>   Ext4Pkg: Add .DSC file.
> 
>  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 +++++++
>  Features/Ext4Pkg/Ext4Pkg.dec          |  17 +
>  Features/Ext4Pkg/Ext4Pkg.dsc          |  68 ++
>  Features/Ext4Pkg/Ext4Pkg.uni          |  14 +
>  19 files changed, 5250 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
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni
> 
> --
> 2.32.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
  2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
                   ` (3 preceding siblings ...)
  2021-08-05 15:36 ` [Patch 0/3] Ext4Pkg: Add Ext4Pkg Michael D Kinney
@ 2021-08-05 16:02 ` Michael D Kinney
  2021-08-05 18:33 ` Michael D Kinney
  5 siblings, 0 replies; 11+ messages in thread
From: Michael D Kinney @ 2021-08-05 16:02 UTC (permalink / raw)
  To: Pedro Falcato, devel@edk2.groups.io, Kinney, Michael D
  Cc: Leif Lindholm, Bret Barkelew

Hi Pedro,

There is a build failure when building the Ext4Pkg.dsc file for a missing RegisterFilerLib mapping.

The correct fix for this issue is to add the following !include statement to the DSC file after the
the [Defines] section.

!include MdePkg/MdeLibs.dsc.inc

With this one change, the X64 VS2019 builds pass.

There is an additional issue for IA32 VS2019 NOOPT builds.

build -a IA32 -n 5 -t VS2019 -p Features\Ext4Pkg\Ext4Pkg.dsc -b NOOPT

Ext4.lib(DiskUtil.obj) : error LNK2001: unresolved external symbol __allmul
Ext4.lib(File.obj) : error LNK2001: unresolved external symbol __allmul
Ext4.lib(Inode.obj) : error LNK2001: unresolved external symbol __allmul
Ext4.lib(BlockGroup.obj) : error LNK2001: unresolved external symbol __allmul
Ext4.lib(Superblock.obj) : error LNK2001: unresolved external symbol __allmul
Ext4.lib(BlockGroup.obj) : error LNK2001: unresolved external symbol __allshl
Ext4.lib(Superblock.obj) : error LNK2001: unresolved external symbol __allshl
Ext4.lib(File.obj) : error LNK2001: unresolved external symbol __allshl
Ext4.lib(Extents.obj) : error LNK2001: unresolved external symbol __allshl
Ext4.lib(Directory.obj) : error LNK2001: unresolved external symbol __allshl
Ext4.lib(Inode.obj) : error LNK2001: unresolved external symbol __allshl

These are usually caused by doing 64-bit math operations when building for IA32.
There are BaseLib functions that do 64-bit path for multiply and left shift that
need to be used when operands are of type INT64 or UINT64.

From the disasm, the following functions are doing 64bit multiple (__allmull)
_Ext4GetFilesystemInfo:
_Ext4FilePhysicalSpace:
_Ext4Read:
_Ext4BlockToByteOffset:
_Ext4ReadInode:
_Ext4OpenSuperblock:
_Ext4ReadBlocks:


From the disasm, the following functions are doing 64-bit shift or power of 2 multiply (__allshl)

_Ext4GetFileInfo:
_Ext4MakeBlockNumberFromHalfs:
_Ext4SetPosition:
_Ext4ExtentIdxLeafBlock:
_Ext4InodeSize:
_Ext4RetrieveDirent:
_Ext4FileATime:
_Ext4FileCrTime:
_Ext4FileMTime:
_Ext4FilePhysicalSpace:
_Ext4InodeSize:
_Ext4Read:
_Ext4MakeBlockNumberFromHalfs:
_Ext4MakeBlockNumberFromHalfs:

For example, Ext4GetFileInfo, uses EXT4_INODE_SIZE() macro, which does a 32-bit shift:

    ext4disk.h:#define EXT4_INODE_SIZE (ino)  (((UINT64)ino->i_size_hi << 32) | ino->i_size_lo)

This can be changes to use the BaseLib function:

    UINT64
    EFIAPI
    LShiftU64 (
      IN      UINT64                    Operand,
      IN      UINTN                     Count
      );

    ext4disk.h:#define EXT4_INODE_SIZE (ino)  ((LShiftU64 (no->i_size_hi, 32) | ino->i_size_lo)

Best regards,

Mike

> -----Original Message-----
> From: Pedro Falcato <pedro.falcato@gmail.com>
> Sent: Friday, July 30, 2021 9:17 AM
> To: devel@edk2.groups.io
> Cc: Pedro Falcato <pedro.falcato@gmail.com>; Leif Lindholm <leif@nuviainc.com>; Kinney, Michael D
> <michael.d.kinney@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
> Subject: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
> 
> This patch-set adds Ext4Pkg, a package designed to hold various drivers and
> utilities related to the EXT4 filesystem.
> 
> Right now, it holds a single read-only UEFI EXT4 driver (Ext4Dxe), which consumes the
> DISK_IO, BLOCK_IO and DISK_IO2 protocols and produce EFI_FILE_PROTOCOL and
> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; this driver allows the mounting of EXT4 partitions and
> the reading of their contents.
> 
> Relevant RFC discussion, which includes a more in-depth walkthrough of EXT4 internals and
> driver limitations is available at https://edk2.groups.io/g/devel/topic/84368561.
> 
> Cc: Leif Lindholm <leif@nuviainc.com>
> Cc: Michael D Kinney <michael.d.kinney@intel.com>
> Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
> 
> Pedro Falcato (3):
>   Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
>   Ext4Pkg: Add Ext4Dxe driver.
>   Ext4Pkg: Add .DSC file.
> 
>  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 +++++++
>  Features/Ext4Pkg/Ext4Pkg.dec          |  17 +
>  Features/Ext4Pkg/Ext4Pkg.dsc          |  68 ++
>  Features/Ext4Pkg/Ext4Pkg.uni          |  14 +
>  19 files changed, 5250 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
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni
> 
> --
> 2.32.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
  2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
                   ` (4 preceding siblings ...)
  2021-08-05 16:02 ` Michael D Kinney
@ 2021-08-05 18:33 ` Michael D Kinney
  2021-08-05 22:50   ` Pedro Falcato
  5 siblings, 1 reply; 11+ messages in thread
From: Michael D Kinney @ 2021-08-05 18:33 UTC (permalink / raw)
  To: Pedro Falcato, devel@edk2.groups.io, Kinney, Michael D
  Cc: Leif Lindholm, Bret Barkelew

Hi Pedro,

1) Ext4Pkg/Ext4Dxe/Ext4Dxe.inf:
  
  * To be consistent with other drivers, BASE_NAME should be changed from Ext4 to Ext4Dxe.
  * For proper dependency checking in incremental builds, please add the .h files to the [Sources] section

      Ext4Disk.h
      Ext4Dxe.h

2) There are a number of code style issues that need to be addressed.  Can you fix those for V2?

3) I did a quick pass to find the IA32 NOOPT VS2019 issues.  With the following changes, I can get it to build.  Do not know if I introduced any functional changes by mistake.

diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
index 10a82d40a0..f2db93f02c 100644
--- a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
+++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
@@ -61,7 +61,7 @@ Ext4ReadInode (
              Partition,
              Inode,
              Partition->InodeSize,
-             Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOffset * Partition->InodeSize
+             Ext4BlockToByteOffset (Partition, InodeTableStart) + MultU64x32 (InodeOffset, Partition->InodeSize)
              );
 
   if (EFI_ERROR (Status)) {
diff --git a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
index 1cafdd64cd..65109809c0 100644
--- a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
+++ b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
@@ -45,7 +45,7 @@ Ext4ReadBlocks (
   IN EXT4_BLOCK_NR BlockNumber
   )
 {
-  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, BlockNumber * Partition->BlockSize);
+  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, MultU64x32 (BlockNumber, Partition->BlockSize));
 }
 
 /**
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
index d790e70be1..8aa584df14 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
@@ -445,6 +445,6 @@ typedef struct {
 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)
+#define EXT4_INODE_SIZE(ino)  (LShiftU64 (ino->i_size_hi, 32) | ino->i_size_lo)
 
 #endif
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
index f6875c919e..a055a139e1 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
@@ -244,7 +244,7 @@ Ext4MakeBlockNumberFromHalfs (
   )
 {
   // High might have garbage if it's not a 64 bit filesystem
-  return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;
+  return Ext4Is64Bit (Partition) ? (Low | LShiftU64 (High, 32)) : Low;
 }
 
 /**
@@ -297,7 +297,7 @@ Ext4BlockToByteOffset (
   IN EXT4_BLOCK_NR Block
   )
 {
-  return Partition->BlockSize * Block;
+  return MultU64x32 (Block, Partition->BlockSize);
 }
 
 /**
@@ -333,7 +333,7 @@ Ext4InodeSize (
   CONST EXT4_INODE *Inode
   )
 {
-  return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;
+  return (LShiftU64 (Inode->i_size_hi, 32) | Inode->i_size_lo);
 }
 
 /**
diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
index 102b12d613..fc0185285e 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
+++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
@@ -111,6 +111,8 @@ [Sources]
   Collation.c
   Crc32c.c
   Crc16.c
+  Ext4Disk.h
+  Ext4Dxe.h
 
 [Packages]
   MdePkg/MdePkg.dec
diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/Extents.c
index db4bf5aa3f..8c9b4a4c75 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Extents.c
+++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
@@ -210,7 +210,7 @@ Ext4ExtentIdxLeafBlock (
   IN EXT4_EXTENT_INDEX *Index
   )
 {
-  return ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;
+  return LShiftU64(Index->ei_leaf_hi, 32) | Index->ei_leaf_lo;
 }
 
 STATIC UINTN  GetExtentRequests  = 0;
diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/File.c
index 10dda64b16..71d36d1990 100644
--- a/Features/Ext4Pkg/Ext4Dxe/File.c
+++ b/Features/Ext4Pkg/Ext4Dxe/File.c
@@ -487,8 +487,8 @@ Ext4GetFilesystemInfo (
   Info->BlockSize = Part->BlockSize;
   Info->Size       = NeededLength;
   Info->ReadOnly   = Part->ReadOnly;
-  Info->VolumeSize = TotalBlocks * Part->BlockSize;
-  Info->FreeSpace  = FreeBlocks * Part->BlockSize;
+  Info->VolumeSize = MultU64x32 (TotalBlocks, Part->BlockSize);
+  Info->FreeSpace  = MultU64x32 (FreeBlocks, Part->BlockSize);
 
   if (VolumeName != NULL) {
     StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/Inode.c
index 304bf0c4a9..2a9f534d7e 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Inode.c
+++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
@@ -154,7 +154,7 @@ Ext4Read (
       UINT64  ExtentOffset;
       UINTN   ExtentMayRead;
 
-      ExtentStartBytes   = (((UINT64)Extent.ee_start_hi << 32) | Extent.ee_start_lo) * Partition->BlockSize;
+      ExtentStartBytes   = MultU64x32 (LShiftU64 (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;
@@ -276,17 +276,17 @@ Ext4FilePhysicalSpace (
   Blocks   = File->Inode->i_blocks;
 
   if(HugeFile) {
-    Blocks |= ((UINT64)File->Inode->i_osd2.data_linux.l_i_blocks_high) << 32;
+    Blocks |= LShiftU64 (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;
+      return MultU64x32 (Blocks, File->Partition->BlockSize);
     }
   }
 
   // Else, each i_blocks unit corresponds to 512 bytes
-  return Blocks * 512;
+  return MultU64x32 (Blocks, 512);
 }
 
 // Copied from EmbeddedPkg at my mentor's request.
@@ -368,7 +368,7 @@ EpochToEfiTime (
     UINT32      Nanoseconds  = 0;                                \
                                                            \
     if (Ext4InodeHasField (Inode, Field ## _extra)) {          \
-      SecondsEpoch |= ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK)) << 32; \
+      SecondsEpoch |= LShiftU64 ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK), 32); \
       Nanoseconds   = Inode->Field ## _extra >> 2;                                            \
     }                                                                                       \
     EpochToEfiTime ((UINTN)SecondsEpoch, Time);                                                     \
diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
index 18d8295a1f..88d01b62a8 100644
--- a/Features/Ext4Pkg/Ext4Dxe/Superblock.c
+++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
@@ -161,7 +161,7 @@ Ext4OpenSuperblock (
 
   DEBUG ((EFI_D_INFO, "Read only = %u\n", Partition->ReadOnly));
 
-  Partition->BlockSize = 1024 << Sb->s_log_block_size;
+  Partition->BlockSize = (UINT32)LShiftU64 (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) {
@@ -195,7 +195,7 @@ Ext4OpenSuperblock (
   }
 
   NrBlocks = (UINTN)DivU64x32Remainder (
-                              Partition->NumberBlockGroups * Partition->DescSize,
+                              MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
                               Partition->BlockSize,
                               &NrBlocksRem
                               );
diff --git a/Features/Ext4Pkg/Ext4Pkg.dsc b/Features/Ext4Pkg/Ext4Pkg.dsc
index 62cb4e69cf..57f279a4d9 100644
--- a/Features/Ext4Pkg/Ext4Pkg.dsc
+++ b/Features/Ext4Pkg/Ext4Pkg.dsc
@@ -20,6 +20,8 @@ [Defines]
   BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
   SKUID_IDENTIFIER               = DEFAULT
 
+!include MdePkg/MdeLibs.dsc.inc
+
 [BuildOptions]
   *_*_*_CC_FLAGS                       = -D DISABLE_NEW_DEPRECATED_INTERFACES




Thanks,

Mike




> -----Original Message-----
> From: Pedro Falcato <pedro.falcato@gmail.com>
> Sent: Friday, July 30, 2021 9:17 AM
> To: devel@edk2.groups.io
> Cc: Pedro Falcato <pedro.falcato@gmail.com>; Leif Lindholm <leif@nuviainc.com>; Kinney, Michael D
> <michael.d.kinney@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
> Subject: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
> 
> This patch-set adds Ext4Pkg, a package designed to hold various drivers and
> utilities related to the EXT4 filesystem.
> 
> Right now, it holds a single read-only UEFI EXT4 driver (Ext4Dxe), which consumes the
> DISK_IO, BLOCK_IO and DISK_IO2 protocols and produce EFI_FILE_PROTOCOL and
> EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; this driver allows the mounting of EXT4 partitions and
> the reading of their contents.
> 
> Relevant RFC discussion, which includes a more in-depth walkthrough of EXT4 internals and
> driver limitations is available at https://edk2.groups.io/g/devel/topic/84368561.
> 
> Cc: Leif Lindholm <leif@nuviainc.com>
> Cc: Michael D Kinney <michael.d.kinney@intel.com>
> Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
> 
> Pedro Falcato (3):
>   Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
>   Ext4Pkg: Add Ext4Dxe driver.
>   Ext4Pkg: Add .DSC file.
> 
>  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 +++++++
>  Features/Ext4Pkg/Ext4Pkg.dec          |  17 +
>  Features/Ext4Pkg/Ext4Pkg.dsc          |  68 ++
>  Features/Ext4Pkg/Ext4Pkg.uni          |  14 +
>  19 files changed, 5250 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
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc
>  create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni
> 
> --
> 2.32.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
  2021-08-05 18:33 ` Michael D Kinney
@ 2021-08-05 22:50   ` Pedro Falcato
  2021-08-06 16:15     ` [edk2-devel] " Michael D Kinney
  0 siblings, 1 reply; 11+ messages in thread
From: Pedro Falcato @ 2021-08-05 22:50 UTC (permalink / raw)
  To: Kinney, Michael D, devel@edk2.groups.io; +Cc: Leif Lindholm, Bret Barkelew

Hi Mike,

Thanks for the helpful pointers. I'll consider everything for V2,
which I'll submit as soon as possible (hopefully tomorrow).

RE: Code style. I'll re-run ECC and try to solve the issues. One thing
though: Is it possible to make an exception for the naming of
ext4-specific struct members?
Example: Members' names like "bg_block_bitmap_lo" in
EXT4_BLOCK_GROUP_DESC. I'd like to make a case for it; from my
experience with my own hobby project's ext2 driver, having names
similar to what's used in the documentation/other source code is
incredibly helpful when trying to work on the code; with the original
docs' names, which are admittedly not compliant with the EDK2 coding
style, it really makes everything much clearer when using other code
or documentation as reference. Of course, if it's not possible I'll
rename them all.

Thanks,

Pedro


On Thu, 5 Aug 2021 at 19:33, Kinney, Michael D
<michael.d.kinney@intel.com> wrote:
>
> Hi Pedro,
>
> 1) Ext4Pkg/Ext4Dxe/Ext4Dxe.inf:
>
>   * To be consistent with other drivers, BASE_NAME should be changed from Ext4 to Ext4Dxe.
>   * For proper dependency checking in incremental builds, please add the .h files to the [Sources] section
>
>       Ext4Disk.h
>       Ext4Dxe.h
>
> 2) There are a number of code style issues that need to be addressed.  Can you fix those for V2?
>
> 3) I did a quick pass to find the IA32 NOOPT VS2019 issues.  With the following changes, I can get it to build.  Do not know if I introduced any functional changes by mistake.
>
> diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> index 10a82d40a0..f2db93f02c 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> +++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> @@ -61,7 +61,7 @@ Ext4ReadInode (
>               Partition,
>               Inode,
>               Partition->InodeSize,
> -             Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOffset * Partition->InodeSize
> +             Ext4BlockToByteOffset (Partition, InodeTableStart) + MultU64x32 (InodeOffset, Partition->InodeSize)
>               );
>
>    if (EFI_ERROR (Status)) {
> diff --git a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> index 1cafdd64cd..65109809c0 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> +++ b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> @@ -45,7 +45,7 @@ Ext4ReadBlocks (
>    IN EXT4_BLOCK_NR BlockNumber
>    )
>  {
> -  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, BlockNumber * Partition->BlockSize);
> +  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, MultU64x32 (BlockNumber, Partition->BlockSize));
>  }
>
>  /**
> diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> index d790e70be1..8aa584df14 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> @@ -445,6 +445,6 @@ typedef struct {
>  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)
> +#define EXT4_INODE_SIZE(ino)  (LShiftU64 (ino->i_size_hi, 32) | ino->i_size_lo)
>
>  #endif
> diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> index f6875c919e..a055a139e1 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> @@ -244,7 +244,7 @@ Ext4MakeBlockNumberFromHalfs (
>    )
>  {
>    // High might have garbage if it's not a 64 bit filesystem
> -  return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;
> +  return Ext4Is64Bit (Partition) ? (Low | LShiftU64 (High, 32)) : Low;
>  }
>
>  /**
> @@ -297,7 +297,7 @@ Ext4BlockToByteOffset (
>    IN EXT4_BLOCK_NR Block
>    )
>  {
> -  return Partition->BlockSize * Block;
> +  return MultU64x32 (Block, Partition->BlockSize);
>  }
>
>  /**
> @@ -333,7 +333,7 @@ Ext4InodeSize (
>    CONST EXT4_INODE *Inode
>    )
>  {
> -  return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;
> +  return (LShiftU64 (Inode->i_size_hi, 32) | Inode->i_size_lo);
>  }
>
>  /**
> diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> index 102b12d613..fc0185285e 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> @@ -111,6 +111,8 @@ [Sources]
>    Collation.c
>    Crc32c.c
>    Crc16.c
> +  Ext4Disk.h
> +  Ext4Dxe.h
>
>  [Packages]
>    MdePkg/MdePkg.dec
> diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/Extents.c
> index db4bf5aa3f..8c9b4a4c75 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/Extents.c
> +++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
> @@ -210,7 +210,7 @@ Ext4ExtentIdxLeafBlock (
>    IN EXT4_EXTENT_INDEX *Index
>    )
>  {
> -  return ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;
> +  return LShiftU64(Index->ei_leaf_hi, 32) | Index->ei_leaf_lo;
>  }
>
>  STATIC UINTN  GetExtentRequests  = 0;
> diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/File.c
> index 10dda64b16..71d36d1990 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/File.c
> +++ b/Features/Ext4Pkg/Ext4Dxe/File.c
> @@ -487,8 +487,8 @@ Ext4GetFilesystemInfo (
>    Info->BlockSize = Part->BlockSize;
>    Info->Size       = NeededLength;
>    Info->ReadOnly   = Part->ReadOnly;
> -  Info->VolumeSize = TotalBlocks * Part->BlockSize;
> -  Info->FreeSpace  = FreeBlocks * Part->BlockSize;
> +  Info->VolumeSize = MultU64x32 (TotalBlocks, Part->BlockSize);
> +  Info->FreeSpace  = MultU64x32 (FreeBlocks, Part->BlockSize);
>
>    if (VolumeName != NULL) {
>      StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
> diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/Inode.c
> index 304bf0c4a9..2a9f534d7e 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/Inode.c
> +++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
> @@ -154,7 +154,7 @@ Ext4Read (
>        UINT64  ExtentOffset;
>        UINTN   ExtentMayRead;
>
> -      ExtentStartBytes   = (((UINT64)Extent.ee_start_hi << 32) | Extent.ee_start_lo) * Partition->BlockSize;
> +      ExtentStartBytes   = MultU64x32 (LShiftU64 (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;
> @@ -276,17 +276,17 @@ Ext4FilePhysicalSpace (
>    Blocks   = File->Inode->i_blocks;
>
>    if(HugeFile) {
> -    Blocks |= ((UINT64)File->Inode->i_osd2.data_linux.l_i_blocks_high) << 32;
> +    Blocks |= LShiftU64 (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;
> +      return MultU64x32 (Blocks, File->Partition->BlockSize);
>      }
>    }
>
>    // Else, each i_blocks unit corresponds to 512 bytes
> -  return Blocks * 512;
> +  return MultU64x32 (Blocks, 512);
>  }
>
>  // Copied from EmbeddedPkg at my mentor's request.
> @@ -368,7 +368,7 @@ EpochToEfiTime (
>      UINT32      Nanoseconds  = 0;                                \
>                                                             \
>      if (Ext4InodeHasField (Inode, Field ## _extra)) {          \
> -      SecondsEpoch |= ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK)) << 32; \
> +      SecondsEpoch |= LShiftU64 ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK), 32); \
>        Nanoseconds   = Inode->Field ## _extra >> 2;                                            \
>      }                                                                                       \
>      EpochToEfiTime ((UINTN)SecondsEpoch, Time);                                                     \
> diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> index 18d8295a1f..88d01b62a8 100644
> --- a/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> +++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> @@ -161,7 +161,7 @@ Ext4OpenSuperblock (
>
>    DEBUG ((EFI_D_INFO, "Read only = %u\n", Partition->ReadOnly));
>
> -  Partition->BlockSize = 1024 << Sb->s_log_block_size;
> +  Partition->BlockSize = (UINT32)LShiftU64 (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) {
> @@ -195,7 +195,7 @@ Ext4OpenSuperblock (
>    }
>
>    NrBlocks = (UINTN)DivU64x32Remainder (
> -                              Partition->NumberBlockGroups * Partition->DescSize,
> +                              MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
>                                Partition->BlockSize,
>                                &NrBlocksRem
>                                );
> diff --git a/Features/Ext4Pkg/Ext4Pkg.dsc b/Features/Ext4Pkg/Ext4Pkg.dsc
> index 62cb4e69cf..57f279a4d9 100644
> --- a/Features/Ext4Pkg/Ext4Pkg.dsc
> +++ b/Features/Ext4Pkg/Ext4Pkg.dsc
> @@ -20,6 +20,8 @@ [Defines]
>    BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
>    SKUID_IDENTIFIER               = DEFAULT
>
> +!include MdePkg/MdeLibs.dsc.inc
> +
>  [BuildOptions]
>    *_*_*_CC_FLAGS                       = -D DISABLE_NEW_DEPRECATED_INTERFACES
>
>
>
>
> Thanks,
>
> Mike
>
>
>
>
> > -----Original Message-----
> > From: Pedro Falcato <pedro.falcato@gmail.com>
> > Sent: Friday, July 30, 2021 9:17 AM
> > To: devel@edk2.groups.io
> > Cc: Pedro Falcato <pedro.falcato@gmail.com>; Leif Lindholm <leif@nuviainc.com>; Kinney, Michael D
> > <michael.d.kinney@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
> > Subject: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
> >
> > This patch-set adds Ext4Pkg, a package designed to hold various drivers and
> > utilities related to the EXT4 filesystem.
> >
> > Right now, it holds a single read-only UEFI EXT4 driver (Ext4Dxe), which consumes the
> > DISK_IO, BLOCK_IO and DISK_IO2 protocols and produce EFI_FILE_PROTOCOL and
> > EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; this driver allows the mounting of EXT4 partitions and
> > the reading of their contents.
> >
> > Relevant RFC discussion, which includes a more in-depth walkthrough of EXT4 internals and
> > driver limitations is available at https://edk2.groups.io/g/devel/topic/84368561.
> >
> > Cc: Leif Lindholm <leif@nuviainc.com>
> > Cc: Michael D Kinney <michael.d.kinney@intel.com>
> > Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
> >
> > Pedro Falcato (3):
> >   Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
> >   Ext4Pkg: Add Ext4Dxe driver.
> >   Ext4Pkg: Add .DSC file.
> >
> >  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 +++++++
> >  Features/Ext4Pkg/Ext4Pkg.dec          |  17 +
> >  Features/Ext4Pkg/Ext4Pkg.dsc          |  68 ++
> >  Features/Ext4Pkg/Ext4Pkg.uni          |  14 +
> >  19 files changed, 5250 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
> >  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
> >  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc
> >  create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni
> >
> > --
> > 2.32.0
>


-- 
Pedro Falcato

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [edk2-devel] [Patch 0/3] Ext4Pkg: Add Ext4Pkg
  2021-08-05 22:50   ` Pedro Falcato
@ 2021-08-06 16:15     ` Michael D Kinney
  0 siblings, 0 replies; 11+ messages in thread
From: Michael D Kinney @ 2021-08-06 16:15 UTC (permalink / raw)
  To: devel@edk2.groups.io, pedro.falcato@gmail.com, Kinney, Michael D
  Cc: Leif Lindholm, Bret Barkelew



> -----Original Message-----
> From: devel@edk2.groups.io <devel@edk2.groups.io> On Behalf Of Pedro Falcato
> Sent: Thursday, August 5, 2021 3:50 PM
> To: Kinney, Michael D <michael.d.kinney@intel.com>; devel@edk2.groups.io
> Cc: Leif Lindholm <leif@nuviainc.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
> Subject: Re: [edk2-devel] [Patch 0/3] Ext4Pkg: Add Ext4Pkg
> 
> Hi Mike,
> 
> Thanks for the helpful pointers. I'll consider everything for V2,
> which I'll submit as soon as possible (hopefully tomorrow).
> 
> RE: Code style. I'll re-run ECC and try to solve the issues. One thing
> though: Is it possible to make an exception for the naming of
> ext4-specific struct members?
> Example: Members' names like "bg_block_bitmap_lo" in

This is ok since this is a structure that is based on the EXT4
documentation,

> EXT4_BLOCK_GROUP_DESC. I'd like to make a case for it; from my
> experience with my own hobby project's ext2 driver, having names
> similar to what's used in the documentation/other source code is
> incredibly helpful when trying to work on the code; with the original
> docs' names, which are admittedly not compliant with the EDK2 coding
> style, it really makes everything much clearer when using other code
> or documentation as reference. Of course, if it's not possible I'll
> rename them all.
> 
> Thanks,
> 
> Pedro
> 
> 
> On Thu, 5 Aug 2021 at 19:33, Kinney, Michael D
> <michael.d.kinney@intel.com> wrote:
> >
> > Hi Pedro,
> >
> > 1) Ext4Pkg/Ext4Dxe/Ext4Dxe.inf:
> >
> >   * To be consistent with other drivers, BASE_NAME should be changed from Ext4 to Ext4Dxe.
> >   * For proper dependency checking in incremental builds, please add the .h files to the [Sources] section
> >
> >       Ext4Disk.h
> >       Ext4Dxe.h
> >
> > 2) There are a number of code style issues that need to be addressed.  Can you fix those for V2?
> >
> > 3) I did a quick pass to find the IA32 NOOPT VS2019 issues.  With the following changes, I can get it to build.  Do not
> know if I introduced any functional changes by mistake.
> >
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> > index 10a82d40a0..f2db93f02c 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> > +++ b/Features/Ext4Pkg/Ext4Dxe/BlockGroup.c
> > @@ -61,7 +61,7 @@ Ext4ReadInode (
> >               Partition,
> >               Inode,
> >               Partition->InodeSize,
> > -             Ext4BlockToByteOffset (Partition, InodeTableStart) + InodeOffset * Partition->InodeSize
> > +             Ext4BlockToByteOffset (Partition, InodeTableStart) + MultU64x32 (InodeOffset, Partition->InodeSize)
> >               );
> >
> >    if (EFI_ERROR (Status)) {
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> > index 1cafdd64cd..65109809c0 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> > +++ b/Features/Ext4Pkg/Ext4Dxe/DiskUtil.c
> > @@ -45,7 +45,7 @@ Ext4ReadBlocks (
> >    IN EXT4_BLOCK_NR BlockNumber
> >    )
> >  {
> > -  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, BlockNumber * Partition->BlockSize);
> > +  return Ext4ReadDiskIo (Partition, Buffer, NumberBlocks * Partition->BlockSize, MultU64x32 (BlockNumber, Partition-
> >BlockSize));
> >  }
> >
> >  /**
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> > index d790e70be1..8aa584df14 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> > @@ -445,6 +445,6 @@ typedef struct {
> >  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)
> > +#define EXT4_INODE_SIZE(ino)  (LShiftU64 (ino->i_size_hi, 32) | ino->i_size_lo)
> >
> >  #endif
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> > index f6875c919e..a055a139e1 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> > @@ -244,7 +244,7 @@ Ext4MakeBlockNumberFromHalfs (
> >    )
> >  {
> >    // High might have garbage if it's not a 64 bit filesystem
> > -  return Ext4Is64Bit (Partition) ? Low | ((UINT64)High << 32) : Low;
> > +  return Ext4Is64Bit (Partition) ? (Low | LShiftU64 (High, 32)) : Low;
> >  }
> >
> >  /**
> > @@ -297,7 +297,7 @@ Ext4BlockToByteOffset (
> >    IN EXT4_BLOCK_NR Block
> >    )
> >  {
> > -  return Partition->BlockSize * Block;
> > +  return MultU64x32 (Block, Partition->BlockSize);
> >  }
> >
> >  /**
> > @@ -333,7 +333,7 @@ Ext4InodeSize (
> >    CONST EXT4_INODE *Inode
> >    )
> >  {
> > -  return ((UINT64)Inode->i_size_hi << 32) | Inode->i_size_lo;
> > +  return (LShiftU64 (Inode->i_size_hi, 32) | Inode->i_size_lo);
> >  }
> >
> >  /**
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> > index 102b12d613..fc0185285e 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> > @@ -111,6 +111,8 @@ [Sources]
> >    Collation.c
> >    Crc32c.c
> >    Crc16.c
> > +  Ext4Disk.h
> > +  Ext4Dxe.h
> >
> >  [Packages]
> >    MdePkg/MdePkg.dec
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Extents.c b/Features/Ext4Pkg/Ext4Dxe/Extents.c
> > index db4bf5aa3f..8c9b4a4c75 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/Extents.c
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Extents.c
> > @@ -210,7 +210,7 @@ Ext4ExtentIdxLeafBlock (
> >    IN EXT4_EXTENT_INDEX *Index
> >    )
> >  {
> > -  return ((UINT64)Index->ei_leaf_hi << 32) | Index->ei_leaf_lo;
> > +  return LShiftU64(Index->ei_leaf_hi, 32) | Index->ei_leaf_lo;
> >  }
> >
> >  STATIC UINTN  GetExtentRequests  = 0;
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/File.c b/Features/Ext4Pkg/Ext4Dxe/File.c
> > index 10dda64b16..71d36d1990 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/File.c
> > +++ b/Features/Ext4Pkg/Ext4Dxe/File.c
> > @@ -487,8 +487,8 @@ Ext4GetFilesystemInfo (
> >    Info->BlockSize = Part->BlockSize;
> >    Info->Size       = NeededLength;
> >    Info->ReadOnly   = Part->ReadOnly;
> > -  Info->VolumeSize = TotalBlocks * Part->BlockSize;
> > -  Info->FreeSpace  = FreeBlocks * Part->BlockSize;
> > +  Info->VolumeSize = MultU64x32 (TotalBlocks, Part->BlockSize);
> > +  Info->FreeSpace  = MultU64x32 (FreeBlocks, Part->BlockSize);
> >
> >    if (VolumeName != NULL) {
> >      StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Inode.c b/Features/Ext4Pkg/Ext4Dxe/Inode.c
> > index 304bf0c4a9..2a9f534d7e 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/Inode.c
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Inode.c
> > @@ -154,7 +154,7 @@ Ext4Read (
> >        UINT64  ExtentOffset;
> >        UINTN   ExtentMayRead;
> >
> > -      ExtentStartBytes   = (((UINT64)Extent.ee_start_hi << 32) | Extent.ee_start_lo) * Partition->BlockSize;
> > +      ExtentStartBytes   = MultU64x32 (LShiftU64 (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;
> > @@ -276,17 +276,17 @@ Ext4FilePhysicalSpace (
> >    Blocks   = File->Inode->i_blocks;
> >
> >    if(HugeFile) {
> > -    Blocks |= ((UINT64)File->Inode->i_osd2.data_linux.l_i_blocks_high) << 32;
> > +    Blocks |= LShiftU64 (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;
> > +      return MultU64x32 (Blocks, File->Partition->BlockSize);
> >      }
> >    }
> >
> >    // Else, each i_blocks unit corresponds to 512 bytes
> > -  return Blocks * 512;
> > +  return MultU64x32 (Blocks, 512);
> >  }
> >
> >  // Copied from EmbeddedPkg at my mentor's request.
> > @@ -368,7 +368,7 @@ EpochToEfiTime (
> >      UINT32      Nanoseconds  = 0;                                \
> >                                                             \
> >      if (Ext4InodeHasField (Inode, Field ## _extra)) {          \
> > -      SecondsEpoch |= ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK)) << 32; \
> > +      SecondsEpoch |= LShiftU64 ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK), 32); \
> >        Nanoseconds   = Inode->Field ## _extra >> 2;                                            \
> >      }                                                                                       \
> >      EpochToEfiTime ((UINTN)SecondsEpoch, Time);                                                     \
> > diff --git a/Features/Ext4Pkg/Ext4Dxe/Superblock.c b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> > index 18d8295a1f..88d01b62a8 100644
> > --- a/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> > +++ b/Features/Ext4Pkg/Ext4Dxe/Superblock.c
> > @@ -161,7 +161,7 @@ Ext4OpenSuperblock (
> >
> >    DEBUG ((EFI_D_INFO, "Read only = %u\n", Partition->ReadOnly));
> >
> > -  Partition->BlockSize = 1024 << Sb->s_log_block_size;
> > +  Partition->BlockSize = (UINT32)LShiftU64 (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) {
> > @@ -195,7 +195,7 @@ Ext4OpenSuperblock (
> >    }
> >
> >    NrBlocks = (UINTN)DivU64x32Remainder (
> > -                              Partition->NumberBlockGroups * Partition->DescSize,
> > +                              MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
> >                                Partition->BlockSize,
> >                                &NrBlocksRem
> >                                );
> > diff --git a/Features/Ext4Pkg/Ext4Pkg.dsc b/Features/Ext4Pkg/Ext4Pkg.dsc
> > index 62cb4e69cf..57f279a4d9 100644
> > --- a/Features/Ext4Pkg/Ext4Pkg.dsc
> > +++ b/Features/Ext4Pkg/Ext4Pkg.dsc
> > @@ -20,6 +20,8 @@ [Defines]
> >    BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
> >    SKUID_IDENTIFIER               = DEFAULT
> >
> > +!include MdePkg/MdeLibs.dsc.inc
> > +
> >  [BuildOptions]
> >    *_*_*_CC_FLAGS                       = -D DISABLE_NEW_DEPRECATED_INTERFACES
> >
> >
> >
> >
> > Thanks,
> >
> > Mike
> >
> >
> >
> >
> > > -----Original Message-----
> > > From: Pedro Falcato <pedro.falcato@gmail.com>
> > > Sent: Friday, July 30, 2021 9:17 AM
> > > To: devel@edk2.groups.io
> > > Cc: Pedro Falcato <pedro.falcato@gmail.com>; Leif Lindholm <leif@nuviainc.com>; Kinney, Michael D
> > > <michael.d.kinney@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>
> > > Subject: [Patch 0/3] Ext4Pkg: Add Ext4Pkg
> > >
> > > This patch-set adds Ext4Pkg, a package designed to hold various drivers and
> > > utilities related to the EXT4 filesystem.
> > >
> > > Right now, it holds a single read-only UEFI EXT4 driver (Ext4Dxe), which consumes the
> > > DISK_IO, BLOCK_IO and DISK_IO2 protocols and produce EFI_FILE_PROTOCOL and
> > > EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; this driver allows the mounting of EXT4 partitions and
> > > the reading of their contents.
> > >
> > > Relevant RFC discussion, which includes a more in-depth walkthrough of EXT4 internals and
> > > driver limitations is available at https://edk2.groups.io/g/devel/topic/84368561.
> > >
> > > Cc: Leif Lindholm <leif@nuviainc.com>
> > > Cc: Michael D Kinney <michael.d.kinney@intel.com>
> > > Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
> > >
> > > Pedro Falcato (3):
> > >   Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni.
> > >   Ext4Pkg: Add Ext4Dxe driver.
> > >   Ext4Pkg: Add .DSC file.
> > >
> > >  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 +++++++
> > >  Features/Ext4Pkg/Ext4Pkg.dec          |  17 +
> > >  Features/Ext4Pkg/Ext4Pkg.dsc          |  68 ++
> > >  Features/Ext4Pkg/Ext4Pkg.uni          |  14 +
> > >  19 files changed, 5250 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
> > >  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dec
> > >  create mode 100644 Features/Ext4Pkg/Ext4Pkg.dsc
> > >  create mode 100644 Features/Ext4Pkg/Ext4Pkg.uni
> > >
> > > --
> > > 2.32.0
> >
> 
> 
> --
> Pedro Falcato
> 
> 
> 
> 


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2021-08-06 16:15 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-07-30 16:16 [Patch 0/3] Ext4Pkg: Add Ext4Pkg Pedro Falcato
2021-07-30 16:16 ` [Patch 1/3] Ext4Pkg: Add Ext4Pkg.dec and Ext4Pkg.uni Pedro Falcato
2021-07-30 16:16 ` [Patch 2/3] Ext4Pkg: Add Ext4Dxe driver Pedro Falcato
2021-07-30 20:28   ` [edk2-devel] " Marvin Häuser
2021-08-02 15:56     ` Pedro Falcato
2021-07-30 16:16 ` [Patch 3/3] Ext4Pkg: Add .DSC file Pedro Falcato
2021-08-05 15:36 ` [Patch 0/3] Ext4Pkg: Add Ext4Pkg Michael D Kinney
2021-08-05 16:02 ` Michael D Kinney
2021-08-05 18:33 ` Michael D Kinney
2021-08-05 22:50   ` Pedro Falcato
2021-08-06 16:15     ` [edk2-devel] " Michael D Kinney

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox