public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [RFC PATCH 1/1] Ext4Pkg: Add Ext4Pkg to edk2.git
@ 2022-11-16 23:34 Pedro Falcato
  2022-11-17 10:28 ` Marvin Häuser
  0 siblings, 1 reply; 2+ messages in thread
From: Pedro Falcato @ 2022-11-16 23:34 UTC (permalink / raw)
  To: devel
  Cc: Pedro Falcato, Marvin Häuser, Andrew Fish, Leif Lindholm,
	Michael D Kinney

Having non-platform packages in edk2-platforms has proven to be a poor
idea over time.

1) Having features in edk2-platforms makes little sense, and new packages
   are still being added over time to edk2/.

2) edk2-platforms has no versioning, no stable tags - you don't know
   what edk2 tag you're supposed to compile against, and builds just
   break.

3) edk2-platforms is missing all the good innovation (being) added to
   edk2.git, like CodeQL and GitHub PRs; there are seemingly no plans to
   add this to edk2-platforms.

4) Existing platforms in edk2.git like EmulatorPkg and OvmfPkg cannot
   depend on an edk2-platforms package; there are also no plans to add
   these to edk2-platforms.

Therefore, add Ext4Pkg to edk2.git, in Filesystems/.

Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com>
Cc: Marvin Häuser <mhaeuser@posteo.de>
Cc: Andrew Fish <afish@apple.com>
Cc: Leif Lindholm <quic_llindhol@quicinc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
---
 This is just an RFC patch to get comments from EDK2 stewards and relevant people.
 This patch is not supposed to be merged in its current state, as it's missing the corresponding
 change in edk2-platforms and possibly some way to keep git history.

 There are very apparent flaws in current edk2-platforms, as mentioned in the commit message.
 Originally, when Ext4Pkg was proposed in GSoC 2021, we opted for edk2-platforms/Features/Ext4Pkg.
 I genuinely believe this to be a poor choice given the reasons above, and some others.
 I also believe that Ext4Pkg has proven its usefulness and maturity over the past year and a half.

 The choice of Filesystems/{Package} was deliberate - adding packages to the root of edk2.git
 is clearly not sustainable, given the 26(!) packages that are already there, making for a confusing
 filesystem hierarchy (or lack-of).

 Comments and suggestions are very welcome.

 Filesystems/Ext4Pkg/Ext4.dsc.inc           |   16 +
 Filesystems/Ext4Pkg/Ext4.fdf.inc           |   11 +
 Filesystems/Ext4Pkg/Ext4Components.dsc.inc |   14 +
 Filesystems/Ext4Pkg/Ext4Defines.dsc.inc    |   14 +
 Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c   |  228 ++++
 Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c     |  285 +++++
 Filesystems/Ext4Pkg/Ext4Dxe/Collation.c    |  173 +++
 Filesystems/Ext4Pkg/Ext4Dxe/Directory.c    |  669 +++++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c     |  113 ++
 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h     |  475 ++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c      |  844 ++++++++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h      | 1232 ++++++++++++++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf    |  149 +++
 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni    |   15 +
 Filesystems/Ext4Pkg/Ext4Dxe/Extents.c      |  624 ++++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/File.c         | 1001 ++++++++++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/Inode.c        |  490 ++++++++
 Filesystems/Ext4Pkg/Ext4Dxe/Partition.c    |  132 +++
 Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c   |  355 ++++++
 Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c      |  261 +++++
 Filesystems/Ext4Pkg/Ext4Libs.dsc.inc       |   11 +
 Filesystems/Ext4Pkg/Ext4Pkg.dec            |   17 +
 Filesystems/Ext4Pkg/Ext4Pkg.dsc            |   75 ++
 Filesystems/Ext4Pkg/Ext4Pkg.uni            |   14 +
 Maintainers.txt                            |    7 +
 25 files changed, 7225 insertions(+)
 create mode 100644 Filesystems/Ext4Pkg/Ext4.dsc.inc
 create mode 100644 Filesystems/Ext4Pkg/Ext4.fdf.inc
 create mode 100644 Filesystems/Ext4Pkg/Ext4Components.dsc.inc
 create mode 100644 Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Collation.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Directory.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Extents.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/File.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Inode.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Partition.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c
 create mode 100644 Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
 create mode 100644 Filesystems/Ext4Pkg/Ext4Pkg.dec
 create mode 100644 Filesystems/Ext4Pkg/Ext4Pkg.dsc
 create mode 100644 Filesystems/Ext4Pkg/Ext4Pkg.uni

diff --git a/Filesystems/Ext4Pkg/Ext4.dsc.inc b/Filesystems/Ext4Pkg/Ext4.dsc.inc
new file mode 100644
index 000000000000..7d4114c54f7b
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4.dsc.inc
@@ -0,0 +1,16 @@
+## @file
+# Ext4 DSC include file for Platform DSC
+#
+# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+!include Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
+
+[LibraryClasses]
+!include Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
+
+[Components.common]
+!include Filesystems/Ext4Pkg/Ext4Components.dsc.inc
diff --git a/Filesystems/Ext4Pkg/Ext4.fdf.inc b/Filesystems/Ext4Pkg/Ext4.fdf.inc
new file mode 100644
index 000000000000..ee23b5ddd9eb
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4.fdf.inc
@@ -0,0 +1,11 @@
+## @file
+# Ext4 FDF include file for All Architectures.
+#
+# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+!if $(EXT4_ENABLE) == TRUE
+  INF Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
+!endif
diff --git a/Filesystems/Ext4Pkg/Ext4Components.dsc.inc b/Filesystems/Ext4Pkg/Ext4Components.dsc.inc
new file mode 100644
index 000000000000..aa9f60b46cbf
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Components.dsc.inc
@@ -0,0 +1,14 @@
+## @file
+# Ext4 DSC include file for [Components] section of all Architectures.
+#
+# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+!if $(EXT4_ENABLE) == TRUE
+  Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf {
+    <PcdsFixedAtBuild>
+      gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000007
+  }
+!endif
diff --git a/Filesystems/Ext4Pkg/Ext4Defines.dsc.inc b/Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
new file mode 100644
index 000000000000..b02eac59cf7a
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
@@ -0,0 +1,14 @@
+## @file
+# Ext4 DSC include file for [Defines] section of all Architectures.
+#
+# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+!ifndef EXT4_ENABLE
+  #
+  # This flag is to enable or disable the ext4 feature.
+  #
+  DEFINE EXT4_ENABLE = TRUE
+!endif
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c
new file mode 100644
index 000000000000..cba96cd95afc
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c
@@ -0,0 +1,228 @@
+/** @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"
+
+/**
+   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.
+**/
+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
+  )
+{
+  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/if we add write support
+
+  InodeTableStart = EXT4_BLOCK_NR_FROM_HALFS (
+                      Partition,
+                      BlockGroup->bg_inode_table_lo,
+                      BlockGroup->bg_inode_table_hi
+                      );
+
+  Status = Ext4ReadDiskIo (
+             Partition,
+             Inode,
+             Partition->InodeSize,
+             EXT4_BLOCK_TO_BYTES (Partition, InodeTableStart) + MultU64x32 (InodeOffset, Partition->InodeSize)
+             );
+
+  if (EFI_ERROR (Status)) {
+    DEBUG ((
+      DEBUG_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 ((
+      DEBUG_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 = CalculateCrc16Ansi (Partition->SuperBlock.s_uuid, 16, 0);
+  Csum = CalculateCrc16Ansi (&BlockGroupNum, sizeof (BlockGroupNum), Csum);
+  Csum = CalculateCrc16Ansi (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
+  Csum = CalculateCrc16Ansi (&Dummy, sizeof (Dummy), Csum);
+  Csum =
+    CalculateCrc16Ansi (
+      &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 (!EXT4_HAS_METADATA_CSUM (Partition) && !EXT4_HAS_GDT_CSUM (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 (EXT4_HAS_METADATA_CSUM (Partition)) {
+    return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, BlockGroupDesc, BlockGroupNum);
+  } else if (EXT4_HAS_GDT_CSUM (Partition)) {
+    return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGroupDesc, BlockGroupNum);
+  }
+
+  return 0;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c b/Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c
new file mode 100644
index 000000000000..2bc629fe9d38
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c
@@ -0,0 +1,285 @@
+/** @file
+  Implementation of routines that deal with ext2/3 block maps.
+
+  Copyright (c) 2022 Pedro Falcato All rights reserved.
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <Ext4Dxe.h>
+
+// Note: The largest path we can take uses up 4 indices
+#define EXT4_MAX_BLOCK_PATH  4
+
+typedef enum ext4_logical_block_type {
+  EXT4_TYPE_DIRECT_BLOCK = 0,
+  EXT4_TYPE_SINGLY_BLOCK,
+  EXT4_TYPE_DOUBLY_BLOCK,
+  EXT4_TYPE_TREBLY_BLOCK,
+  EXT4_TYPE_BAD_BLOCK
+} EXT4_LOGICAL_BLOCK_TYPE;
+
+/**
+   @brief Detect the type of path the logical block will follow
+
+   @param[in] LogicalBlock The logical block
+   @param[in] Partition    Pointer to an EXT4_PARTITION
+   @return The type of path the logical block will need to follow
+ */
+STATIC
+EXT4_LOGICAL_BLOCK_TYPE
+Ext4DetectBlockType (
+  IN UINT32                LogicalBlock,
+  IN CONST EXT4_PARTITION  *Partition
+  )
+{
+  UINT32  Entries;
+  UINT32  MinSinglyBlock;
+  UINT32  MinDoublyBlock;
+  UINT32  MinTreblyBlock;
+  UINT32  MinQuadBlock;
+
+  Entries        = (Partition->BlockSize / sizeof (UINT32));
+  MinSinglyBlock = EXT4_DBLOCKS;
+  MinDoublyBlock = Entries + MinSinglyBlock;
+  MinTreblyBlock = Entries * Entries + MinDoublyBlock;
+  MinQuadBlock   = Entries * Entries * Entries + MinTreblyBlock; // Doesn't actually exist
+
+  if (LogicalBlock < MinSinglyBlock) {
+    return EXT4_TYPE_DIRECT_BLOCK;
+  } else if ((LogicalBlock >= MinSinglyBlock) && (LogicalBlock < MinDoublyBlock)) {
+    return EXT4_TYPE_SINGLY_BLOCK;
+  } else if ((LogicalBlock >= MinDoublyBlock) && (LogicalBlock < MinTreblyBlock)) {
+    return EXT4_TYPE_DOUBLY_BLOCK;
+  } else if (((LogicalBlock >= MinTreblyBlock) && (LogicalBlock < MinQuadBlock))) {
+    return EXT4_TYPE_TREBLY_BLOCK;
+  } else {
+    return EXT4_TYPE_BAD_BLOCK;
+  }
+}
+
+/**
+   @brief Get a block's path in indices
+
+   @param[in]  Partition       Pointer to an EXT4_PARTITION
+   @param[in]  LogicalBlock    Logical block
+   @param[out] BlockPath       Pointer to an array of EXT4_MAX_BLOCK_PATH elements, where the
+                               indices we'll need to read are inserted.
+   @return The number of path elements that are required (and were inserted in BlockPath)
+ */
+UINTN
+Ext4GetBlockPath (
+  IN  CONST EXT4_PARTITION  *Partition,
+  IN  UINT32                LogicalBlock,
+  OUT EXT2_BLOCK_NR         BlockPath[EXT4_MAX_BLOCK_PATH]
+  )
+{
+  // The logic behind the block map is very much like a page table
+  // Let's think of blocks with 512 entries (exactly like a page table on x64).
+  // On doubly indirect block paths, we subtract the min doubly blocks from the logical block.
+  // The top 9 bits of the result are the index inside the dind block, the bottom 9 bits are the
+  // index inside the ind block. Since Entries is always a power of 2, entries - 1 will give us
+  // a mask of the BlockMapBits.
+  // Note that all this math could be done with ands and shifts (similar implementations exist
+  // in a bunch of other places), but I'm doing it a simplified way with divs and modulus,
+  // since it's not going to be a bottleneck anyway.
+
+  UINT32  Entries;
+  UINT32  EntriesEntries;
+  UINT32  MinSinglyBlock;
+  UINT32  MinDoublyBlock;
+  UINT32  MinTreblyBlock;
+
+  EXT4_LOGICAL_BLOCK_TYPE  Type;
+
+  Entries        = (Partition->BlockSize / sizeof (UINT32));
+  EntriesEntries = Entries * Entries;
+
+  MinSinglyBlock = EXT4_DBLOCKS;
+  MinDoublyBlock = Entries + MinSinglyBlock;
+  MinTreblyBlock = EntriesEntries + MinDoublyBlock;
+
+  Type = Ext4DetectBlockType (LogicalBlock, Partition);
+
+  switch (Type) {
+    case EXT4_TYPE_DIRECT_BLOCK:
+      BlockPath[0] = LogicalBlock;
+      break;
+    case EXT4_TYPE_SINGLY_BLOCK:
+      BlockPath[0] = EXT4_IND_BLOCK;
+      BlockPath[1] = LogicalBlock - EXT4_DBLOCKS;
+      break;
+    case EXT4_TYPE_DOUBLY_BLOCK:
+      BlockPath[0]  = EXT4_DIND_BLOCK;
+      LogicalBlock -= MinDoublyBlock;
+      BlockPath[1]  = LogicalBlock / Entries;
+      BlockPath[2]  = LogicalBlock % Entries;
+      break;
+    case EXT4_TYPE_TREBLY_BLOCK:
+      BlockPath[0]  = EXT4_DIND_BLOCK;
+      LogicalBlock -= MinTreblyBlock;
+      BlockPath[1]  = LogicalBlock / EntriesEntries;
+      BlockPath[2]  = (LogicalBlock % EntriesEntries) / Entries;
+      BlockPath[3]  = (LogicalBlock % EntriesEntries) % Entries;
+      break;
+    default:
+      // EXT4_TYPE_BAD_BLOCK
+      break;
+  }
+
+  return Type + 1;
+}
+
+/**
+   @brief Get an extent from a block map
+   Note: Also parses file holes and creates uninitialised extents from them.
+
+   @param[in]  Buffer          Buffer of block pointers
+   @param[in]  IndEntries      Number of entries in this block pointer table
+   @param[in]  StartIndex      The start index from which we want to find a contiguous extent
+   @param[out] Extent          Pointer to the resulting EXT4_EXTENT
+ */
+VOID
+Ext4GetExtentInBlockMap (
+  IN CONST UINT32  *Buffer,
+  IN CONST UINT32  IndEntries,
+  IN UINT32        StartIndex,
+  OUT EXT4_EXTENT  *Extent
+  )
+{
+  UINT32  Index;
+  UINT32  FirstBlock;
+  UINT32  LastBlock;
+  UINT16  Count;
+
+  Count      = 1;
+  LastBlock  = Buffer[StartIndex];
+  FirstBlock = LastBlock;
+
+  if (FirstBlock == EXT4_BLOCK_FILE_HOLE) {
+    // File hole, let's see how many blocks this hole spans
+    Extent->ee_start_hi = 0;
+    Extent->ee_start_lo = 0;
+
+    for (Index = StartIndex + 1; Index < IndEntries; Index++) {
+      if (Count == EXT4_EXTENT_MAX_INITIALIZED - 1) {
+        // We've reached the max size of an uninit extent, break
+        break;
+      }
+
+      if (Buffer[Index] == EXT4_BLOCK_FILE_HOLE) {
+        Count++;
+      } else {
+        break;
+      }
+    }
+
+    // We mark the extent as uninitialised, although there's a difference between uninit
+    // extents and file holes.
+    Extent->ee_len = EXT4_EXTENT_MAX_INITIALIZED + Count;
+    return;
+  }
+
+  for (Index = StartIndex + 1; Index < IndEntries; Index++) {
+    if (Count == EXT4_EXTENT_MAX_INITIALIZED) {
+      // We've reached the max size of an extent, break
+      break;
+    }
+
+    if ((Buffer[Index] == LastBlock + 1) && (Buffer[Index] != EXT4_BLOCK_FILE_HOLE)) {
+      Count++;
+    } else {
+      break;
+    }
+
+    LastBlock = Buffer[Index];
+  }
+
+  Extent->ee_start_lo = FirstBlock;
+  Extent->ee_start_hi = 0;
+  Extent->ee_len      = Count;
+}
+
+/**
+   Retrieves an extent from an EXT2/3 inode (with a blockmap).
+   @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
+Ext4GetBlocks (
+  IN  EXT4_PARTITION  *Partition,
+  IN  EXT4_FILE       *File,
+  IN  EXT2_BLOCK_NR   LogicalBlock,
+  OUT EXT4_EXTENT     *Extent
+  )
+{
+  EXT4_INODE     *Inode;
+  EXT2_BLOCK_NR  BlockPath[EXT4_MAX_BLOCK_PATH];
+  UINTN          BlockPathLength;
+  UINTN          Index;
+  UINT32         *Buffer;
+  EFI_STATUS     Status;
+  UINT32         Block;
+  UINT32         BlockIndex;
+
+  Inode = File->Inode;
+
+  BlockPathLength = Ext4GetBlockPath (Partition, LogicalBlock, BlockPath);
+
+  if (BlockPathLength - 1 == EXT4_TYPE_BAD_BLOCK) {
+    // Bad logical block (out of range)
+    return EFI_NO_MAPPING;
+  }
+
+  Extent->ee_block = LogicalBlock;
+
+  if (BlockPathLength == 1) {
+    // Fast path for blocks 0 - 12 that skips allocations
+    Ext4GetExtentInBlockMap (Inode->i_data, EXT4_DBLOCKS, BlockPath[0], Extent);
+
+    return EFI_SUCCESS;
+  }
+
+  Buffer = AllocatePool (Partition->BlockSize);
+  if (Buffer == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  // Note the BlockPathLength - 1 so we don't end up reading the final block
+  for (Index = 0; Index < BlockPathLength - 1; Index++) {
+    BlockIndex = BlockPath[Index];
+
+    if (Index == 0) {
+      Block = Inode->i_data[BlockIndex];
+    } else {
+      Block = Buffer[BlockIndex];
+    }
+
+    if (Block == EXT4_BLOCK_FILE_HOLE) {
+      FreePool (Buffer);
+      return EFI_NO_MAPPING;
+    }
+
+    Status = Ext4ReadBlocks (Partition, Buffer, 1, Block);
+
+    if (EFI_ERROR (Status)) {
+      FreePool (Buffer);
+      return Status;
+    }
+  }
+
+  Ext4GetExtentInBlockMap (
+    Buffer,
+    Partition->BlockSize / sizeof (UINT32),
+    BlockPath[BlockPathLength - 1],
+    Extent
+    );
+
+  FreePool (Buffer);
+
+  return EFI_SUCCESS;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Collation.c b/Filesystems/Ext4Pkg/Ext4Dxe/Collation.c
new file mode 100644
index 000000000000..91d172b1cb89
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Collation.c
@@ -0,0 +1,173 @@
+/** @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.
+**/
+
+/**
+  Worker function to initialize Unicode Collation support.
+
+  It tries to locate Unicode Collation (2) protocol and matches it with current
+  platform language code.
+
+  @param[in]  DriverHandle         The handle used to open Unicode Collation (2) protocol.
+  @param[in]  ProtocolGuid         The pointer to Unicode Collation (2) protocol GUID.
+  @param[in]  VariableName         The name of the RFC 4646 or ISO 639-2 language variable.
+  @param[in]  DefaultLanguage      The default language in case the RFC 4646 or ISO 639-2 language is absent.
+
+  @retval EFI_SUCCESS          The Unicode Collation (2) protocol has been successfully located.
+  @retval Others               The Unicode Collation (2) protocol has not been located.
+
+**/
+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;
+  UINTN                           Idx;
+  CHAR8                           *BestLanguage;
+
+  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;
+  }
+
+  for (Idx = 0; Idx < NumHandles; Idx++) {
+    Status = gBS->OpenProtocol (
+                    Handles[Idx],
+                    ProtocolGuid,
+                    (VOID **)&Uci,
+                    DriverHandle,
+                    NULL,
+                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
+                    );
+
+    if (EFI_ERROR (Status)) {
+      continue;
+    }
+
+    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/Filesystems/Ext4Pkg/Ext4Dxe/Directory.c b/Filesystems/Ext4Pkg/Ext4Dxe/Directory.c
new file mode 100644
index 000000000000..4441e6d192b6
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Directory.c
@@ -0,0 +1,669 @@
+/** @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;
+}
+
+/**
+   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;
+
+  RequiredSize = Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;
+
+  if (Dirent->rec_len < RequiredSize) {
+    DEBUG ((DEBUG_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) != 0) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+   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     *Name,
+  IN EXT4_PARTITION   *Partition,
+  OUT EXT4_DIR_ENTRY  *Result
+  )
+{
+  EFI_STATUS      Status;
+  CHAR8           *Buf;
+  UINT64          Off;
+  EXT4_INODE      *Inode;
+  UINT64          DirInoSize;
+  UINT32          BlockRemainder;
+  UINTN           Length;
+  EXT4_DIR_ENTRY  *Entry;
+  UINTN           RemainingBlock;
+  CHAR16          DirentUcs2Name[EXT4_NAME_MAX + 1];
+  UINTN           ToCopy;
+  UINTN           BlockOffset;
+
+  Status = EFI_NOT_FOUND;
+  Buf    = AllocatePool (Partition->BlockSize);
+
+  if (Buf == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  Off = 0;
+
+  Inode      = Directory->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) {
+    Length = Partition->BlockSize;
+
+    Status = Ext4Read (Partition, Directory, Buf, Off, &Length);
+
+    if (Status != EFI_SUCCESS) {
+      FreePool (Buf);
+      return Status;
+    }
+
+    for (BlockOffset = 0; BlockOffset < Partition->BlockSize; ) {
+      Entry          = (EXT4_DIR_ENTRY *)(Buf + BlockOffset);
+      RemainingBlock = Partition->BlockSize - BlockOffset;
+      // Check if the minimum directory entry fits inside [BlockOffset, EndOfBlock]
+      if (RemainingBlock < EXT4_MIN_DIR_ENTRY_LEN) {
+        FreePool (Buf);
+        return EFI_VOLUME_CORRUPTED;
+      }
+
+      if (!Ext4ValidDirent (Entry)) {
+        FreePool (Buf);
+        return EFI_VOLUME_CORRUPTED;
+      }
+
+      if ((Entry->name_len > RemainingBlock) || (Entry->rec_len > RemainingBlock)) {
+        // Corrupted filesystem
+        FreePool (Buf);
+        return EFI_VOLUME_CORRUPTED;
+      }
+
+      // Unused entry
+      if (Entry->inode == 0) {
+        BlockOffset += Entry->rec_len;
+        continue;
+      }
+
+      Status = Ext4GetUcs2DirentName (Entry, DirentUcs2Name);
+
+      /* 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.
+        BlockOffset += Entry->rec_len;
+        continue;
+      }
+
+      if ((Entry->name_len == StrLen (Name)) &&
+          !Ext4StrCmpInsensitive (DirentUcs2Name, (CHAR16 *)Name))
+      {
+        ToCopy = MIN (Entry->rec_len, sizeof (EXT4_DIR_ENTRY));
+
+        CopyMem (Result, Entry, ToCopy);
+        FreePool (Buf);
+        return EFI_SUCCESS;
+      }
+
+      BlockOffset += 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.
+   @param[in]      Directory   Pointer to the opened directory.
+
+   @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,
+  IN  EXT4_FILE       *Directory
+  )
+{
+  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;
+  }
+
+  if (StrCmp (FileName, L".") == 0) {
+    // We're using the parent directory's dentry
+    File->Dentry = Directory->Dentry;
+
+    ASSERT (File->Dentry != NULL);
+
+    Ext4RefDentry (File->Dentry);
+  } else if (StrCmp (FileName, L"..") == 0) {
+    // Using the parent's parent's dentry
+    File->Dentry = Directory->Dentry->Parent;
+
+    ASSERT (File->Dentry != NULL);
+
+    Ext4RefDentry (File->Dentry);
+  } else {
+    File->Dentry = Ext4CreateDentry (FileName, Directory->Dentry);
+
+    if (!File->Dentry) {
+      goto Error;
+    }
+  }
+
+  Status = Ext4InitExtentsMap (File);
+
+  if (EFI_ERROR (Status)) {
+    goto Error;
+  }
+
+  File->InodeNum = Entry->inode;
+
+  Ext4SetupFile (File, 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->Dentry != NULL) {
+      Ext4UnrefDentry (File->Dentry);
+    }
+
+    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, Directory);
+}
+
+/**
+  Open the root directory on a volume.
+
+  @param[in]   This A pointer to the volume to open the root directory.
+  @param[out]  Root A pointer to the location to return the opened file handle for the
+                    root directory.
+
+  @retval EFI_SUCCESS          The device was opened.
+  @retval EFI_UNSUPPORTED      This volume does not support the requested file system type.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_ACCESS_DENIED    The service denied access to the file.
+  @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of resources.
+  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
+                               longer supported. Any existing file handles for this volume are
+                               no longer valid. To access the files on the new medium, the
+                               volume must be reopened with OpenVolume().
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4OpenVolume (
+  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
+  OUT EFI_FILE_PROTOCOL               **Root
+  )
+{
+  EXT4_INODE      *RootInode;
+  EFI_STATUS      Status;
+  EXT4_FILE       *RootDir;
+  EXT4_PARTITION  *Partition;
+
+  Partition = (EXT4_PARTITION *)This;
+
+  Status = Ext4ReadInode (Partition, EXT4_ROOT_INODE_NR, &RootInode);
+
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "[ext4] Could not open root inode - error %r\n", Status));
+    return Status;
+  }
+
+  RootDir = AllocateZeroPool (sizeof (EXT4_FILE));
+
+  if (RootDir == NULL) {
+    FreePool (RootInode);
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  RootDir->Inode    = RootInode;
+  RootDir->InodeNum = EXT4_ROOT_INODE_NR;
+
+  Status = Ext4InitExtentsMap (RootDir);
+
+  if (EFI_ERROR (Status)) {
+    FreePool (RootInode);
+    FreePool (RootDir);
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  Ext4SetupFile (RootDir, Partition);
+  *Root = &RootDir->Protocol;
+
+  InsertTailList (&Partition->OpenFiles, &RootDir->OpenFilesListNode);
+
+  ASSERT (Partition->RootDentry != NULL);
+  RootDir->Dentry = Partition->RootDentry;
+
+  Ext4RefDentry (RootDir->Dentry);
+
+  return EFI_SUCCESS;
+}
+
+/**
+   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
+  )
+{
+  EXT4_INODE      *DirIno;
+  EFI_STATUS      Status;
+  UINT64          DirInoSize;
+  UINTN           Len;
+  UINT32          BlockRemainder;
+  EXT4_DIR_ENTRY  Entry;
+  EXT4_FILE       *TempFile;
+  BOOLEAN         ShouldSkip;
+  BOOLEAN         IsDotOrDotDot;
+
+  DirIno     = File->Inode;
+  Status     = EFI_SUCCESS;
+  DirInoSize = EXT4_INODE_SIZE (DirIno);
+
+  DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
+  if (BlockRemainder != 0) {
+    // Directory inodes need to have block aligned sizes
+    return EFI_VOLUME_CORRUPTED;
+  }
+
+  while (TRUE) {
+    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 (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 ((DEBUG_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
+      Status = EFI_VOLUME_CORRUPTED;
+      goto Out;
+    }
+
+    // Check if the entire dir entry length fits in Len
+    if (Len < (UINTN)(EXT4_MIN_DIR_ENTRY_LEN + Entry.name_len)) {
+      Status = EFI_VOLUME_CORRUPTED;
+      goto Out;
+    }
+
+    // We don't care about passing . or .. entries to the caller of ReadDir(),
+    // since they're generally useless entries *and* may break things if too
+    // many callers assume FAT32.
+
+    // Entry.name_len may be 0 if it's a nameless entry, like an unused entry
+    // or a checksum at the end of the directory block.
+    // memcmp (and CompareMem) return 0 when the passed length is 0.
+
+    IsDotOrDotDot = Entry.name_len != 0 &&
+                    (CompareMem (Entry.name, ".", Entry.name_len) == 0 ||
+                     CompareMem (Entry.name, "..", Entry.name_len) == 0);
+
+    // When inode = 0, it's unused.
+    ShouldSkip = Entry.inode == 0 || IsDotOrDotDot;
+
+    if (ShouldSkip) {
+      Offset += Entry.rec_len;
+      continue;
+    }
+
+    Status = Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &Entry, File);
+
+    if (EFI_ERROR (Status)) {
+      goto Out;
+    }
+
+    Status = Ext4GetFileInfo (TempFile, Buffer, OutLength);
+    if (!EFI_ERROR (Status)) {
+      File->Position = Offset + Entry.rec_len;
+    }
+
+    Ext4CloseInternal (TempFile);
+
+    goto Out;
+  }
+
+  Status = EFI_SUCCESS;
+Out:
+  return Status;
+}
+
+/**
+   Removes a dentry from the other's list.
+
+   @param[in out]            Parent       Pointer to the parent EXT4_DENTRY.
+   @param[in out]            ToBeRemoved  Pointer to the child EXT4_DENTRY.
+**/
+STATIC
+VOID
+Ext4RemoveDentry (
+  IN OUT EXT4_DENTRY  *Parent,
+  IN OUT EXT4_DENTRY  *ToBeRemoved
+  )
+{
+  ASSERT (IsNodeInList (&ToBeRemoved->ListNode, &Parent->Children));
+  RemoveEntryList (&ToBeRemoved->ListNode);
+}
+
+/**
+   Adds a dentry to the other's list.
+
+   The dentry that is added to the other one's list gets ->Parent set to Parent,
+   and the parent gets its reference count incremented.
+
+   @param[in out]            Parent       Pointer to the parent EXT4_DENTRY.
+   @param[in out]            ToBeAdded    Pointer to the child EXT4_DENTRY.
+**/
+STATIC
+VOID
+Ext4AddDentry (
+  IN OUT EXT4_DENTRY  *Parent,
+  IN OUT EXT4_DENTRY  *ToBeAdded
+  )
+{
+  ToBeAdded->Parent = Parent;
+  InsertTailList (&Parent->Children, &ToBeAdded->ListNode);
+  Ext4RefDentry (Parent);
+}
+
+/**
+   Creates a new dentry object.
+
+   @param[in]              Name        Name of the dentry.
+   @param[in out opt]      Parent      Parent dentry, if it's not NULL.
+
+   @return The new allocated and initialised dentry.
+           The ref count will be set to 1.
+**/
+EXT4_DENTRY *
+Ext4CreateDentry (
+  IN CONST CHAR16     *Name,
+  IN OUT EXT4_DENTRY  *Parent  OPTIONAL
+  )
+{
+  EXT4_DENTRY  *Dentry;
+  EFI_STATUS   Status;
+
+  Dentry = AllocateZeroPool (sizeof (EXT4_DENTRY));
+
+  if (Dentry == NULL) {
+    return NULL;
+  }
+
+  Dentry->RefCount = 1;
+
+  // This StrCpyS should not fail.
+  Status = StrCpyS (Dentry->Name, ARRAY_SIZE (Dentry->Name), Name);
+
+  ASSERT_EFI_ERROR (Status);
+
+  InitializeListHead (&Dentry->Children);
+
+  if (Parent != NULL) {
+    Ext4AddDentry (Parent, Dentry);
+  }
+
+  DEBUG ((DEBUG_FS, "[ext4] Created dentry %s\n", Name));
+
+  return Dentry;
+}
+
+/**
+   Increments the ref count of the dentry.
+
+   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
+**/
+VOID
+Ext4RefDentry (
+  IN OUT EXT4_DENTRY  *Dentry
+  )
+{
+  UINTN  OldRef;
+
+  OldRef = Dentry->RefCount;
+
+  Dentry->RefCount++;
+
+  // I'm not sure if this (Refcount overflow) is a valid concern,
+  // but it's better to be safe than sorry.
+  ASSERT (OldRef < Dentry->RefCount);
+}
+
+/**
+   Deletes the dentry.
+
+   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
+**/
+STATIC
+VOID
+Ext4DeleteDentry (
+  IN OUT EXT4_DENTRY  *Dentry
+  )
+{
+  if (Dentry->Parent) {
+    Ext4RemoveDentry (Dentry->Parent, Dentry);
+    Ext4UnrefDentry (Dentry->Parent);
+  }
+
+  DEBUG ((DEBUG_FS, "[ext4] Deleted dentry %s\n", Dentry->Name));
+  FreePool (Dentry);
+}
+
+/**
+   Decrements the ref count of the dentry.
+   If the ref count is 0, it's destroyed.
+
+   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
+
+   @retval True if it was destroyed, false if it's alive.
+**/
+BOOLEAN
+Ext4UnrefDentry (
+  IN OUT EXT4_DENTRY  *Dentry
+  )
+{
+  Dentry->RefCount--;
+
+  if (Dentry->RefCount == 0) {
+    Ext4DeleteDentry (Dentry);
+    return TRUE;
+  }
+
+  return FALSE;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c
new file mode 100644
index 000000000000..32da35f7d9f5
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c
@@ -0,0 +1,113 @@
+/** @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 EXT4_DISK_IO (Partition)->ReadDisk (
+                                     EXT4_DISK_IO (Partition),
+                                     EXT4_MEDIA_ID (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
+  )
+{
+  UINT64  Offset;
+  UINTN   Length;
+
+  Offset = MultU64x32 (BlockNumber, Partition->BlockSize);
+  Length = NumberBlocks * Partition->BlockSize;
+
+  // Check for overflow on the block -> byte conversions.
+  // Partition->BlockSize is never 0, so we don't need to check for that.
+
+  if (Offset > DivU64x32 ((UINT64)-1, Partition->BlockSize)) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  if (Length > (UINTN)-1/Partition->BlockSize) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  return Ext4ReadDiskIo (Partition, Buffer, Length, Offset);
+}
+
+/**
+   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;
+  UINTN  Length;
+
+  Length = NumberBlocks * Partition->BlockSize;
+
+  if (Length > (UINTN)-1/Partition->BlockSize) {
+    return NULL;
+  }
+
+  Buf = AllocatePool (Length);
+
+  if (Buf == NULL) {
+    return NULL;
+  }
+
+  if (Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) != EFI_SUCCESS) {
+    FreePool (Buf);
+    return NULL;
+  }
+
+  return Buf;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h
new file mode 100644
index 000000000000..4fd91a423324
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h
@@ -0,0 +1,475 @@
+/** @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_ENCRYPT_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_FAST_SYMLINK_MAX_SIZE  EXT4_NR_BLOCKS * sizeof(UINT32)
+
+#define EXT4_GOOD_OLD_INODE_SIZE  128U
+
+typedef 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;
+} EXT4_OSD2_LINUX;
+
+typedef 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;
+} EXT4_OSD2_HURD;
+
+typedef union {
+  // Note: Toolchain-specific defines (such as "linux") stops us from using
+  // simpler names down here.
+  EXT4_OSD2_LINUX    data_linux;
+  EXT4_OSD2_HURD     data_hurd;
+} EXT4_OSD2;
+
+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;
+
+  EXT4_OSD2    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;
+
+/**
+ * EXT4 has this feature called uninitialized extents:
+ * An extent has a maximum of 32768 blocks (2^15 or 1 << 15).
+ * When we find an extent with > 32768 blocks, this extent is called
+ * uninitialized. Long story short, it's an extent that behaves as a file hole
+ * but has blocks already allocated.
+ */
+#define EXT4_EXTENT_MAX_INITIALIZED  (1 << 15)
+
+typedef UINT64  EXT4_BLOCK_NR;
+typedef UINT32  EXT2_BLOCK_NR;
+typedef UINT32  EXT4_INO_NR;
+
+// 2 is always the root inode number in ext4
+#define EXT4_ROOT_INODE_NR  2
+
+#define EXT4_BLOCK_FILE_HOLE  0
+
+#endif
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
new file mode 100644
index 000000000000..2a4f5a7bd0ef
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
@@ -0,0 +1,844 @@
+/** @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*
+
+/**
+  Retrieves a Unicode string that is the user-readable name of the EFI Driver.
+
+  @param[in]  This       A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
+  @param[in]  Language   A pointer to a three-character ISO 639-2 language identifier.
+                     This is the language of the driver name that that the caller
+                     is requesting, and it must match one of the languages specified
+                     in SupportedLanguages.  The number of languages supported by a
+                     driver is up to the driver writer.
+  @param[out]  DriverName A pointer to the Unicode string to return.  This Unicode string
+                     is the name of the driver specified by This in the language
+                     specified by Language.
+
+  @retval EFI_SUCCESS           The Unicode string for the Driver specified by This
+                                and the language specified by Language was returned
+                                in DriverName.
+  @retval EFI_INVALID_PARAMETER Language is NULL.
+  @retval EFI_INVALID_PARAMETER DriverName is NULL.
+  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
+                                language specified by Language.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4ComponentNameGetDriverName (
+  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
+  IN  CHAR8                        *Language,
+  OUT CHAR16                       **DriverName
+  );
+
+/**
+  Retrieves a Unicode string that is the user readable name of the controller
+  that is being managed by an EFI Driver.
+
+  @param[in]  This             A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
+  @param[in]  ControllerHandle The handle of a controller that the driver specified by
+                               This is managing.  This handle specifies the controller
+                               whose name is to be returned.
+  @param[in]  ChildHandle      The handle of the child controller to retrieve the name
+                               of.  This is an optional parameter that may be NULL.  It
+                               will be NULL for device drivers.  It will also be NULL
+                               for a bus drivers that wish to retrieve the name of the
+                               bus controller.  It will not be NULL for a bus driver
+                               that wishes to retrieve the name of a child controller.
+  @param[in]  Language         A pointer to a three character ISO 639-2 language
+                               identifier.  This is the language of the controller name
+                               that the caller is requesting, and it must match one
+                               of the languages specified in SupportedLanguages.  The
+                               number of languages supported by a driver is up to the
+                               driver writer.
+  @param[out]  ControllerName  A pointer to the Unicode string to return.  This Unicode
+                               string is the name of the controller specified by
+                               ControllerHandle and ChildHandle in the language specified
+                               by Language, from the point of view of the driver specified
+                               by This.
+
+  @retval EFI_SUCCESS           The Unicode string for the user-readable name in the
+                                language specified by Language for the driver
+                                specified by This was returned in DriverName.
+  @retval EFI_INVALID_PARAMETER ControllerHandle is NULL.
+  @retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid EFI_HANDLE.
+  @retval EFI_INVALID_PARAMETER Language is NULL.
+  @retval EFI_INVALID_PARAMETER ControllerName is NULL.
+  @retval EFI_UNSUPPORTED       The driver specified by This is not currently managing
+                                the controller specified by ControllerHandle and
+                                ChildHandle.
+  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
+                                language specified by Language.
+
+**/
+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
+
+/**
+  Tests to see if this driver supports a given controller. If a child device is provided,
+  it further tests to see if this driver supports creating a handle for the specified child device.
+
+  This function checks to see if the driver specified by This supports the device specified by
+  ControllerHandle. Drivers will typically use the device path attached to
+  ControllerHandle and/or the services from the bus I/O abstraction attached to
+  ControllerHandle to determine if the driver supports ControllerHandle. This function
+  may be called many times during platform initialization. In order to reduce boot times, the tests
+  performed by this function must be very small, and take as little time as possible to execute. This
+  function must not change the state of any hardware devices, and this function must be aware that the
+  device specified by ControllerHandle may already be managed by the same driver or a
+  different driver. This function must match its calls to AllocatePages() with FreePages(),
+  AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol().
+  Because ControllerHandle may have been previously started by the same driver, if a protocol is
+  already in the opened state, then it must not be closed with CloseProtocol(). This is required
+  to guarantee the state of ControllerHandle is not modified by this function.
+
+  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
+  @param[in]  ControllerHandle     The handle of the controller to test. This handle
+                                   must support a protocol interface that supplies
+                                   an I/O abstraction to the driver.
+  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
+                                   parameter is ignored by device drivers, and is optional for bus
+                                   drivers. For bus drivers, if this parameter is not NULL, then
+                                   the bus driver must determine if the bus controller specified
+                                   by ControllerHandle and the child controller specified
+                                   by RemainingDevicePath are both supported by this
+                                   bus driver.
+
+  @retval EFI_SUCCESS              The device specified by ControllerHandle and
+                                   RemainingDevicePath is supported by the driver specified by This.
+  @retval EFI_ALREADY_STARTED      The device specified by ControllerHandle and
+                                   RemainingDevicePath is already being managed by the driver
+                                   specified by This.
+  @retval EFI_ACCESS_DENIED        The device specified by ControllerHandle and
+                                   RemainingDevicePath is already being managed by a different
+                                   driver or an application that requires exclusive access.
+                                   Currently not implemented.
+  @retval EFI_UNSUPPORTED          The device specified by ControllerHandle and
+                                   RemainingDevicePath is not supported by the driver specified by This.
+**/
+EFI_STATUS
+EFIAPI
+Ext4IsBindingSupported (
+  IN EFI_DRIVER_BINDING_PROTOCOL  *BindingProtocol,
+  IN EFI_HANDLE                   ControllerHandle,
+  IN EFI_DEVICE_PATH              *RemainingDevicePath  OPTIONAL
+  );
+
+/**
+  Starts a device controller or a bus controller.
+
+  The Start() function is designed to be invoked from the EFI boot service ConnectController().
+  As a result, much of the error checking on the parameters to Start() has been moved into this
+  common boot service. It is legal to call Start() from other locations,
+  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
+  1. ControllerHandle must be a valid EFI_HANDLE.
+  2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned
+     EFI_DEVICE_PATH_PROTOCOL.
+  3. Prior to calling Start(), the Supported() function for the driver specified by This must
+     have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS.
+
+  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
+  @param[in]  ControllerHandle     The handle of the controller to start. This handle
+                                   must support a protocol interface that supplies
+                                   an I/O abstraction to the driver.
+  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
+                                   parameter is ignored by device drivers, and is optional for bus
+                                   drivers. For a bus driver, if this parameter is NULL, then handles
+                                   for all the children of Controller are created by this driver.
+                                   If this parameter is not NULL and the first Device Path Node is
+                                   not the End of Device Path Node, then only the handle for the
+                                   child device specified by the first Device Path Node of
+                                   RemainingDevicePath is created by this driver.
+                                   If the first Device Path Node of RemainingDevicePath is
+                                   the End of Device Path Node, no child handle is created by this
+                                   driver.
+
+  @retval EFI_SUCCESS              The device was started.
+  @retval EFI_DEVICE_ERROR         The device could not be started due to a device error.Currently not implemented.
+  @retval EFI_OUT_OF_RESOURCES     The request could not be completed due to a lack of resources.
+  @retval Others                   The driver failded to start the device.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4Bind (
+  IN EFI_DRIVER_BINDING_PROTOCOL  *BindingProtocol,
+  IN EFI_HANDLE                   ControllerHandle,
+  IN EFI_DEVICE_PATH              *RemainingDevicePath  OPTIONAL
+  );
+
+/**
+  Stops a device controller or a bus controller.
+
+  The Stop() function is designed to be invoked from the EFI boot service DisconnectController().
+  As a result, much of the error checking on the parameters to Stop() has been moved
+  into this common boot service. It is legal to call Stop() from other locations,
+  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
+  1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this
+     same driver's Start() function.
+  2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid
+     EFI_HANDLE. In addition, all of these handles must have been created in this driver's
+     Start() function, and the Start() function must have called OpenProtocol() on
+     ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
+
+  @param[in]  This              A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
+  @param[in]  ControllerHandle  A handle to the device being stopped. The handle must
+                                support a bus specific I/O protocol for the driver
+                                to use to stop the device.
+  @param[in]  NumberOfChildren  The number of child device handles in ChildHandleBuffer.
+  @param[in]  ChildHandleBuffer An array of child handles to be freed. May be NULL
+                                if NumberOfChildren is 0.
+
+  @retval EFI_SUCCESS           The device was stopped.
+  @retval EFI_DEVICE_ERROR      The device could not be stopped due to a device error.
+
+**/
+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 =
+{
+  .Supported           = Ext4IsBindingSupported,
+  .Start               = Ext4Bind,
+  .Stop                = Ext4Stop,
+  .Version             = EXT4_DRIVER_VERSION,
+  .ImageHandle         = NULL,
+  .DriverBindingHandle = NULL
+};
+
+/**
+  Retrieves a Unicode string that is the user readable name of the controller
+  that is being managed by an EFI Driver.
+
+  @param[in]  This             A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
+  @param[in]  ControllerHandle The handle of a controller that the driver specified by
+                               This is managing.  This handle specifies the controller
+                               whose name is to be returned.
+  @param[in]  ChildHandle      The handle of the child controller to retrieve the name
+                               of.  This is an optional parameter that may be NULL.  It
+                               will be NULL for device drivers.  It will also be NULL
+                               for a bus drivers that wish to retrieve the name of the
+                               bus controller.  It will not be NULL for a bus driver
+                               that wishes to retrieve the name of a child controller.
+  @param[in]  Language         A pointer to a three character ISO 639-2 language
+                               identifier.  This is the language of the controller name
+                               that the caller is requesting, and it must match one
+                               of the languages specified in SupportedLanguages.  The
+                               number of languages supported by a driver is up to the
+                               driver writer.
+  @param[out]  ControllerName   A pointer to the Unicode string to return.  This Unicode
+                                string is the name of the controller specified by
+                                ControllerHandle and ChildHandle in the language specified
+                                by Language, from the point of view of the driver specified
+                                by This.
+
+  @retval EFI_SUCCESS           The Unicode string for the user-readable name in the
+                                language specified by Language for the driver
+                                specified by This was returned in DriverName.
+  @retval EFI_INVALID_PARAMETER ControllerHandle is NULL.
+  @retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid EFI_HANDLE.
+  @retval EFI_INVALID_PARAMETER Language is NULL.
+  @retval EFI_INVALID_PARAMETER ControllerName is NULL.
+  @retval EFI_UNSUPPORTED       The driver specified by This is not currently managing
+                                the controller specified by ControllerHandle and
+                                ChildHandle.
+  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
+                                language specified by Language.
+
+**/
+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
+  )
+{
+  EFI_STATUS  Status;
+
+  if (ChildHandle != NULL) {
+    return EFI_UNSUPPORTED;
+  }
+
+  // Test if the driver manages ControllHandle
+  Status = EfiTestManagedDevice (
+             ControllerHandle,
+             gExt4BindingProtocol.DriverBindingHandle,
+             &gEfiDiskIoProtocolGuid
+             );
+
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  return LookupUnicodeString2 (
+           Language,
+           This->SupportedLanguages,
+           mExt4ControllerNameTable,
+           ControllerName,
+           (BOOLEAN)(This == &gExt4ComponentName)
+           );
+}
+
+/**
+  Retrieves a Unicode string that is the user-readable name of the EFI Driver.
+
+  @param[in]  This        A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
+  @param[in]  Language    A pointer to a three-character ISO 639-2 language identifier.
+                          This is the language of the driver name that that the caller
+                          is requesting, and it must match one of the languages specified
+                          in SupportedLanguages.  The number of languages supported by a
+                          driver is up to the driver writer.
+  @param[out]  DriverName A pointer to the Unicode string to return.  This Unicode string
+                          is the name of the driver specified by This in the language
+                          specified by Language.
+
+  @retval EFI_SUCCESS           The Unicode string for the Driver specified by This
+                                and the language specified by Language was returned
+                                in DriverName.
+  @retval EFI_INVALID_PARAMETER Language is NULL.
+  @retval EFI_INVALID_PARAMETER DriverName is NULL.
+  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
+                                language specified by Language.
+
+**/
+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)
+           );
+}
+
+/**
+  Stops a device controller or a bus controller.
+
+  The Stop() function is designed to be invoked from the EFI boot service DisconnectController().
+  As a result, much of the error checking on the parameters to Stop() has been moved
+  into this common boot service. It is legal to call Stop() from other locations,
+  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
+  1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this
+     same driver's Start() function.
+  2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid
+     EFI_HANDLE. In addition, all of these handles must have been created in this driver's
+     Start() function, and the Start() function must have called OpenProtocol() on
+     ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
+
+  @param[in]  This              A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
+  @param[in]  ControllerHandle  A handle to the device being stopped. The handle must
+                                support a bus specific I/O protocol for the driver
+                                to use to stop the device.
+  @param[in]  NumberOfChildren  The number of child device handles in ChildHandleBuffer.
+  @param[in]  ChildHandleBuffer An array of child handles to be freed. May be NULL
+                                if NumberOfChildren is 0.
+
+  @retval EFI_SUCCESS           The device was stopped.
+  @retval EFI_DEVICE_ERROR      The device could not be stopped due to a device error.
+
+**/
+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 = EXT4_DISK_IO2 (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;
+}
+
+/**
+   Ext4Dxe Driver's entry point.
+
+   Called at load time.
+
+   @param[in]    ImageHandle   Handle to the image.
+   @param[in]    SystemTable   Pointer to the EFI_SYSTEM_TABLE.
+   @return Result of the load.
+**/
+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);
+}
+
+/**
+   Ext4Dxe Driver's unload callback.
+
+   Called at unload time.
+
+   @param[in]    ImageHandle   Handle to the image.
+   @return Result of the unload.
+**/
+EFI_STATUS
+EFIAPI
+Ext4Unload (
+  IN EFI_HANDLE  ImageHandle
+  )
+{
+  EFI_STATUS  Status;
+  EFI_HANDLE  *DeviceHandleBuffer;
+  UINTN       DeviceHandleCount;
+  UINTN       Index;
+  EFI_HANDLE  Handle;
+
+  Status = gBS->LocateHandleBuffer (
+                  AllHandles,
+                  NULL,
+                  NULL,
+                  &DeviceHandleCount,
+                  &DeviceHandleBuffer
+                  );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  for (Index = 0; Index < DeviceHandleCount; Index++) {
+    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;
+}
+
+/**
+  Tests to see if this driver supports a given controller. If a child device is provided,
+  it further tests to see if this driver supports creating a handle for the specified child device.
+
+  This function checks to see if the driver specified by This supports the device specified by
+  ControllerHandle. Drivers will typically use the device path attached to
+  ControllerHandle and/or the services from the bus I/O abstraction attached to
+  ControllerHandle to determine if the driver supports ControllerHandle. This function
+  may be called many times during platform initialization. In order to reduce boot times, the tests
+  performed by this function must be very small, and take as little time as possible to execute. This
+  function must not change the state of any hardware devices, and this function must be aware that the
+  device specified by ControllerHandle may already be managed by the same driver or a
+  different driver. This function must match its calls to AllocatePages() with FreePages(),
+  AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol().
+  Because ControllerHandle may have been previously started by the same driver, if a protocol is
+  already in the opened state, then it must not be closed with CloseProtocol(). This is required
+  to guarantee the state of ControllerHandle is not modified by this function.
+
+  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
+  @param[in]  ControllerHandle     The handle of the controller to test. This handle
+                                   must support a protocol interface that supplies
+                                   an I/O abstraction to the driver.
+  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
+                                   parameter is ignored by device drivers, and is optional for bus
+                                   drivers. For bus drivers, if this parameter is not NULL, then
+                                   the bus driver must determine if the bus controller specified
+                                   by ControllerHandle and the child controller specified
+                                   by RemainingDevicePath are both supported by this
+                                   bus driver.
+
+  @retval EFI_SUCCESS              The device specified by ControllerHandle and
+                                   RemainingDevicePath is supported by the driver specified by This.
+  @retval EFI_ALREADY_STARTED      The device specified by ControllerHandle and
+                                   RemainingDevicePath is already being managed by the driver
+                                   specified by This.
+  @retval EFI_ACCESS_DENIED        The device specified by ControllerHandle and
+                                   RemainingDevicePath is already being managed by a different
+                                   driver or an application that requires exclusive access.
+  @retval EFI_UNSUPPORTED          The device specified by ControllerHandle and
+                                   RemainingDevicePath is not supported by the driver specified by This.
+**/
+EFI_STATUS
+EFIAPI
+Ext4IsBindingSupported (
+  IN EFI_DRIVER_BINDING_PROTOCOL  *BindingProtocol,
+  IN EFI_HANDLE                   ControllerHandle,
+  IN EFI_DEVICE_PATH              *RemainingDevicePath  OPTIONAL
+  )
+{
+  EFI_STATUS             Status;
+  EFI_DISK_IO_PROTOCOL   *DiskIo;
+  EFI_BLOCK_IO_PROTOCOL  *BlockIo;
+
+  DiskIo  = NULL;
+  BlockIo = NULL;
+
+  //
+  // Open the IO Abstraction(s) needed to perform the supported test
+  //
+  Status = gBS->OpenProtocol (
+                  ControllerHandle,
+                  &gEfiDiskIoProtocolGuid,
+                  (VOID **)&DiskIo,
+                  BindingProtocol->DriverBindingHandle,
+                  ControllerHandle,
+                  EFI_OPEN_PROTOCOL_BY_DRIVER
+                  );
+
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  //
+  // Open the IO Abstraction(s) needed to perform the supported test
+  //
+  Status = gBS->OpenProtocol (
+                  ControllerHandle,
+                  &gEfiBlockIoProtocolGuid,
+                  (VOID **)&BlockIo,
+                  BindingProtocol->DriverBindingHandle,
+                  ControllerHandle,
+                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
+                  );
+
+  if (!EFI_ERROR (Status)) {
+    if (!Ext4SuperblockCheckMagic (DiskIo, BlockIo)) {
+      Status = EFI_UNSUPPORTED;
+    }
+  }
+
+  //
+  // Close the I/O Abstraction(s) used to perform the supported test
+  //
+  if (DiskIo != NULL) {
+    gBS->CloseProtocol (
+           ControllerHandle,
+           &gEfiDiskIoProtocolGuid,
+           BindingProtocol->DriverBindingHandle,
+           ControllerHandle
+           );
+  }
+
+  if (BlockIo != NULL) {
+    gBS->CloseProtocol (
+           ControllerHandle,
+           &gEfiBlockIoProtocolGuid,
+           BindingProtocol->DriverBindingHandle,
+           ControllerHandle
+           );
+  }
+
+  return Status;
+}
+
+/**
+  Starts a device controller or a bus controller.
+
+  The Start() function is designed to be invoked from the EFI boot service ConnectController().
+  As a result, much of the error checking on the parameters to Start() has been moved into this
+  common boot service. It is legal to call Start() from other locations,
+  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
+  1. ControllerHandle must be a valid EFI_HANDLE.
+  2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned
+     EFI_DEVICE_PATH_PROTOCOL.
+  3. Prior to calling Start(), the Supported() function for the driver specified by This must
+     have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS.
+
+  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
+  @param[in]  ControllerHandle     The handle of the controller to start. This handle
+                                   must support a protocol interface that supplies
+                                   an I/O abstraction to the driver.
+  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
+                                   parameter is ignored by device drivers, and is optional for bus
+                                   drivers. For a bus driver, if this parameter is NULL, then handles
+                                   for all the children of Controller are created by this driver.
+                                   If this parameter is not NULL and the first Device Path Node is
+                                   not the End of Device Path Node, then only the handle for the
+                                   child device specified by the first Device Path Node of
+                                   RemainingDevicePath is created by this driver.
+                                   If the first Device Path Node of RemainingDevicePath is
+                                   the End of Device Path Node, no child handle is created by this
+                                   driver.
+
+  @retval EFI_SUCCESS              The device was started.
+  @retval EFI_DEVICE_ERROR         The device could not be started due to a device error.Currently not implemented.
+  @retval EFI_OUT_OF_RESOURCES     The request could not be completed due to a lack of resources.
+  @retval Others                   The driver failded to start the device.
+
+**/
+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;
+  BlockIo = NULL;
+  DiskIo  = NULL;
+
+  Status = gBS->OpenProtocol (
+                  ControllerHandle,
+                  &gEfiDiskIoProtocolGuid,
+                  (VOID **)&DiskIo,
+                  BindingProtocol->ImageHandle,
+                  ControllerHandle,
+                  EFI_OPEN_PROTOCOL_BY_DRIVER
+                  );
+
+  if (EFI_ERROR (Status)) {
+    goto Error;
+  }
+
+  DEBUG ((DEBUG_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 ((DEBUG_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;
+  }
+
+  Status = Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, BlockIo);
+
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "[ext4] Error mounting: %r\n", Status));
+    // Falls through to Error
+  } else {
+    return 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/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
new file mode 100644
index 000000000000..adf3c13f6ea9
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
@@ -0,0 +1,1232 @@
+/** @file
+  Common header for the driver
+
+  Copyright (c) 2021 - 2022 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/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/OrderedCollectionLib.h>
+#include <Library/PcdLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiDriverEntryPoint.h>
+#include <Library/UefiLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+
+#include "Ext4Disk.h"
+
+#define SYMLOOP_MAX    8
+#define EXT4_NAME_MAX  255
+//
+// We need to specify path length limit for security purposes, to prevent possible
+// overflows and dead-loop conditions. Originally this limit is absent in FS design,
+// but present in UNIX distros and shell environments, which may varies from 1024 to 4096.
+//
+#define EXT4_EFI_PATH_MAX    4096
+#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_Dentry  EXT4_DENTRY;
+
+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_DENTRY                        *RootDentry;
+} EXT4_PARTITION;
+
+/**
+   This structure represents a directory entry inside our directory entry tree.
+   For now, it will be used as a way to track file names inside our opening
+   code, but it may very well be used as a directory cache in the future.
+   Because it's not being used as a directory cache right now,
+   an EXT4_DENTRY structure is not necessarily unique name-wise in the list of
+   children. Therefore, the dentry tree does not accurately reflect the
+   filesystem structure.
+ */
+struct _Ext4_Dentry {
+  UINTN                  RefCount;
+  CHAR16                 Name[EXT4_NAME_MAX + 1];
+  EXT4_INO_NR            Inode;
+  struct _Ext4_Dentry    *Parent;
+  LIST_ENTRY             Children;
+  LIST_ENTRY             ListNode;
+};
+
+#define EXT4_DENTRY_FROM_DENTRY_LIST(Node)  BASE_CR(Node, EXT4_DENTRY, ListNode)
+
+/**
+   Creates a new dentry object.
+
+   @param[in]              Name        Name of the dentry.
+   @param[in out opt]      Parent      Parent dentry, if it's not NULL.
+
+   @return The new allocated and initialised dentry.
+           The ref count will be set to 1.
+**/
+EXT4_DENTRY *
+Ext4CreateDentry (
+  IN CONST CHAR16     *Name,
+  IN OUT EXT4_DENTRY  *Parent OPTIONAL
+  );
+
+/**
+   Increments the ref count of the dentry.
+
+   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
+**/
+VOID
+Ext4RefDentry (
+  IN OUT EXT4_DENTRY  *Dentry
+  );
+
+/**
+   Decrements the ref count of the dentry.
+   If the ref count is 0, it's destroyed.
+
+   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
+
+   @retval True if it was destroyed, false if it's alive.
+**/
+BOOLEAN
+Ext4UnrefDentry (
+  IN OUT EXT4_DENTRY  *Dentry
+  );
+
+/**
+   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.
+**/
+#define EXT4_BLOCK_IO(Partition)  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.
+**/
+#define EXT4_DISK_IO(Partition)  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.
+**/
+#define EXT4_DISK_IO2(Partition)  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.
+**/
+#define EXT4_MEDIA_ID(Partition)  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.
+**/
+#define EXT4_IS_64_BIT(Partition)                                              \
+  ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_64BIT) != 0)
+
+/**
+   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.
+**/
+#define EXT4_BLOCK_NR_FROM_HALFS(Partition, Low, High)                         \
+  EXT4_IS_64_BIT(Partition) ? (Low | LShiftU64(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.
+**/
+EXT4_BLOCK_GROUP_DESC *
+Ext4GetBlockGroupDesc (
+  IN EXT4_PARTITION  *Partition,
+  IN UINT32          BlockGroup
+  );
+
+/**
+   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.
+**/
+#define EXT4_BLOCK_TO_BYTES(Partition, Block)                                  \
+  MultU64x32(Block, Partition->BlockSize)
+
+/**
+   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.
+**/
+#define EXT4_INODE_SIZE(Inode)                                                 \
+  (LShiftU64(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;
+  UINT32                SymLoops;
+
+  EXT4_PARTITION        *Partition;
+
+  ORDERED_COLLECTION    *ExtentsMap;
+
+  LIST_ENTRY            OpenFilesListNode;
+
+  // Owning reference to this file's directory entry.
+  EXT4_DENTRY           *Dentry;
+};
+
+#define EXT4_FILE_FROM_THIS(This)  BASE_CR ((This), EXT4_FILE, Protocol)
+
+#define EXT4_FILE_FROM_OPEN_FILES_NODE(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.
+   @param[in]      Directory   Pointer to the opened directory.
+
+   @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,
+  IN EXT4_FILE       *Directory
+  );
+
+/**
+   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
+
+/**
+  Open the root directory on a volume.
+
+  @param[in]   This A pointer to the volume to open the root directory.
+  @param[out]  Root A pointer to the location to return the opened file handle
+for the root directory.
+
+  @retval EFI_SUCCESS          The device was opened.
+  @retval EFI_UNSUPPORTED      This volume does not support the requested file
+system type.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_ACCESS_DENIED    The service denied access to the file.
+  @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of
+resources.
+  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the
+medium is no longer supported. Any existing file handles for this volume are no
+longer valid. To access the files on the new medium, the volume must be reopened
+with OpenVolume().
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4OpenVolume (
+  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
+  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
+  );
+
+/**
+  Opens a new file relative to the source file's location.
+
+  @param[out] FoundFile  A pointer to the location to return the opened handle for the new
+                         file.
+  @param[in]  Source     A pointer to the EXT4_FILE instance that is the file
+                         handle to the source location. This would typically be an open
+                         handle to a directory.
+  @param[in]  FileName   The Null-terminated string of the name of the file to be opened.
+                         The file name may contain the following path modifiers: "\", ".",
+                         and "..".
+  @param[in]  OpenMode   The mode to open the file. The only valid combinations that the
+                         file may be opened with are: Read, Read/Write, or Create/Read/Write.
+  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case these are the
+                         attribute bits for the newly created file.
+
+  @retval EFI_SUCCESS          The file was opened.
+  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
+                               longer supported.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a file for write
+                               when the media is write-protected.
+  @retval EFI_ACCESS_DENIED    The service denied access to the file.
+  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
+  @retval EFI_VOLUME_FULL      The volume is full.
+
+**/
+EFI_STATUS
+Ext4OpenInternal (
+  OUT EXT4_FILE  **FoundFile,
+  IN  EXT4_FILE  *Source,
+  IN  CHAR16     *FileName,
+  IN  UINT64     OpenMode,
+  IN  UINT64     Attributes
+  );
+
+/**
+   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
+
+/**
+  Opens a new file relative to the source file's location.
+
+  @param[in]  This       A pointer to the EFI_FILE_PROTOCOL instance that is the
+file handle to the source location. This would typically be an open handle to a
+directory.
+  @param[out] NewHandle  A pointer to the location to return the opened handle
+for the new file.
+  @param[in]  FileName   The Null-terminated string of the name of the file to
+be opened. The file name may contain the following path modifiers: "\", ".", and
+"..".
+  @param[in]  OpenMode   The mode to open the file. The only valid combinations
+that the file may be opened with are: Read, Read/Write, or Create/Read/Write.
+  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case
+these are the attribute bits for the newly created file.
+
+  @retval EFI_SUCCESS          The file was opened.
+  @retval EFI_NOT_FOUND        The specified file could not be found on the
+device.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the
+medium is no longer supported.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a
+file for write when the media is write-protected.
+  @retval EFI_ACCESS_DENIED    The service denied access to the file.
+  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the
+file.
+  @retval EFI_VOLUME_FULL      The volume is full.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4Open (
+  IN EFI_FILE_PROTOCOL   *This,
+  OUT EFI_FILE_PROTOCOL  **NewHandle,
+  IN CHAR16              *FileName,
+  IN UINT64              OpenMode,
+  IN UINT64              Attributes
+  );
+
+/**
+  Closes a specified file handle.
+
+  @param[in]  This          A pointer to the EFI_FILE_PROTOCOL instance that is
+the file handle to close.
+
+  @retval EFI_SUCCESS   The file was closed.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4Close (
+  IN EFI_FILE_PROTOCOL  *This
+  );
+
+/**
+  Close and delete the file handle.
+
+  @param[in]  This                     A pointer to the EFI_FILE_PROTOCOL
+instance that is the handle to the file to delete.
+
+  @retval EFI_SUCCESS              The file was closed and deleted, and the
+handle was closed.
+  @retval EFI_WARN_DELETE_FAILURE  The handle was closed, but the file was not
+deleted.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4Delete (
+  IN EFI_FILE_PROTOCOL  *This
+  );
+
+/**
+  Reads data from a file.
+
+  @param[in]      This             A pointer to the EFI_FILE_PROTOCOL instance
+that is the file handle to read data from.
+  @param[in out]  BufferSize       On input, the size of the Buffer. On output,
+the amount of data returned in Buffer. In both cases, the size is measured in
+bytes.
+  @param[out]     Buffer           The buffer into which the data is read.
+
+  @retval EFI_SUCCESS          Data was read.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_DEVICE_ERROR     An attempt was made to read from a deleted file.
+  @retval EFI_DEVICE_ERROR     On entry, the current file position is beyond the
+end of the file.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current
+directory entry. BufferSize has been updated with the size needed to complete
+the request.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4ReadFile (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN OUT UINTN          *BufferSize,
+  OUT VOID              *Buffer
+  );
+
+/**
+  Writes data to a file.
+
+  @param[in]      This        A pointer to the EFI_FILE_PROTOCOL instance that
+is the file handle to write data to.
+  @param[in out]  BufferSize  On input, the size of the Buffer. On output, the
+amount of data actually written. In both cases, the size is measured in bytes.
+  @param[in]      Buffer      The buffer of data to write.
+
+  @retval EFI_SUCCESS          Data was written.
+  @retval EFI_UNSUPPORTED      Writes to open directory files are not supported.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_DEVICE_ERROR     An attempt was made to write to a deleted file.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  The file or medium is write-protected.
+  @retval EFI_ACCESS_DENIED    The file was opened read only.
+  @retval EFI_VOLUME_FULL      The volume is full.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4WriteFile (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN OUT UINTN          *BufferSize,
+  IN VOID               *Buffer
+  );
+
+/**
+  Returns a file's current position.
+
+  @param[in]   This            A pointer to the EFI_FILE_PROTOCOL instance that
+is the file handle to get the current position on.
+  @param[out]  Position        The address to return the file's current position
+value.
+
+  @retval EFI_SUCCESS      The position was returned.
+  @retval EFI_UNSUPPORTED  The request is not valid on open directories.
+  @retval EFI_DEVICE_ERROR An attempt was made to get the position from a
+deleted file.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4GetPosition (
+  IN EFI_FILE_PROTOCOL  *This,
+  OUT UINT64            *Position
+  );
+
+/**
+  Sets a file's current position.
+
+  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that
+is the file handle to set the requested position on.
+  @param[in] Position        The byte position from the start of the file to
+set.
+
+  @retval EFI_SUCCESS      The position was set.
+  @retval EFI_UNSUPPORTED  The seek request for nonzero is not valid on open
+                           directories.
+  @retval EFI_DEVICE_ERROR An attempt was made to set the position of a deleted
+file.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4SetPosition (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN UINT64             Position
+  );
+
+/**
+  Returns information about a file.
+
+  @param[in]      This            A pointer to the EFI_FILE_PROTOCOL instance
+that is the file handle the requested information is for.
+  @param[in]      InformationType The type identifier for the information being
+requested.
+  @param[in out]  BufferSize      On input, the size of Buffer. On output, the
+amount of data returned in Buffer. In both cases, the size is measured in bytes.
+  @param[out]     Buffer          A pointer to the data buffer to return. The
+buffer's type is indicated by InformationType.
+
+  @retval EFI_SUCCESS          The information was returned.
+  @retval EFI_UNSUPPORTED      The InformationType is not known.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current
+directory entry. BufferSize has been updated with the size needed to complete
+                               the request.
+**/
+EFI_STATUS
+EFIAPI
+Ext4GetInfo (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN EFI_GUID           *InformationType,
+  IN OUT UINTN          *BufferSize,
+  OUT VOID              *Buffer
+  );
+
+/**
+  Sets information about a file.
+
+  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that
+is the file handle the information is for.
+  @param[in]  InformationType The type identifier for the information being set.
+  @param[in]  BufferSize      The size, in bytes, of Buffer.
+  @param[in]  Buffer          A pointer to the data buffer to write. The
+buffer's type is indicated by InformationType.
+
+  @retval EFI_SUCCESS          The information was set.
+  @retval EFI_UNSUPPORTED      The InformationType is not known.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_INFO_ID and the media
+is read-only.
+  @retval EFI_WRITE_PROTECTED  InformationType is
+EFI_FILE_PROTOCOL_SYSTEM_INFO_ID and the media is read only.
+  @retval EFI_WRITE_PROTECTED  InformationType is
+EFI_FILE_SYSTEM_VOLUME_LABEL_ID and the media is read-only.
+  @retval EFI_ACCESS_DENIED    An attempt is made to change the name of a file
+to a file that is already present.
+  @retval EFI_ACCESS_DENIED    An attempt is being made to change the
+EFI_FILE_DIRECTORY Attribute.
+  @retval EFI_ACCESS_DENIED    An attempt is being made to change the size of a
+directory.
+  @retval EFI_ACCESS_DENIED    InformationType is EFI_FILE_INFO_ID and the file
+was opened read-only and an attempt is being made to modify a field other than
+Attribute.
+  @retval EFI_VOLUME_FULL      The volume is full.
+  @retval EFI_BAD_BUFFER_SIZE  BufferSize is smaller than the size of the type
+indicated by InformationType.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4SetInfo (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN EFI_GUID           *InformationType,
+  IN UINTN              BufferSize,
+  IN 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 symlink.
+
+   @param[in]      File          Pointer to the opened file.
+
+   @return BOOLEAN         Whether file is a symlink
+**/
+BOOLEAN
+Ext4FileIsSymlink (
+  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.
+**/
+#define Ext4FileIsOpenable(File)  (Ext4FileIsReg (File) || Ext4FileIsDir (File) || Ext4FileIsSymlink (File))
+
+#define EXT4_INODE_HAS_FIELD(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 (
+  IN 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 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.
+**/
+#define EXT4_HAS_RO_COMPAT(Partition, RoCompatFeatureSet)                      \
+  ((Partition->FeaturesRoCompat & RoCompatFeatureSet) == RoCompatFeatureSet)
+
+/**
+   Verifies the existance of a particular compat feature set.
+   @param[in]      Partition           Pointer to the opened EXT4 partition.
+   @param[in]      CompatFeatureSet  Feature set to test.
+
+   @return TRUE if all features are supported, else FALSE.
+**/
+#define EXT4_HAS_COMPAT(Partition, CompatFeatureSet)                           \
+  ((Partition->FeaturesCompat & CompatFeatureSet) == CompatFeatureSet)
+
+/**
+   Verifies the existance of a particular compat feature set.
+   @param[in]      Partition           Pointer to the opened EXT4 partition.
+   @param[in]      IncompatFeatureSet  Feature set to test.
+
+   @return TRUE if all features are supported, else FALSE.
+**/
+#define EXT4_HAS_INCOMPAT(Partition, IncompatFeatureSet)                       \
+  ((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.
+
+   @return TRUE if the metadata_csum is supported, else FALSE.
+**/
+#define EXT4_HAS_METADATA_CSUM(Partition)                                      \
+  EXT4_HAS_RO_COMPAT(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.
+
+   @return TRUE if the gdt_csum is supported, else FALSE.
+**/
+#define EXT4_HAS_GDT_CSUM(Partition)                                           \
+  EXT4_HAS_RO_COMPAT(Partition, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)
+
+/**
+   Retrieves the volume name.
+
+   @param[in]      Part           Pointer to the opened partition.
+   @param[out]     Info           Pointer to a CHAR16*.
+   @param[out]     BufferSize     Pointer to a UINTN, where the string length
+                                  of the name will be put.
+
+   @return Status of the volume name request.
+**/
+EFI_STATUS
+Ext4GetVolumeName (
+  IN EXT4_PARTITION  *Partition,
+  OUT CHAR16         **OutVolName,
+  OUT UINTN          *VolNameLen
+  );
+
+/**
+   Checks the superblock's magic value.
+
+   @param[in] DiskIo      Pointer to the DiskIo.
+   @param[in] BlockIo     Pointer to the BlockIo.
+
+   @returns Whether the partition has a valid EXT4 superblock magic value.
+**/
+BOOLEAN
+Ext4SuperblockCheckMagic (
+  IN EFI_DISK_IO_PROTOCOL   *DiskIo,
+  IN EFI_BLOCK_IO_PROTOCOL  *BlockIo
+  );
+
+/**
+   Check if the extent is uninitialized
+
+   @param[in] Extent    Pointer to the EXT4_EXTENT
+
+   @returns True if uninitialized, else false.
+**/
+#define EXT4_EXTENT_IS_UNINITIALIZED(Extent)                                   \
+  ((Extent)->ee_len > EXT4_EXTENT_MAX_INITIALIZED)
+
+/**
+   Retrieves the extent's length, dealing with uninitialized extents in the
+process.
+
+   @param[in] Extent      Pointer to the EXT4_EXTENT
+
+   @returns Extent's length, in filesystem blocks.
+**/
+EXT4_BLOCK_NR
+Ext4GetExtentLength (
+  IN CONST EXT4_EXTENT  *Extent
+  );
+
+/**
+   Retrieves an extent from an EXT2/3 inode (with a blockmap).
+   @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
+Ext4GetBlocks (
+  IN  EXT4_PARTITION  *Partition,
+  IN  EXT4_FILE       *File,
+  IN  EXT2_BLOCK_NR   LogicalBlock,
+  OUT EXT4_EXTENT     *Extent
+  );
+
+#endif
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
new file mode 100644
index 000000000000..a153fc41ccd6
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
@@ -0,0 +1,149 @@
+## @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                      = Ext4Dxe
+  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
+  Symlink.c
+  Collation.c
+  Ext4Disk.h
+  Ext4Dxe.h
+  BlockMap.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/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
new file mode 100644
index 000000000000..7476fbf9bd70
--- /dev/null
+++ b/Filesystems/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/Filesystems/Ext4Pkg/Ext4Dxe/Extents.c b/Filesystems/Ext4Pkg/Ext4Dxe/Extents.c
new file mode 100644
index 000000000000..f1964426d276
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Extents.c
@@ -0,0 +1,624 @@
+/** @file
+  Extent related routines
+
+  Copyright (c) 2021 - 2022 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 ((DEBUG_ERROR, "[ext4] Invalid extent header depth %u\n", Header->eh_depth));
+    return FALSE;
+  }
+
+  if (Header->eh_magic != EXT4_EXTENT_HEADER_MAGIC) {
+    DEBUG ((DEBUG_ERROR, "[ext4] Invalid extent header magic %x\n", Header->eh_magic));
+    return FALSE;
+  }
+
+  if (Header->eh_max < Header->eh_entries) {
+    DEBUG ((
+      DEBUG_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 LShiftU64 (Index->ei_leaf_hi, 32) | Index->ei_leaf_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
+  )
+{
+  EXT4_INODE          *Inode;
+  VOID                *Buffer;
+  EXT4_EXTENT         *Ext;
+  UINT32              CurrentDepth;
+  EXT4_EXTENT_HEADER  *ExtHeader;
+  EXT4_EXTENT_INDEX   *Index;
+  EFI_STATUS          Status;
+
+  Inode  = File->Inode;
+  Ext    = NULL;
+  Buffer = NULL;
+
+  DEBUG ((DEBUG_FS, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
+
+  // ext4 does not have support for logical block numbers bigger than UINT32_MAX
+  if (LogicalBlock > (UINT32)-1) {
+    return EFI_NO_MAPPING;
+  }
+
+  // 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;
+
+    return EFI_SUCCESS;
+  }
+
+  if ((Inode->i_flags & EXT4_EXTENTS_FL) == 0) {
+    // If this is an older ext2/ext3 filesystem, emulate Ext4GetExtent using the block map
+    // By specification files using block maps are limited to 2^32 blocks,
+    // so we can safely cast LogicalBlock to uint32
+    Status = Ext4GetBlocks (Partition, File, (UINT32)LogicalBlock, Extent);
+
+    if (!EFI_ERROR (Status)) {
+      Ext4CacheExtents (File, Extent, 1);
+    }
+
+    return Status;
+  }
+
+  // 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) {
+    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 ((DEBUG_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 + Ext4GetExtentLength (Ext) > 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;
+  CONST EXT4_EXTENT  *Extent2;
+
+  Extent1 = UserStruct1;
+  Extent2 = UserStruct2;
+
+  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;
+  UINT32             Block;
+
+  // Note that logical blocks are 32-bits in size so no truncation can happen here
+  // with regards to 32-bit architectures.
+  Extent = UserStruct;
+  Block  = (UINT32)(UINTN)StandaloneKey;
+
+  if ((Block >= Extent->ee_block) && (Block - Extent->ee_block < Ext4GetExtentLength (Extent))) {
+    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;
+  EXT4_EXTENT               *Ext;
+
+  MinEntry = NULL;
+
+  while ((MinEntry = OrderedCollectionMin (File->ExtentsMap)) != NULL) {
+    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       Idx;
+  EXT4_EXTENT  *Extent;
+  EFI_STATUS   Status;
+
+  /* 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 (Idx = 0; Idx < NumberExtents; Idx++, Extents++) {
+    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;
+    }
+  }
+}
+
+/**
+   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 (!EXT4_HAS_METADATA_CSUM (Partition)) {
+    return TRUE;
+  }
+
+  Tail = (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->BlockSize - 4));
+
+  return Tail->eb_checksum == Ext4CalculateExtentChecksum (ExtHeader, File);
+}
+
+/**
+   Retrieves the extent's length, dealing with uninitialized extents in the process.
+
+   @param[in] Extent      Pointer to the EXT4_EXTENT
+
+   @returns Extent's length, in filesystem blocks.
+**/
+EXT4_BLOCK_NR
+Ext4GetExtentLength (
+  IN CONST EXT4_EXTENT  *Extent
+  )
+{
+  // If it's an unintialized extent, the true length is ee_len - 2^15
+  if (EXT4_EXTENT_IS_UNINITIALIZED (Extent)) {
+    return Extent->ee_len - EXT4_EXTENT_MAX_INITIALIZED;
+  }
+
+  return Extent->ee_len;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/File.c b/Filesystems/Ext4Pkg/Ext4Dxe/File.c
new file mode 100644
index 000000000000..04198a53bfc0
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/File.c
@@ -0,0 +1,1001 @@
+/** @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>
+
+/**
+  Reads a symlink file.
+
+  @param[in]      Partition   Pointer to the ext4 partition.
+  @param[in]      File        Pointer to the open symlink file.
+  @param[out]     Symlink     Pointer to the output unicode symlink string.
+
+  @retval EFI_SUCCESS           Symlink was read.
+  @retval EFI_ACCESS_DENIED     Symlink is encrypted.
+  @retval EFI_OUT_OF_RESOURCES  Memory allocation error.
+  @retval EFI_INVALID_PARAMETER Symlink path has incorrect length
+  @retval EFI_VOLUME_CORRUPTED  Symlink read block size differ from inode value
+**/
+EFI_STATUS
+Ext4ReadSymlink (
+  IN     EXT4_PARTITION  *Partition,
+  IN     EXT4_FILE       *File,
+  OUT    CHAR16          **Symlink
+  );
+
+/**
+   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;
+  CONST CHAR16  *End;
+
+  Start = Path;
+  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;
+
+  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;
+}
+
+/**
+  Opens a new file relative to the source file's location.
+
+  @param[out] FoundFile  A pointer to the location to return the opened handle for the new
+                         file.
+  @param[in]  Source     A pointer to the EXT4_FILE instance that is the file
+                         handle to the source location. This would typically be an open
+                         handle to a directory.
+  @param[in]  FileName   The Null-terminated string of the name of the file to be opened.
+                         The file name may contain the following path modifiers: "\", ".",
+                         and "..".
+  @param[in]  OpenMode   The mode to open the file. The only valid combinations that the
+                         file may be opened with are: Read, Read/Write, or Create/Read/Write.
+  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case these are the
+                         attribute bits for the newly created file.
+
+  @retval EFI_SUCCESS          The file was opened.
+  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
+                               longer supported.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a file for write
+                               when the media is write-protected.
+  @retval EFI_ACCESS_DENIED    The service denied access to the file.
+  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
+  @retval EFI_VOLUME_FULL      The volume is full.
+
+**/
+EFI_STATUS
+Ext4OpenInternal (
+  OUT EXT4_FILE  **FoundFile,
+  IN  EXT4_FILE  *Source,
+  IN  CHAR16     *FileName,
+  IN  UINT64     OpenMode,
+  IN  UINT64     Attributes
+  )
+{
+  EXT4_FILE       *Current;
+  EXT4_PARTITION  *Partition;
+  UINTN           Level;
+  CHAR16          PathSegment[EXT4_NAME_MAX + 1];
+  UINTN           Length;
+  EXT4_FILE       *File;
+  CHAR16          *Symlink;
+  EFI_STATUS      Status;
+
+  Current   = Source;
+  Partition = Current->Partition;
+  Level     = 0;
+
+  DEBUG ((DEBUG_FS, "[ext4] Ext4OpenInternal %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') {
+    if (Partition->Root->SymLoops > SYMLOOP_MAX) {
+      DEBUG ((DEBUG_FS, "[ext4] Symloop limit is hit !\n"));
+      return EFI_ACCESS_DENIED;
+    }
+
+    // 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;
+
+    if (StrCmp (PathSegment, L".") == 0) {
+      // Opens of "." are a no-op
+      continue;
+    }
+
+    DEBUG ((DEBUG_FS, "[ext4] Opening %s\n", PathSegment));
+
+    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) {
+      // We explicitly ignore the EFI_FILE_MODE_CREATE flag, since we don't have write support
+      /// If/ we add write support, this should be changed.
+      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;
+    }
+
+    //
+    // Reading symlink and then trying to follow it
+    //
+    if (Ext4FileIsSymlink (File)) {
+      Partition->Root->SymLoops++;
+      DEBUG ((DEBUG_FS, "[ext4] File %s is symlink, trying to read it\n", PathSegment));
+      Status = Ext4ReadSymlink (Partition, File, &Symlink);
+      if (EFI_ERROR (Status)) {
+        DEBUG ((DEBUG_FS, "[ext4] Error reading %s symlink!\n", PathSegment));
+        return Status;
+      }
+
+      DEBUG ((DEBUG_FS, "[ext4] File %s is linked to %s\n", PathSegment, Symlink));
+      //
+      // Close symlink file
+      //
+      Ext4CloseInternal (File);
+      //
+      // Open linked file by recursive call of Ext4OpenFile
+      //
+      Status = Ext4OpenInternal (FoundFile, Current, Symlink, OpenMode, Attributes);
+      FreePool (Symlink);
+      if (EFI_ERROR (Status)) {
+        DEBUG ((DEBUG_FS, "[ext4] Error opening linked file %s\n", Symlink));
+        return Status;
+      }
+
+      //
+      // Set File to newly opened
+      //
+      File = *FoundFile;
+    }
+
+    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;
+  }
+
+  *FoundFile = Current;
+
+  DEBUG ((DEBUG_FS, "[ext4] Opened filename %s\n", Current->Dentry->Name));
+  return EFI_SUCCESS;
+}
+
+/**
+  Opens a new file relative to the source file's location.
+  @param[in]  This       A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                         handle to the source location. This would typically be an open
+                         handle to a directory.
+  @param[out] NewHandle  A pointer to the location to return the opened handle for the new
+                         file.
+  @param[in]  FileName   The Null-terminated string of the name of the file to be opened.
+                         The file name may contain the following path modifiers: "\", ".",
+                         and "..".
+  @param[in]  OpenMode   The mode to open the file. The only valid combinations that the
+                         file may be opened with are: Read, Read/Write, or Create/Read/Write.
+  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case these are the
+                         attribute bits for the newly created file.
+  @retval EFI_SUCCESS          The file was opened.
+  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
+                               longer supported.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a file for write
+                               when the media is write-protected.
+  @retval EFI_ACCESS_DENIED    The service denied access to the file.
+  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
+  @retval EFI_VOLUME_FULL      The volume is full.
+**/
+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  Status;
+  EXT4_FILE   *FoundFile;
+  EXT4_FILE   *Source;
+
+  Source = EXT4_FILE_FROM_THIS (This);
+
+  //
+  // Reset SymLoops counter
+  //
+  Source->Partition->Root->SymLoops = 0;
+
+  Status = Ext4OpenInternal (
+             &FoundFile,
+             Source,
+             FileName,
+             OpenMode,
+             Attributes
+             );
+
+  if (!EFI_ERROR (Status)) {
+    *NewHandle = &FoundFile->Protocol;
+  }
+
+  return Status;
+}
+
+/**
+  Closes a specified file handle.
+
+  @param[in]  This          A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                            handle to close.
+
+  @retval EFI_SUCCESS   The file was closed.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4Close (
+  IN EFI_FILE_PROTOCOL  *This
+  )
+{
+  return Ext4CloseInternal (EXT4_FILE_FROM_THIS (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 ((DEBUG_FS, "[ext4] Closed file %p (inode %lu)\n", File, File->InodeNum));
+  RemoveEntryList (&File->OpenFilesListNode);
+  FreePool (File->Inode);
+  Ext4FreeExtentsMap (File);
+  Ext4UnrefDentry (File->Dentry);
+  FreePool (File);
+  return EFI_SUCCESS;
+}
+
+/**
+  Close and delete the file handle.
+
+  @param[in]  This                     A pointer to the EFI_FILE_PROTOCOL instance that is the
+                                       handle to the file to delete.
+
+  @retval EFI_SUCCESS              The file was closed and deleted, and the handle was closed.
+  @retval EFI_WARN_DELETE_FAILURE  The handle was closed, but the file was not deleted.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4Delete (
+  IN EFI_FILE_PROTOCOL  *This
+  )
+{
+  // We do a regular close here since we don't have write support.
+  Ext4Close (This);
+  return EFI_WARN_DELETE_FAILURE;
+}
+
+/**
+  Reads data from a file.
+
+  @param[in]      This             A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                                   handle to read data from.
+  @param[in out]  BufferSize       On input, the size of the Buffer. On output, the amount of data
+                                   returned in Buffer. In both cases, the size is measured in bytes.
+  @param[out]     Buffer           The buffer into which the data is read.
+
+  @retval EFI_SUCCESS          Data was read.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_DEVICE_ERROR     An attempt was made to read from a deleted file.
+  @retval EFI_DEVICE_ERROR     On entry, the current file position is beyond the end of the file.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory
+                               entry. BufferSize has been updated with the size
+                               needed to complete the request.
+
+**/
+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_FROM_THIS (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);
+
+    return Status;
+  }
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Writes data to a file.
+
+  @param[in]      This        A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                              handle to write data to.
+  @param[in out]  BufferSize  On input, the size of the Buffer. On output, the amount of data
+                              actually written. In both cases, the size is measured in bytes.
+  @param[in]      Buffer      The buffer of data to write.
+
+  @retval EFI_SUCCESS          Data was written.
+  @retval EFI_UNSUPPORTED      Writes to open directory files are not supported.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_DEVICE_ERROR     An attempt was made to write to a deleted file.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  The file or medium is write-protected.
+  @retval EFI_ACCESS_DENIED    The file was opened read only.
+  @retval EFI_VOLUME_FULL      The volume is full.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4WriteFile (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN OUT UINTN          *BufferSize,
+  IN VOID               *Buffer
+  )
+{
+  EXT4_FILE  *File;
+
+  File = EXT4_FILE_FROM_THIS (This);
+
+  if (!(File->OpenMode & EFI_FILE_MODE_WRITE)) {
+    return EFI_ACCESS_DENIED;
+  }
+
+  return EFI_WRITE_PROTECTED;
+}
+
+/**
+  Returns a file's current position.
+
+  @param[in]   This            A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                               handle to get the current position on.
+  @param[out]  Position        The address to return the file's current position value.
+
+  @retval EFI_SUCCESS      The position was returned.
+  @retval EFI_UNSUPPORTED  The request is not valid on open directories.
+  @retval EFI_DEVICE_ERROR An attempt was made to get the position from a deleted file.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4GetPosition (
+  IN EFI_FILE_PROTOCOL  *This,
+  OUT UINT64            *Position
+  )
+{
+  EXT4_FILE  *File;
+
+  File = EXT4_FILE_FROM_THIS (This);
+
+  if (Ext4FileIsDir (File)) {
+    return EFI_UNSUPPORTED;
+  }
+
+  *Position = File->Position;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Sets a file's current position.
+
+  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that is the
+                              file handle to set the requested position on.
+  @param[in] Position        The byte position from the start of the file to set.
+
+  @retval EFI_SUCCESS      The position was set.
+  @retval EFI_UNSUPPORTED  The seek request for nonzero is not valid on open
+                           directories.
+  @retval EFI_DEVICE_ERROR An attempt was made to set the position of a deleted file.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4SetPosition (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN UINT64             Position
+  )
+{
+  EXT4_FILE  *File;
+
+  File = EXT4_FILE_FROM_THIS (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
+  )
+{
+  UINTN         FileNameLen;
+  UINTN         FileNameSize;
+  UINTN         NeededLength;
+  CONST CHAR16  *FileName;
+
+  if (File->InodeNum == EXT4_ROOT_INODE_NR) {
+    // Root inode gets a filename of "", regardless of how it was opened.
+    FileName = L"";
+  } else {
+    FileName = File->Dentry->Name;
+  }
+
+  FileNameLen  = StrLen (FileName);
+  FileNameSize = StrSize (FileName);
+
+  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, FileName);
+}
+
+/**
+   Retrieves the volume name.
+
+   @param[in]      Part           Pointer to the opened partition.
+   @param[out]     Info           Pointer to a CHAR16*.
+   @param[out]     BufferSize     Pointer to a UINTN, where the string length
+                                  of the name will be put.
+
+   @return Status of the volume name request.
+**/
+EFI_STATUS
+Ext4GetVolumeName (
+  IN EXT4_PARTITION  *Partition,
+  OUT CHAR16         **OutVolName,
+  OUT UINTN          *VolNameLen
+  )
+{
+  CHAR8       TempVolName[16 + 1];
+  CHAR16      *VolumeName;
+  UINTN       VolNameLength;
+  EFI_STATUS  Status;
+
+  VolNameLength = 0;
+  VolumeName    = NULL;
+
+  // s_volume_name is only valid on dynamic revision; old filesystems don't support this
+  if (Partition->SuperBlock.s_rev_level == EXT4_DYNAMIC_REV) {
+    CopyMem (TempVolName, Partition->SuperBlock.s_volume_name, 16);
+    TempVolName[16] = '\0';
+
+    Status = UTF8StrToUCS2 (TempVolName, &VolumeName);
+
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+
+    VolNameLength = StrLen (VolumeName);
+  } else {
+    VolumeName    = AllocateZeroPool (sizeof (CHAR16));
+    VolNameLength = 0;
+  }
+
+  *OutVolName = VolumeName;
+  *VolNameLen = VolNameLength;
+
+  return EFI_SUCCESS;
+}
+
+/**
+   Retrieves information about the filesystem and stores it in the EFI_FILE_SYSTEM_INFO format.
+
+   @param[in]      Part           Pointer to the opened partition.
+   @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
+  EFI_STATUS     Status;
+  UINTN          NeededLength;
+  EXT4_BLOCK_NR  TotalBlocks;
+  EXT4_BLOCK_NR  FreeBlocks;
+  CHAR16         *VolumeName;
+  UINTN          VolNameLength;
+
+  Status = Ext4GetVolumeName (Part, &VolumeName, &VolNameLength);
+
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  NeededLength = SIZE_OF_EFI_FILE_SYSTEM_INFO;
+
+  NeededLength += StrSize (VolumeName);
+
+  if (*BufferSize < NeededLength) {
+    *BufferSize = NeededLength;
+
+    FreePool (VolumeName);
+
+    return EFI_BUFFER_TOO_SMALL;
+  }
+
+  TotalBlocks = Part->NumberBlocks;
+
+  FreeBlocks = EXT4_BLOCK_NR_FROM_HALFS (
+                 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 = MultU64x32 (TotalBlocks, Part->BlockSize);
+  Info->FreeSpace  = MultU64x32 (FreeBlocks, Part->BlockSize);
+
+  StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
+
+  FreePool (VolumeName);
+
+  *BufferSize = NeededLength;
+
+  return EFI_SUCCESS;
+}
+
+/**
+   Retrieves the volume label and stores it in the EFI_FILE_SYSTEM_VOLUME_LABEL format.
+
+   @param[in]      Part           Pointer to the opened partition.
+   @param[out]     Info           Pointer to a EFI_FILE_SYSTEM_VOLUME_LABEL.
+   @param[in out]  BufferSize     Pointer to the buffer size
+
+   @return Status of the file information request.
+**/
+STATIC
+EFI_STATUS
+Ext4GetVolumeLabelInfo (
+  IN EXT4_PARTITION                 *Part,
+  OUT EFI_FILE_SYSTEM_VOLUME_LABEL  *Info,
+  IN OUT UINTN                      *BufferSize
+  )
+{
+  // Length of s_volume_name + null terminator
+  CHAR16      *VolumeName;
+  UINTN       VolNameLength;
+  EFI_STATUS  Status;
+  UINTN       NeededLength;
+
+  Status = Ext4GetVolumeName (Part, &VolumeName, &VolNameLength);
+
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  NeededLength = (VolNameLength + 1) * sizeof (CHAR16);
+
+  if (NeededLength > *BufferSize) {
+    *BufferSize = NeededLength;
+
+    FreePool (VolumeName);
+
+    return EFI_BUFFER_TOO_SMALL;
+  }
+
+  Status = StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
+
+  ASSERT_EFI_ERROR (Status);
+
+  FreePool (VolumeName);
+
+  *BufferSize = NeededLength;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Returns information about a file.
+
+  @param[in]      This            A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                                  handle the requested information is for.
+  @param[in]      InformationType The type identifier for the information being requested.
+  @param[in out]  BufferSize      On input, the size of Buffer. On output, the amount of data
+                                  returned in Buffer. In both cases, the size is measured in bytes.
+  @param[out]     Buffer          A pointer to the data buffer to return. The buffer's type is
+                                  indicated by InformationType.
+
+  @retval EFI_SUCCESS          The information was returned.
+  @retval EFI_UNSUPPORTED      The InformationType is not known.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory entry.
+                               BufferSize has been updated with the size needed to complete
+                               the request.
+**/
+EFI_STATUS
+EFIAPI
+Ext4GetInfo (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN EFI_GUID           *InformationType,
+  IN OUT UINTN          *BufferSize,
+  OUT VOID              *Buffer
+  )
+{
+  EXT4_FILE       *File;
+  EXT4_PARTITION  *Partition;
+
+  File      = EXT4_FILE_FROM_THIS (This);
+  Partition = File->Partition;
+
+  if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
+    return Ext4GetFileInfo (File, Buffer, BufferSize);
+  }
+
+  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
+    return Ext4GetFilesystemInfo (Partition, Buffer, BufferSize);
+  }
+
+  if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
+    return Ext4GetVolumeLabelInfo (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;
+  EFI_STATUS      Status;
+
+  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->Position = 0;
+  Ext4SetupFile (File, Partition);
+  File->InodeNum = Original->InodeNum;
+  File->OpenMode = 0; // Will be filled by other code
+
+  Status = Ext4InitExtentsMap (File);
+  if (EFI_ERROR (Status)) {
+    FreePool (File->Inode);
+    FreePool (File);
+    return NULL;
+  }
+
+  File->Dentry = Original->Dentry;
+
+  Ext4RefDentry (File->Dentry);
+
+  InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
+
+  return File;
+}
+
+/**
+  Sets information about a file.
+
+  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that is the file
+                              handle the information is for.
+  @param[in]  InformationType The type identifier for the information being set.
+  @param[in]  BufferSize      The size, in bytes, of Buffer.
+  @param[in]  Buffer          A pointer to the data buffer to write. The buffer's type is
+                              indicated by InformationType.
+
+  @retval EFI_SUCCESS          The information was set.
+  @retval EFI_UNSUPPORTED      The InformationType is not known.
+  @retval EFI_NO_MEDIA         The device has no medium.
+  @retval EFI_DEVICE_ERROR     The device reported an error.
+  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
+  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_INFO_ID and the media is
+                               read-only.
+  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_PROTOCOL_SYSTEM_INFO_ID
+                               and the media is read only.
+  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_SYSTEM_VOLUME_LABEL_ID
+                               and the media is read-only.
+  @retval EFI_ACCESS_DENIED    An attempt is made to change the name of a file to a
+                               file that is already present.
+  @retval EFI_ACCESS_DENIED    An attempt is being made to change the EFI_FILE_DIRECTORY
+                               Attribute.
+  @retval EFI_ACCESS_DENIED    An attempt is being made to change the size of a directory.
+  @retval EFI_ACCESS_DENIED    InformationType is EFI_FILE_INFO_ID and the file was opened
+                               read-only and an attempt is being made to modify a field
+                               other than Attribute.
+  @retval EFI_VOLUME_FULL      The volume is full.
+  @retval EFI_BAD_BUFFER_SIZE  BufferSize is smaller than the size of the type indicated
+                               by InformationType.
+
+**/
+EFI_STATUS
+EFIAPI
+Ext4SetInfo (
+  IN EFI_FILE_PROTOCOL  *This,
+  IN EFI_GUID           *InformationType,
+  IN UINTN              BufferSize,
+  IN VOID               *Buffer
+  )
+{
+  EXT4_FILE       *File;
+  EXT4_PARTITION  *Partition;
+
+  File      = EXT4_FILE_FROM_THIS (This);
+  Partition = File->Partition;
+
+  if (Partition->ReadOnly) {
+    return EFI_WRITE_PROTECTED;
+  }
+
+  // There's no write support just yet.
+  return EFI_UNSUPPORTED;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Inode.c b/Filesystems/Ext4Pkg/Ext4Dxe/Inode.c
new file mode 100644
index 000000000000..5ccb4d2bfc42
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Inode.c
@@ -0,0 +1,490 @@
+/** @file
+  Inode related routines
+
+  Copyright (c) 2021 - 2022 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"
+
+/**
+   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;
+  UINTN       Length;
+
+  HasSecondChecksumField = EXT4_INODE_HAS_FIELD (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) {
+    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
+  )
+{
+  EXT4_INODE   *Inode;
+  UINT64       InodeSize;
+  UINT64       CurrentSeek;
+  UINTN        RemainingRead;
+  UINTN        BeenRead;
+  UINTN        WasRead;
+  EXT4_EXTENT  Extent;
+  UINT32       BlockOff;
+  EFI_STATUS   Status;
+  BOOLEAN      HasBackingExtent;
+  UINT32       HoleOff;
+  UINT64       HoleLen;
+  UINT64       ExtentStartBytes;
+  UINT64       ExtentLengthBytes;
+  UINT64       ExtentLogicalBytes;
+
+  // Our extent offset is the difference between CurrentSeek and ExtentLogicalBytes
+  UINT64  ExtentOffset;
+  UINTN   ExtentMayRead;
+
+  Inode         = File->Inode;
+  InodeSize     = EXT4_INODE_SIZE (Inode);
+  CurrentSeek   = Offset;
+  RemainingRead = *Length;
+  BeenRead      = 0;
+
+  DEBUG ((DEBUG_FS, "[ext4] Ext4Read(%s, Offset %lu, Length %lu)\n", File->Dentry->Name, Offset, *Length));
+
+  if (Offset > InodeSize) {
+    return EFI_DEVICE_ERROR;
+  }
+
+  if (RemainingRead > InodeSize - Offset) {
+    RemainingRead = (UINTN)(InodeSize - Offset);
+  }
+
+  while (RemainingRead != 0) {
+    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 || EXT4_EXTENT_IS_UNINITIALIZED (&Extent)) {
+      HoleOff = BlockOff;
+
+      if (!HasBackingExtent) {
+        HoleLen = Partition->BlockSize - HoleOff;
+      } else {
+        // Uninitialized extents behave exactly the same as file holes, except they have
+        // blocks already allocated to them.
+        HoleLen = (Ext4GetExtentLength (&Extent) * Partition->BlockSize) - HoleOff;
+      }
+
+      WasRead = HoleLen > RemainingRead ? RemainingRead : (UINTN)HoleLen;
+      // Potential improvement: In the future, we could get the file hole's total
+      // size and memset all that
+      ZeroMem (Buffer, WasRead);
+    } else {
+      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;
+      ExtentMayRead      = (UINTN)(ExtentLengthBytes - ExtentOffset);
+
+      WasRead = ExtentMayRead > RemainingRead ? RemainingRead : ExtentMayRead;
+
+      Status = Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBytes + ExtentOffset);
+
+      if (EFI_ERROR (Status)) {
+        DEBUG ((
+          DEBUG_ERROR,
+          "[ext4] Error %r 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;
+
+  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;
+
+  // 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.
+  // It's important to note that EXT4_INODE includes fields that may not exist in an actual
+  // filesystem (the minimum inode size is 128 byte and at the moment the size of EXT4_INODE
+  // is 160 bytes).
+
+  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 symlink.
+
+   @param[in]      File          Pointer to the opened file.
+
+   @return BOOLEAN         Whether file is a symlink
+**/
+BOOLEAN
+Ext4FileIsSymlink (
+  IN CONST EXT4_FILE  *File
+  )
+{
+  return (File->Inode->i_mode & EXT4_INO_TYPE_SYMLINK) == EXT4_INO_TYPE_SYMLINK;
+}
+
+/**
+   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 (
+  IN EXT4_FILE  *File
+  )
+{
+  BOOLEAN  HugeFile;
+  UINT64   Blocks;
+
+  HugeFile = EXT4_HAS_RO_COMPAT (File->Partition, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
+  Blocks   = File->Inode->i_blocks;
+
+  if (HugeFile) {
+    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) != 0) {
+      return MultU64x32 (Blocks, File->Partition->BlockSize);
+    }
+  }
+
+  // Else, each i_blocks unit corresponds to 512 bytes
+  return MultU64x32 (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[in]   EpochSeconds   Epoch seconds.
+  @param[out]  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 (EXT4_INODE_HAS_FIELD (Inode, Field ## _extra)) {          \
+      SecondsEpoch |= LShiftU64 ((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 (!EXT4_INODE_HAS_FIELD (Inode, i_crtime)) {
+    ZeroMem (Time, sizeof (EFI_TIME));
+    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 (!EXT4_HAS_METADATA_CSUM (Partition)) {
+    return TRUE;
+  }
+
+  Csum = Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);
+
+  DiskCsum = Inode->i_osd2.data_linux.l_i_checksum_lo;
+
+  if (EXT4_INODE_HAS_FIELD (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;
+  }
+
+  return Csum == DiskCsum;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Partition.c b/Filesystems/Ext4Pkg/Ext4Dxe/Partition.c
new file mode 100644
index 000000000000..316807497dd4
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Partition.c
@@ -0,0 +1,132 @@
+/** @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,
+  IN EXT4_PARTITION  *Partition
+  )
+{
+  // Note: We don't yet support revision 2 of the file protocol
+  // (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->Protocol.SetInfo     = Ext4SetInfo;
+
+  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;
+  EXT4_FILE   *File;
+  BOOLEAN     DeletedRootDentry;
+
+  Partition->Unmounting = TRUE;
+  Ext4CloseInternal (Partition->Root);
+
+  BASE_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Partition->OpenFiles) {
+    File = EXT4_FILE_FROM_OPEN_FILES_NODE (Entry);
+
+    Ext4CloseInternal (File);
+  }
+
+  DeletedRootDentry = Ext4UnrefDentry (Partition->RootDentry);
+
+  if (!DeletedRootDentry) {
+    DEBUG ((DEBUG_ERROR, "[ext4] Failed to delete root dentry - resource leak present.\n"));
+  }
+
+  FreePool (Partition->BlockGroups);
+  FreePool (Partition);
+
+  return EFI_SUCCESS;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c b/Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c
new file mode 100644
index 000000000000..edee051c41e8
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c
@@ -0,0 +1,355 @@
+/** @file
+  Superblock managing routines
+
+  Copyright (c) 2021 - 2022 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;
+
+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 | EXT4_FEATURE_INCOMPAT_RECOVER;
+
+// Future features that may be nice additions in the future:
+// 1) Btree support: Required for write support and would speed up lookups in large directories.
+// 2) meta_bg: Required to mount meta_bg-enabled partitions.
+
+// 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).
+
+// Note on corruption signaling:
+// We (Ext4Dxe) could signal corruption by setting s_state to |= EXT4_FS_STATE_ERRORS_DETECTED.
+// I've decided against that, because right now the driver is read-only, and
+// that would mean we would need to writeback the superblock. If something like
+// this is desired, it's fairly trivial to look for EFI_VOLUME_CORRUPTED
+// references and add some Ext4SignalCorruption function + function call.
+
+/**
+   Checks the superblock's magic value.
+
+   @param[in] DiskIo      Pointer to the DiskIo.
+   @param[in] BlockIo     Pointer to the BlockIo.
+
+   @returns Whether the partition has a valid EXT4 superblock magic value.
+**/
+BOOLEAN
+Ext4SuperblockCheckMagic (
+  IN EFI_DISK_IO_PROTOCOL   *DiskIo,
+  IN EFI_BLOCK_IO_PROTOCOL  *BlockIo
+  )
+{
+  UINT16      Magic;
+  EFI_STATUS  Status;
+
+  Status = DiskIo->ReadDisk (
+                     DiskIo,
+                     BlockIo->Media->MediaId,
+                     EXT4_SUPERBLOCK_OFFSET + OFFSET_OF (EXT4_SUPERBLOCK, s_magic),
+                     sizeof (Magic),
+                     &Magic
+                     );
+  if (EFI_ERROR (Status)) {
+    return FALSE;
+  }
+
+  if (Magic != EXT4_SIGNATURE) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+   Does brief validation of the ext4 superblock.
+
+   @param[in] Sb     Pointer to the read superblock.
+
+   @return TRUE if a valid ext4 superblock, else FALSE.
+**/
+BOOLEAN
+Ext4SuperblockValidate (
+  CONST EXT4_SUPERBLOCK  *Sb
+  )
+{
+  if (Sb->s_magic != EXT4_SIGNATURE) {
+    return FALSE;
+  }
+
+  if ((Sb->s_rev_level != EXT4_DYNAMIC_REV) && (Sb->s_rev_level != EXT4_GOOD_OLD_REV)) {
+    return FALSE;
+  }
+
+  if ((Sb->s_state & EXT4_FS_STATE_UNMOUNTED) == 0) {
+    DEBUG ((DEBUG_WARN, "[ext4] Filesystem was not unmounted cleanly\n"));
+  }
+
+  return TRUE;
+}
+
+/**
+   Calculates the superblock's checksum.
+
+   @param[in] Partition    Pointer to the opened partition.
+   @param[in] Sb           Pointer to the superblock.
+
+   @return The superblock's checksum.
+**/
+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;
+
+  Checksum = Ext4CalculateChecksum (
+               Partition,
+               Sb,
+               OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),
+               ~0U
+               );
+
+  return Checksum;
+}
+
+/**
+   Verifies that the superblock's checksum is valid.
+
+   @param[in] Partition    Pointer to the opened partition.
+   @param[in] Sb           Pointer to the superblock.
+
+   @return The superblock's checksum.
+**/
+STATIC
+BOOLEAN
+Ext4VerifySuperblockChecksum (
+  EXT4_PARTITION         *Partition,
+  CONST EXT4_SUPERBLOCK  *Sb
+  )
+{
+  if (!EXT4_HAS_METADATA_CSUM (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;
+  EXT4_BLOCK_GROUP_DESC  *Desc;
+
+  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;
+
+    // Check for proper alignment of InodeSize and that InodeSize is indeed larger than
+    // the minimum size, 128 bytes.
+    if (((Partition->InodeSize % 4) != 0) || (Partition->InodeSize < EXT4_GOOD_OLD_INODE_SIZE)) {
+      return EFI_VOLUME_CORRUPTED;
+    }
+  } 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 ((
+      DEBUG_ERROR,
+      "[ext4] Unsupported features %lx\n",
+      Partition->FeaturesIncompat & ~gSupportedIncompatFeat
+      ));
+    return EFI_UNSUPPORTED;
+  }
+
+  if (EXT4_HAS_INCOMPAT (Partition, EXT4_FEATURE_INCOMPAT_RECOVER)) {
+    DEBUG ((DEBUG_WARN, "[ext4] Needs journal recovery, mounting read-only\n"));
+    Partition->ReadOnly = TRUE;
+  }
+
+  // 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) != 0) {
+    Partition->InitialSeed = Sb->s_checksum_seed;
+  } else {
+    Partition->InitialSeed = Ext4CalculateChecksum (Partition, Sb->s_uuid, 16, ~0U);
+  }
+
+  UnsupportedRoCompat = Partition->FeaturesRoCompat & ~gSupportedRoCompatFeat;
+
+  if (UnsupportedRoCompat != 0) {
+    DEBUG ((DEBUG_WARN, "[ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
+    Partition->ReadOnly = TRUE;
+  }
+
+  // gSupportedCompatFeat is documentation-only since we never need to access it.
+  // The line below avoids unused variable warnings.
+  (VOID)gSupportedCompatFeat;
+
+  DEBUG ((DEBUG_FS, "Read only = %u\n", Partition->ReadOnly));
+
+  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) {
+    return EFI_UNSUPPORTED;
+  }
+
+  Partition->NumberBlocks      = EXT4_BLOCK_NR_FROM_HALFS (Partition, Sb->s_blocks_count, Sb->s_blocks_count_hi);
+  Partition->NumberBlockGroups = DivU64x32 (Partition->NumberBlocks, Sb->s_blocks_per_group);
+
+  DEBUG ((
+    DEBUG_FS,
+    "[ext4] Number of blocks = %lu\n[ext4] Number of block groups: %lu\n",
+    Partition->NumberBlocks,
+    Partition->NumberBlockGroups
+    ));
+
+  if (EXT4_IS_64_BIT (Partition)) {
+    // s_desc_size should be 4 byte aligned and
+    // 64 bit filesystems need DescSize to be 64 bytes
+    if (((Sb->s_desc_size % 4) != 0) || (Sb->s_desc_size < EXT4_64BIT_BLOCK_DESC_SIZE)) {
+      return EFI_VOLUME_CORRUPTED;
+    }
+
+    Partition->DescSize = Sb->s_desc_size;
+  } else {
+    Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
+  }
+
+  if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {
+    DEBUG ((DEBUG_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
+    return EFI_VOLUME_CORRUPTED;
+  }
+
+  NrBlocks = (UINTN)DivU64x32Remainder (
+                      MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
+                      Partition->BlockSize,
+                      &NrBlocksRem
+                      );
+
+  if (NrBlocksRem != 0) {
+    NrBlocks++;
+  }
+
+  Partition->BlockGroups = Ext4AllocAndReadBlocks (Partition, NrBlocks, Partition->BlockSize == 1024 ? 2 : 1);
+
+  if (Partition->BlockGroups == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  for (Index = 0; Index < Partition->NumberBlockGroups; Index++) {
+    Desc = Ext4GetBlockGroupDesc (Partition, Index);
+    if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {
+      DEBUG ((DEBUG_ERROR, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
+      FreePool (Partition->BlockGroups);
+      return EFI_VOLUME_CORRUPTED;
+    }
+  }
+
+  // RootDentry will serve as the basis of our directory entry tree.
+  Partition->RootDentry = Ext4CreateDentry (L"\\", NULL);
+
+  if (Partition->RootDentry == NULL) {
+    FreePool (Partition->BlockGroups);
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  // 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);
+
+  if (EFI_ERROR (Status)) {
+    Ext4UnrefDentry (Partition->RootDentry);
+    FreePool (Partition->BlockGroups);
+  }
+
+  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 (!EXT4_HAS_METADATA_CSUM (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:
+      ASSERT (FALSE);
+      return 0;
+  }
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c b/Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c
new file mode 100644
index 000000000000..0905417ffb88
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c
@@ -0,0 +1,261 @@
+/** @file
+  Symbolic links routines
+
+  Copyright (c) 2022 Savva Mitrofanov All rights reserved.
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include "Ext4Dxe.h"
+
+/**
+   Detects if a symlink is a fast symlink.
+
+   @param[in]      File          Pointer to the opened file.
+
+   @return BOOLEAN         Whether symlink is a fast symlink
+**/
+STATIC
+BOOLEAN
+Ext4SymlinkIsFastSymlink (
+  IN CONST EXT4_FILE  *File
+  )
+{
+  //
+  // Detection logic of the fast-symlink splits into two behaviors - old and new.
+  // The old behavior is based on comparing the extended attribute blocks
+  // with the inode's i_blocks, and if it's zero we know the inode isn't storing
+  // the link in filesystem blocks, so we look to the inode->i_data.
+  // The new behavior is apparently needed only with the large EA inode feature.
+  // In this case we check that inode size less than maximum fast symlink size.
+  // So, we revert to the old behavior if the large EA inode feature is not set.
+  //
+  UINT32  FileAcl;
+  UINT32  ExtAttrBlocks;
+
+  if ((File->Inode->i_flags & EXT4_EA_INODE_FL) == 0) {
+    FileAcl = File->Inode->i_file_acl;
+    if (EXT4_IS_64_BIT (File->Partition)) {
+      //
+      // We don't care about final value, we are just checking for any bit is set
+      // so, thats why we neglect LShiftU64(.., 32)
+      //
+      FileAcl |= File->Inode->i_osd2.data_linux.l_i_file_acl_high;
+    }
+
+    ExtAttrBlocks = FileAcl != 0 ? (File->Partition->BlockSize >> 9) : 0;
+
+    return File->Inode->i_blocks == ExtAttrBlocks;
+  }
+
+  return EXT4_INODE_SIZE (File->Inode) <= EXT4_FAST_SYMLINK_MAX_SIZE;
+}
+
+/**
+  Reads a fast symlink file.
+
+  @param[in]      Partition   Pointer to the ext4 partition.
+  @param[in]      File        Pointer to the open symlink file.
+  @param[out]     AsciiSymlink     Pointer to the output ascii symlink string.
+  @param[out]     AsciiSymlinkSize Pointer to the output ascii symlink string length.
+
+  @retval EFI_SUCCESS            Fast symlink was read.
+  @retval EFI_OUT_OF_RESOURCES   Memory allocation error.
+**/
+STATIC
+EFI_STATUS
+Ext4ReadFastSymlink (
+  IN     EXT4_PARTITION  *Partition,
+  IN     EXT4_FILE       *File,
+  OUT    CHAR8           **AsciiSymlink,
+  OUT    UINT32          *AsciiSymlinkSize
+  )
+{
+  UINT32  SymlinkSize;
+  CHAR8   *AsciiSymlinkTmp;
+
+  //
+  // Fast-symlink's EXT4_INODE_SIZE is not necessarily validated when we checked it in
+  // Ext4SymlinkIsFastSymlink(), so truncate if necessary.
+  //
+  SymlinkSize = (UINT32)MIN (EXT4_INODE_SIZE (File->Inode), EXT4_FAST_SYMLINK_MAX_SIZE);
+
+  AsciiSymlinkTmp = AllocatePool (SymlinkSize + 1);
+  if (AsciiSymlinkTmp == NULL) {
+    DEBUG ((DEBUG_ERROR, "[ext4] Failed to allocate symlink ascii string buffer\n"));
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  CopyMem (AsciiSymlinkTmp, File->Inode->i_data, SymlinkSize);
+
+  //
+  // Add null-terminator
+  //
+  AsciiSymlinkTmp[SymlinkSize] = '\0';
+
+  *AsciiSymlink     = AsciiSymlinkTmp;
+  *AsciiSymlinkSize = SymlinkSize + 1;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Reads a slow symlink file.
+
+  @param[in]      Partition        Pointer to the ext4 partition.
+  @param[in]      File             Pointer to the open symlink file.
+  @param[out]     AsciiSymlink     Pointer to the output ascii symlink string.
+  @param[out]     AsciiSymlinkSize Pointer to the output ascii symlink string length.
+
+  @retval EFI_SUCCESS           Slow symlink was read.
+  @retval EFI_OUT_OF_RESOURCES  Memory allocation error.
+  @retval EFI_INVALID_PARAMETER Slow symlink path has incorrect length
+  @retval EFI_VOLUME_CORRUPTED  Symlink read block size differ from inode value
+**/
+STATIC
+EFI_STATUS
+Ext4ReadSlowSymlink (
+  IN     EXT4_PARTITION  *Partition,
+  IN     EXT4_FILE       *File,
+  OUT    CHAR8           **AsciiSymlink,
+  OUT    UINT32          *AsciiSymlinkSize
+  )
+{
+  EFI_STATUS  Status;
+  CHAR8       *SymlinkTmp;
+  UINT64      SymlinkSizeTmp;
+  UINT32      SymlinkAllocateSize;
+  UINTN       ReadSize;
+
+  SymlinkSizeTmp = EXT4_INODE_SIZE (File->Inode);
+
+  //
+  // Allocate EXT4_INODE_SIZE + 1
+  //
+  if (SymlinkSizeTmp >= EXT4_EFI_PATH_MAX) {
+    DEBUG ((
+      DEBUG_WARN,
+      "[ext4] Warn: symlink path maximum length was hit!\n"
+      ));
+    return EFI_INVALID_PARAMETER;
+  }
+
+  SymlinkAllocateSize = (UINT32)SymlinkSizeTmp + 1;
+
+  SymlinkTmp = AllocatePool (SymlinkAllocateSize);
+  if (SymlinkTmp == NULL) {
+    DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink ascii string buffer\n"));
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  ReadSize = (UINTN)SymlinkSizeTmp;
+  Status   = Ext4Read (Partition, File, SymlinkTmp, File->Position, &ReadSize);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_FS, "[ext4] Failed to read symlink from blocks with status %r\n", Status));
+    FreePool (SymlinkTmp);
+    return Status;
+  }
+
+  //
+  // Add null-terminator
+  //
+  SymlinkTmp[SymlinkSizeTmp] = '\0';
+
+  if (SymlinkSizeTmp != ReadSize) {
+    DEBUG ((
+      DEBUG_FS,
+      "[ext4] Error! The size of the read block doesn't match the value from the inode!\n"
+      ));
+    return EFI_VOLUME_CORRUPTED;
+  }
+
+  *AsciiSymlinkSize = SymlinkAllocateSize;
+  *AsciiSymlink     = SymlinkTmp;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Reads a symlink file.
+
+  @param[in]      Partition   Pointer to the ext4 partition.
+  @param[in]      File        Pointer to the open symlink file.
+  @param[out]     Symlink     Pointer to the output unicode symlink string.
+
+  @retval EFI_SUCCESS           Symlink was read.
+  @retval EFI_ACCESS_DENIED     Symlink is encrypted.
+  @retval EFI_OUT_OF_RESOURCES  Memory allocation error.
+  @retval EFI_INVALID_PARAMETER Symlink path has incorrect length
+  @retval EFI_VOLUME_CORRUPTED  Symlink read block size differ from inode value
+**/
+EFI_STATUS
+Ext4ReadSymlink (
+  IN     EXT4_PARTITION  *Partition,
+  IN     EXT4_FILE       *File,
+  OUT    CHAR16          **Symlink
+  )
+{
+  EFI_STATUS  Status;
+  CHAR8       *SymlinkTmp;
+  UINT32      SymlinkSize;
+  CHAR16      *Symlink16Tmp;
+  CHAR16      *Needle;
+
+  //
+  // Assume that we alread read Inode via Ext4ReadInode
+  // Skip reading, just check encryption flag
+  //
+  if ((File->Inode->i_flags & EXT4_ENCRYPT_FL) != 0) {
+    DEBUG ((DEBUG_WARN, "[ext4] Warn: symlink is encrypted\n"));
+    return EFI_ACCESS_DENIED;
+  }
+
+  if (Ext4SymlinkIsFastSymlink (File)) {
+    Status = Ext4ReadFastSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
+  } else {
+    Status = Ext4ReadSlowSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
+  }
+
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_FS, "[ext4] Symlink read error with Status %r\n", Status));
+    return Status;
+  }
+
+  Symlink16Tmp = AllocatePool (SymlinkSize * sizeof (CHAR16));
+  if (Symlink16Tmp == NULL) {
+    DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink unicode string buffer\n"));
+    FreePool (SymlinkTmp);
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  Status = AsciiStrToUnicodeStrS (
+             SymlinkTmp,
+             Symlink16Tmp,
+             SymlinkSize
+             );
+
+  FreePool (SymlinkTmp);
+
+  if (EFI_ERROR (Status)) {
+    DEBUG ((
+      DEBUG_FS,
+      "[ext4] Failed to convert ascii symlink to unicode with Status %r\n",
+      Status
+      ));
+    FreePool (Symlink16Tmp);
+    FreePool (SymlinkTmp);
+    return Status;
+  }
+
+  //
+  // Convert to UEFI slashes
+  //
+  for (Needle = Symlink16Tmp; *Needle != L'\0'; Needle++) {
+    if (*Needle == L'/') {
+      *Needle = L'\\';
+    }
+  }
+
+  *Symlink = Symlink16Tmp;
+
+  return Status;
+}
diff --git a/Filesystems/Ext4Pkg/Ext4Libs.dsc.inc b/Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
new file mode 100644
index 000000000000..078183e0ccc6
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
@@ -0,0 +1,11 @@
+## @file
+# Ext4 DSC include file for [LibraryClasses] section of all Architectures.
+#
+# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+!if $(EXT4_ENABLE) == TRUE
+  BaseUcs2Utf8Lib|RedfishPkg/Library/BaseUcs2Utf8Lib/BaseUcs2Utf8Lib.inf
+!endif
diff --git a/Filesystems/Ext4Pkg/Ext4Pkg.dec b/Filesystems/Ext4Pkg/Ext4Pkg.dec
new file mode 100644
index 000000000000..f1f8b39c3c7e
--- /dev/null
+++ b/Filesystems/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/Filesystems/Ext4Pkg/Ext4Pkg.dsc b/Filesystems/Ext4Pkg/Ext4Pkg.dsc
new file mode 100644
index 000000000000..482965bb7757
--- /dev/null
+++ b/Filesystems/Ext4Pkg/Ext4Pkg.dsc
@@ -0,0 +1,75 @@
+## @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
+
+!include MdePkg/MdeLibs.dsc.inc
+
+[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
+  
+  #
+  # Required for stack protector support
+  #
+  NULL|MdePkg/Library/BaseStackCheckLib/BaseStackCheckLib.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]
+  Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
diff --git a/Filesystems/Ext4Pkg/Ext4Pkg.uni b/Filesystems/Ext4Pkg/Ext4Pkg.uni
new file mode 100644
index 000000000000..abeadd8fd926
--- /dev/null
+++ b/Filesystems/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."
diff --git a/Maintainers.txt b/Maintainers.txt
index 7e98083685bc..81346b0caddf 100644
--- a/Maintainers.txt
+++ b/Maintainers.txt
@@ -218,6 +218,13 @@ M: Ray Ni <ray.ni@intel.com> [niruiyu]
 T: svn - https://svn.code.sf.net/p/edk2-fatdriver2/code/trunk/EnhancedFat
 T: git - https://github.com/tianocore/edk2-FatPkg.git
 
+Filesystems/Ext4Pkg
+F: Filesystems/Ext4Pkg/
+M: Pedro Falcato <pedro.falcato@gmail.com> [heatd]
+R: Marvin Häuser <mhaeuser@posteo.de> [mhaeuser]
+T: git (maintainer's tree) - https://github.com/heatd/edk2.git
+S: Maintained
+
 FmpDevicePkg
 F: FmpDevicePkg/
 W: https://github.com/tianocore/tianocore.github.io/wiki/FmpDevicePkg
-- 
2.38.1


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

* Re: [RFC PATCH 1/1] Ext4Pkg: Add Ext4Pkg to edk2.git
  2022-11-16 23:34 [RFC PATCH 1/1] Ext4Pkg: Add Ext4Pkg to edk2.git Pedro Falcato
@ 2022-11-17 10:28 ` Marvin Häuser
  0 siblings, 0 replies; 2+ messages in thread
From: Marvin Häuser @ 2022-11-17 10:28 UTC (permalink / raw)
  To: Pedro Falcato
  Cc: edk2-devel-groups-io, Andrew Fish, Leif Lindholm,
	Michael D Kinney

Good day all,

for what it's worth, as a reviewer, I support this proposal.
We would also greatly benefit from this change at Acidanthera, as we do not want to depend on edk2-platforms for various reasons (including lack of versioning).

Best regards,
Marvin

> On 17. Nov 2022, at 00:34, Pedro Falcato <pedro.falcato@gmail.com> wrote:
> 
> Having non-platform packages in edk2-platforms has proven to be a poor
> idea over time.
> 
> 1) Having features in edk2-platforms makes little sense, and new packages
>   are still being added over time to edk2/.
> 
> 2) edk2-platforms has no versioning, no stable tags - you don't know
>   what edk2 tag you're supposed to compile against, and builds just
>   break.
> 
> 3) edk2-platforms is missing all the good innovation (being) added to
>   edk2.git, like CodeQL and GitHub PRs; there are seemingly no plans to
>   add this to edk2-platforms.
> 
> 4) Existing platforms in edk2.git like EmulatorPkg and OvmfPkg cannot
>   depend on an edk2-platforms package; there are also no plans to add
>   these to edk2-platforms.
> 
> Therefore, add Ext4Pkg to edk2.git, in Filesystems/.
> 
> Signed-off-by: Pedro Falcato <pedro.falcato@gmail.com>
> Cc: Marvin Häuser <mhaeuser@posteo.de>
> Cc: Andrew Fish <afish@apple.com>
> Cc: Leif Lindholm <quic_llindhol@quicinc.com>
> Cc: Michael D Kinney <michael.d.kinney@intel.com>
> ---
> This is just an RFC patch to get comments from EDK2 stewards and relevant people.
> This patch is not supposed to be merged in its current state, as it's missing the corresponding
> change in edk2-platforms and possibly some way to keep git history.
> 
> There are very apparent flaws in current edk2-platforms, as mentioned in the commit message.
> Originally, when Ext4Pkg was proposed in GSoC 2021, we opted for edk2-platforms/Features/Ext4Pkg.
> I genuinely believe this to be a poor choice given the reasons above, and some others.
> I also believe that Ext4Pkg has proven its usefulness and maturity over the past year and a half.
> 
> The choice of Filesystems/{Package} was deliberate - adding packages to the root of edk2.git
> is clearly not sustainable, given the 26(!) packages that are already there, making for a confusing
> filesystem hierarchy (or lack-of).
> 
> Comments and suggestions are very welcome.
> 
> Filesystems/Ext4Pkg/Ext4.dsc.inc           |   16 +
> Filesystems/Ext4Pkg/Ext4.fdf.inc           |   11 +
> Filesystems/Ext4Pkg/Ext4Components.dsc.inc |   14 +
> Filesystems/Ext4Pkg/Ext4Defines.dsc.inc    |   14 +
> Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c   |  228 ++++
> Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c     |  285 +++++
> Filesystems/Ext4Pkg/Ext4Dxe/Collation.c    |  173 +++
> Filesystems/Ext4Pkg/Ext4Dxe/Directory.c    |  669 +++++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c     |  113 ++
> Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h     |  475 ++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c      |  844 ++++++++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h      | 1232 ++++++++++++++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf    |  149 +++
> Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni    |   15 +
> Filesystems/Ext4Pkg/Ext4Dxe/Extents.c      |  624 ++++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/File.c         | 1001 ++++++++++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/Inode.c        |  490 ++++++++
> Filesystems/Ext4Pkg/Ext4Dxe/Partition.c    |  132 +++
> Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c   |  355 ++++++
> Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c      |  261 +++++
> Filesystems/Ext4Pkg/Ext4Libs.dsc.inc       |   11 +
> Filesystems/Ext4Pkg/Ext4Pkg.dec            |   17 +
> Filesystems/Ext4Pkg/Ext4Pkg.dsc            |   75 ++
> Filesystems/Ext4Pkg/Ext4Pkg.uni            |   14 +
> Maintainers.txt                            |    7 +
> 25 files changed, 7225 insertions(+)
> create mode 100644 Filesystems/Ext4Pkg/Ext4.dsc.inc
> create mode 100644 Filesystems/Ext4Pkg/Ext4.fdf.inc
> create mode 100644 Filesystems/Ext4Pkg/Ext4Components.dsc.inc
> create mode 100644 Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Collation.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Directory.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Extents.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/File.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Inode.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Partition.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c
> create mode 100644 Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
> create mode 100644 Filesystems/Ext4Pkg/Ext4Pkg.dec
> create mode 100644 Filesystems/Ext4Pkg/Ext4Pkg.dsc
> create mode 100644 Filesystems/Ext4Pkg/Ext4Pkg.uni
> 
> diff --git a/Filesystems/Ext4Pkg/Ext4.dsc.inc b/Filesystems/Ext4Pkg/Ext4.dsc.inc
> new file mode 100644
> index 000000000000..7d4114c54f7b
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4.dsc.inc
> @@ -0,0 +1,16 @@
> +## @file
> +# Ext4 DSC include file for Platform DSC
> +#
> +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> +# SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +##
> +
> +[Defines]
> +!include Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
> +
> +[LibraryClasses]
> +!include Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
> +
> +[Components.common]
> +!include Filesystems/Ext4Pkg/Ext4Components.dsc.inc
> diff --git a/Filesystems/Ext4Pkg/Ext4.fdf.inc b/Filesystems/Ext4Pkg/Ext4.fdf.inc
> new file mode 100644
> index 000000000000..ee23b5ddd9eb
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4.fdf.inc
> @@ -0,0 +1,11 @@
> +## @file
> +# Ext4 FDF include file for All Architectures.
> +#
> +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> +# SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +##
> +
> +!if $(EXT4_ENABLE) == TRUE
> +  INF Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> +!endif
> diff --git a/Filesystems/Ext4Pkg/Ext4Components.dsc.inc b/Filesystems/Ext4Pkg/Ext4Components.dsc.inc
> new file mode 100644
> index 000000000000..aa9f60b46cbf
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Components.dsc.inc
> @@ -0,0 +1,14 @@
> +## @file
> +# Ext4 DSC include file for [Components] section of all Architectures.
> +#
> +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> +# SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +##
> +
> +!if $(EXT4_ENABLE) == TRUE
> +  Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf {
> +    <PcdsFixedAtBuild>
> +      gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000007
> +  }
> +!endif
> diff --git a/Filesystems/Ext4Pkg/Ext4Defines.dsc.inc b/Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
> new file mode 100644
> index 000000000000..b02eac59cf7a
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Defines.dsc.inc
> @@ -0,0 +1,14 @@
> +## @file
> +# Ext4 DSC include file for [Defines] section of all Architectures.
> +#
> +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> +# SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +##
> +
> +!ifndef EXT4_ENABLE
> +  #
> +  # This flag is to enable or disable the ext4 feature.
> +  #
> +  DEFINE EXT4_ENABLE = TRUE
> +!endif
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c b/Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c
> new file mode 100644
> index 000000000000..cba96cd95afc
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/BlockGroup.c
> @@ -0,0 +1,228 @@
> +/** @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"
> +
> +/**
> +   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.
> +**/
> +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
> +  )
> +{
> +  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/if we add write support
> +
> +  InodeTableStart = EXT4_BLOCK_NR_FROM_HALFS (
> +                      Partition,
> +                      BlockGroup->bg_inode_table_lo,
> +                      BlockGroup->bg_inode_table_hi
> +                      );
> +
> +  Status = Ext4ReadDiskIo (
> +             Partition,
> +             Inode,
> +             Partition->InodeSize,
> +             EXT4_BLOCK_TO_BYTES (Partition, InodeTableStart) + MultU64x32 (InodeOffset, Partition->InodeSize)
> +             );
> +
> +  if (EFI_ERROR (Status)) {
> +    DEBUG ((
> +      DEBUG_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 ((
> +      DEBUG_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 = CalculateCrc16Ansi (Partition->SuperBlock.s_uuid, 16, 0);
> +  Csum = CalculateCrc16Ansi (&BlockGroupNum, sizeof (BlockGroupNum), Csum);
> +  Csum = CalculateCrc16Ansi (BlockGroupDesc, OFFSET_OF (EXT4_BLOCK_GROUP_DESC, bg_checksum), Csum);
> +  Csum = CalculateCrc16Ansi (&Dummy, sizeof (Dummy), Csum);
> +  Csum =
> +    CalculateCrc16Ansi (
> +      &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 (!EXT4_HAS_METADATA_CSUM (Partition) && !EXT4_HAS_GDT_CSUM (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 (EXT4_HAS_METADATA_CSUM (Partition)) {
> +    return Ext4CalculateBlockGroupDescChecksumMetadataCsum (Partition, BlockGroupDesc, BlockGroupNum);
> +  } else if (EXT4_HAS_GDT_CSUM (Partition)) {
> +    return Ext4CalculateBlockGroupDescChecksumGdtCsum (Partition, BlockGroupDesc, BlockGroupNum);
> +  }
> +
> +  return 0;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c b/Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c
> new file mode 100644
> index 000000000000..2bc629fe9d38
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/BlockMap.c
> @@ -0,0 +1,285 @@
> +/** @file
> +  Implementation of routines that deal with ext2/3 block maps.
> +
> +  Copyright (c) 2022 Pedro Falcato All rights reserved.
> +  SPDX-License-Identifier: BSD-2-Clause-Patent
> +**/
> +
> +#include <Ext4Dxe.h>
> +
> +// Note: The largest path we can take uses up 4 indices
> +#define EXT4_MAX_BLOCK_PATH  4
> +
> +typedef enum ext4_logical_block_type {
> +  EXT4_TYPE_DIRECT_BLOCK = 0,
> +  EXT4_TYPE_SINGLY_BLOCK,
> +  EXT4_TYPE_DOUBLY_BLOCK,
> +  EXT4_TYPE_TREBLY_BLOCK,
> +  EXT4_TYPE_BAD_BLOCK
> +} EXT4_LOGICAL_BLOCK_TYPE;
> +
> +/**
> +   @brief Detect the type of path the logical block will follow
> +
> +   @param[in] LogicalBlock The logical block
> +   @param[in] Partition    Pointer to an EXT4_PARTITION
> +   @return The type of path the logical block will need to follow
> + */
> +STATIC
> +EXT4_LOGICAL_BLOCK_TYPE
> +Ext4DetectBlockType (
> +  IN UINT32                LogicalBlock,
> +  IN CONST EXT4_PARTITION  *Partition
> +  )
> +{
> +  UINT32  Entries;
> +  UINT32  MinSinglyBlock;
> +  UINT32  MinDoublyBlock;
> +  UINT32  MinTreblyBlock;
> +  UINT32  MinQuadBlock;
> +
> +  Entries        = (Partition->BlockSize / sizeof (UINT32));
> +  MinSinglyBlock = EXT4_DBLOCKS;
> +  MinDoublyBlock = Entries + MinSinglyBlock;
> +  MinTreblyBlock = Entries * Entries + MinDoublyBlock;
> +  MinQuadBlock   = Entries * Entries * Entries + MinTreblyBlock; // Doesn't actually exist
> +
> +  if (LogicalBlock < MinSinglyBlock) {
> +    return EXT4_TYPE_DIRECT_BLOCK;
> +  } else if ((LogicalBlock >= MinSinglyBlock) && (LogicalBlock < MinDoublyBlock)) {
> +    return EXT4_TYPE_SINGLY_BLOCK;
> +  } else if ((LogicalBlock >= MinDoublyBlock) && (LogicalBlock < MinTreblyBlock)) {
> +    return EXT4_TYPE_DOUBLY_BLOCK;
> +  } else if (((LogicalBlock >= MinTreblyBlock) && (LogicalBlock < MinQuadBlock))) {
> +    return EXT4_TYPE_TREBLY_BLOCK;
> +  } else {
> +    return EXT4_TYPE_BAD_BLOCK;
> +  }
> +}
> +
> +/**
> +   @brief Get a block's path in indices
> +
> +   @param[in]  Partition       Pointer to an EXT4_PARTITION
> +   @param[in]  LogicalBlock    Logical block
> +   @param[out] BlockPath       Pointer to an array of EXT4_MAX_BLOCK_PATH elements, where the
> +                               indices we'll need to read are inserted.
> +   @return The number of path elements that are required (and were inserted in BlockPath)
> + */
> +UINTN
> +Ext4GetBlockPath (
> +  IN  CONST EXT4_PARTITION  *Partition,
> +  IN  UINT32                LogicalBlock,
> +  OUT EXT2_BLOCK_NR         BlockPath[EXT4_MAX_BLOCK_PATH]
> +  )
> +{
> +  // The logic behind the block map is very much like a page table
> +  // Let's think of blocks with 512 entries (exactly like a page table on x64).
> +  // On doubly indirect block paths, we subtract the min doubly blocks from the logical block.
> +  // The top 9 bits of the result are the index inside the dind block, the bottom 9 bits are the
> +  // index inside the ind block. Since Entries is always a power of 2, entries - 1 will give us
> +  // a mask of the BlockMapBits.
> +  // Note that all this math could be done with ands and shifts (similar implementations exist
> +  // in a bunch of other places), but I'm doing it a simplified way with divs and modulus,
> +  // since it's not going to be a bottleneck anyway.
> +
> +  UINT32  Entries;
> +  UINT32  EntriesEntries;
> +  UINT32  MinSinglyBlock;
> +  UINT32  MinDoublyBlock;
> +  UINT32  MinTreblyBlock;
> +
> +  EXT4_LOGICAL_BLOCK_TYPE  Type;
> +
> +  Entries        = (Partition->BlockSize / sizeof (UINT32));
> +  EntriesEntries = Entries * Entries;
> +
> +  MinSinglyBlock = EXT4_DBLOCKS;
> +  MinDoublyBlock = Entries + MinSinglyBlock;
> +  MinTreblyBlock = EntriesEntries + MinDoublyBlock;
> +
> +  Type = Ext4DetectBlockType (LogicalBlock, Partition);
> +
> +  switch (Type) {
> +    case EXT4_TYPE_DIRECT_BLOCK:
> +      BlockPath[0] = LogicalBlock;
> +      break;
> +    case EXT4_TYPE_SINGLY_BLOCK:
> +      BlockPath[0] = EXT4_IND_BLOCK;
> +      BlockPath[1] = LogicalBlock - EXT4_DBLOCKS;
> +      break;
> +    case EXT4_TYPE_DOUBLY_BLOCK:
> +      BlockPath[0]  = EXT4_DIND_BLOCK;
> +      LogicalBlock -= MinDoublyBlock;
> +      BlockPath[1]  = LogicalBlock / Entries;
> +      BlockPath[2]  = LogicalBlock % Entries;
> +      break;
> +    case EXT4_TYPE_TREBLY_BLOCK:
> +      BlockPath[0]  = EXT4_DIND_BLOCK;
> +      LogicalBlock -= MinTreblyBlock;
> +      BlockPath[1]  = LogicalBlock / EntriesEntries;
> +      BlockPath[2]  = (LogicalBlock % EntriesEntries) / Entries;
> +      BlockPath[3]  = (LogicalBlock % EntriesEntries) % Entries;
> +      break;
> +    default:
> +      // EXT4_TYPE_BAD_BLOCK
> +      break;
> +  }
> +
> +  return Type + 1;
> +}
> +
> +/**
> +   @brief Get an extent from a block map
> +   Note: Also parses file holes and creates uninitialised extents from them.
> +
> +   @param[in]  Buffer          Buffer of block pointers
> +   @param[in]  IndEntries      Number of entries in this block pointer table
> +   @param[in]  StartIndex      The start index from which we want to find a contiguous extent
> +   @param[out] Extent          Pointer to the resulting EXT4_EXTENT
> + */
> +VOID
> +Ext4GetExtentInBlockMap (
> +  IN CONST UINT32  *Buffer,
> +  IN CONST UINT32  IndEntries,
> +  IN UINT32        StartIndex,
> +  OUT EXT4_EXTENT  *Extent
> +  )
> +{
> +  UINT32  Index;
> +  UINT32  FirstBlock;
> +  UINT32  LastBlock;
> +  UINT16  Count;
> +
> +  Count      = 1;
> +  LastBlock  = Buffer[StartIndex];
> +  FirstBlock = LastBlock;
> +
> +  if (FirstBlock == EXT4_BLOCK_FILE_HOLE) {
> +    // File hole, let's see how many blocks this hole spans
> +    Extent->ee_start_hi = 0;
> +    Extent->ee_start_lo = 0;
> +
> +    for (Index = StartIndex + 1; Index < IndEntries; Index++) {
> +      if (Count == EXT4_EXTENT_MAX_INITIALIZED - 1) {
> +        // We've reached the max size of an uninit extent, break
> +        break;
> +      }
> +
> +      if (Buffer[Index] == EXT4_BLOCK_FILE_HOLE) {
> +        Count++;
> +      } else {
> +        break;
> +      }
> +    }
> +
> +    // We mark the extent as uninitialised, although there's a difference between uninit
> +    // extents and file holes.
> +    Extent->ee_len = EXT4_EXTENT_MAX_INITIALIZED + Count;
> +    return;
> +  }
> +
> +  for (Index = StartIndex + 1; Index < IndEntries; Index++) {
> +    if (Count == EXT4_EXTENT_MAX_INITIALIZED) {
> +      // We've reached the max size of an extent, break
> +      break;
> +    }
> +
> +    if ((Buffer[Index] == LastBlock + 1) && (Buffer[Index] != EXT4_BLOCK_FILE_HOLE)) {
> +      Count++;
> +    } else {
> +      break;
> +    }
> +
> +    LastBlock = Buffer[Index];
> +  }
> +
> +  Extent->ee_start_lo = FirstBlock;
> +  Extent->ee_start_hi = 0;
> +  Extent->ee_len      = Count;
> +}
> +
> +/**
> +   Retrieves an extent from an EXT2/3 inode (with a blockmap).
> +   @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
> +Ext4GetBlocks (
> +  IN  EXT4_PARTITION  *Partition,
> +  IN  EXT4_FILE       *File,
> +  IN  EXT2_BLOCK_NR   LogicalBlock,
> +  OUT EXT4_EXTENT     *Extent
> +  )
> +{
> +  EXT4_INODE     *Inode;
> +  EXT2_BLOCK_NR  BlockPath[EXT4_MAX_BLOCK_PATH];
> +  UINTN          BlockPathLength;
> +  UINTN          Index;
> +  UINT32         *Buffer;
> +  EFI_STATUS     Status;
> +  UINT32         Block;
> +  UINT32         BlockIndex;
> +
> +  Inode = File->Inode;
> +
> +  BlockPathLength = Ext4GetBlockPath (Partition, LogicalBlock, BlockPath);
> +
> +  if (BlockPathLength - 1 == EXT4_TYPE_BAD_BLOCK) {
> +    // Bad logical block (out of range)
> +    return EFI_NO_MAPPING;
> +  }
> +
> +  Extent->ee_block = LogicalBlock;
> +
> +  if (BlockPathLength == 1) {
> +    // Fast path for blocks 0 - 12 that skips allocations
> +    Ext4GetExtentInBlockMap (Inode->i_data, EXT4_DBLOCKS, BlockPath[0], Extent);
> +
> +    return EFI_SUCCESS;
> +  }
> +
> +  Buffer = AllocatePool (Partition->BlockSize);
> +  if (Buffer == NULL) {
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  // Note the BlockPathLength - 1 so we don't end up reading the final block
> +  for (Index = 0; Index < BlockPathLength - 1; Index++) {
> +    BlockIndex = BlockPath[Index];
> +
> +    if (Index == 0) {
> +      Block = Inode->i_data[BlockIndex];
> +    } else {
> +      Block = Buffer[BlockIndex];
> +    }
> +
> +    if (Block == EXT4_BLOCK_FILE_HOLE) {
> +      FreePool (Buffer);
> +      return EFI_NO_MAPPING;
> +    }
> +
> +    Status = Ext4ReadBlocks (Partition, Buffer, 1, Block);
> +
> +    if (EFI_ERROR (Status)) {
> +      FreePool (Buffer);
> +      return Status;
> +    }
> +  }
> +
> +  Ext4GetExtentInBlockMap (
> +    Buffer,
> +    Partition->BlockSize / sizeof (UINT32),
> +    BlockPath[BlockPathLength - 1],
> +    Extent
> +    );
> +
> +  FreePool (Buffer);
> +
> +  return EFI_SUCCESS;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Collation.c b/Filesystems/Ext4Pkg/Ext4Dxe/Collation.c
> new file mode 100644
> index 000000000000..91d172b1cb89
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Collation.c
> @@ -0,0 +1,173 @@
> +/** @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.
> +**/
> +
> +/**
> +  Worker function to initialize Unicode Collation support.
> +
> +  It tries to locate Unicode Collation (2) protocol and matches it with current
> +  platform language code.
> +
> +  @param[in]  DriverHandle         The handle used to open Unicode Collation (2) protocol.
> +  @param[in]  ProtocolGuid         The pointer to Unicode Collation (2) protocol GUID.
> +  @param[in]  VariableName         The name of the RFC 4646 or ISO 639-2 language variable.
> +  @param[in]  DefaultLanguage      The default language in case the RFC 4646 or ISO 639-2 language is absent.
> +
> +  @retval EFI_SUCCESS          The Unicode Collation (2) protocol has been successfully located.
> +  @retval Others               The Unicode Collation (2) protocol has not been located.
> +
> +**/
> +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;
> +  UINTN                           Idx;
> +  CHAR8                           *BestLanguage;
> +
> +  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;
> +  }
> +
> +  for (Idx = 0; Idx < NumHandles; Idx++) {
> +    Status = gBS->OpenProtocol (
> +                    Handles[Idx],
> +                    ProtocolGuid,
> +                    (VOID **)&Uci,
> +                    DriverHandle,
> +                    NULL,
> +                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
> +                    );
> +
> +    if (EFI_ERROR (Status)) {
> +      continue;
> +    }
> +
> +    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/Filesystems/Ext4Pkg/Ext4Dxe/Directory.c b/Filesystems/Ext4Pkg/Ext4Dxe/Directory.c
> new file mode 100644
> index 000000000000..4441e6d192b6
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Directory.c
> @@ -0,0 +1,669 @@
> +/** @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;
> +}
> +
> +/**
> +   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;
> +
> +  RequiredSize = Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;
> +
> +  if (Dirent->rec_len < RequiredSize) {
> +    DEBUG ((DEBUG_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) != 0) {
> +    return FALSE;
> +  }
> +
> +  return TRUE;
> +}
> +
> +/**
> +   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     *Name,
> +  IN EXT4_PARTITION   *Partition,
> +  OUT EXT4_DIR_ENTRY  *Result
> +  )
> +{
> +  EFI_STATUS      Status;
> +  CHAR8           *Buf;
> +  UINT64          Off;
> +  EXT4_INODE      *Inode;
> +  UINT64          DirInoSize;
> +  UINT32          BlockRemainder;
> +  UINTN           Length;
> +  EXT4_DIR_ENTRY  *Entry;
> +  UINTN           RemainingBlock;
> +  CHAR16          DirentUcs2Name[EXT4_NAME_MAX + 1];
> +  UINTN           ToCopy;
> +  UINTN           BlockOffset;
> +
> +  Status = EFI_NOT_FOUND;
> +  Buf    = AllocatePool (Partition->BlockSize);
> +
> +  if (Buf == NULL) {
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  Off = 0;
> +
> +  Inode      = Directory->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) {
> +    Length = Partition->BlockSize;
> +
> +    Status = Ext4Read (Partition, Directory, Buf, Off, &Length);
> +
> +    if (Status != EFI_SUCCESS) {
> +      FreePool (Buf);
> +      return Status;
> +    }
> +
> +    for (BlockOffset = 0; BlockOffset < Partition->BlockSize; ) {
> +      Entry          = (EXT4_DIR_ENTRY *)(Buf + BlockOffset);
> +      RemainingBlock = Partition->BlockSize - BlockOffset;
> +      // Check if the minimum directory entry fits inside [BlockOffset, EndOfBlock]
> +      if (RemainingBlock < EXT4_MIN_DIR_ENTRY_LEN) {
> +        FreePool (Buf);
> +        return EFI_VOLUME_CORRUPTED;
> +      }
> +
> +      if (!Ext4ValidDirent (Entry)) {
> +        FreePool (Buf);
> +        return EFI_VOLUME_CORRUPTED;
> +      }
> +
> +      if ((Entry->name_len > RemainingBlock) || (Entry->rec_len > RemainingBlock)) {
> +        // Corrupted filesystem
> +        FreePool (Buf);
> +        return EFI_VOLUME_CORRUPTED;
> +      }
> +
> +      // Unused entry
> +      if (Entry->inode == 0) {
> +        BlockOffset += Entry->rec_len;
> +        continue;
> +      }
> +
> +      Status = Ext4GetUcs2DirentName (Entry, DirentUcs2Name);
> +
> +      /* 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.
> +        BlockOffset += Entry->rec_len;
> +        continue;
> +      }
> +
> +      if ((Entry->name_len == StrLen (Name)) &&
> +          !Ext4StrCmpInsensitive (DirentUcs2Name, (CHAR16 *)Name))
> +      {
> +        ToCopy = MIN (Entry->rec_len, sizeof (EXT4_DIR_ENTRY));
> +
> +        CopyMem (Result, Entry, ToCopy);
> +        FreePool (Buf);
> +        return EFI_SUCCESS;
> +      }
> +
> +      BlockOffset += 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.
> +   @param[in]      Directory   Pointer to the opened directory.
> +
> +   @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,
> +  IN  EXT4_FILE       *Directory
> +  )
> +{
> +  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;
> +  }
> +
> +  if (StrCmp (FileName, L".") == 0) {
> +    // We're using the parent directory's dentry
> +    File->Dentry = Directory->Dentry;
> +
> +    ASSERT (File->Dentry != NULL);
> +
> +    Ext4RefDentry (File->Dentry);
> +  } else if (StrCmp (FileName, L"..") == 0) {
> +    // Using the parent's parent's dentry
> +    File->Dentry = Directory->Dentry->Parent;
> +
> +    ASSERT (File->Dentry != NULL);
> +
> +    Ext4RefDentry (File->Dentry);
> +  } else {
> +    File->Dentry = Ext4CreateDentry (FileName, Directory->Dentry);
> +
> +    if (!File->Dentry) {
> +      goto Error;
> +    }
> +  }
> +
> +  Status = Ext4InitExtentsMap (File);
> +
> +  if (EFI_ERROR (Status)) {
> +    goto Error;
> +  }
> +
> +  File->InodeNum = Entry->inode;
> +
> +  Ext4SetupFile (File, 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->Dentry != NULL) {
> +      Ext4UnrefDentry (File->Dentry);
> +    }
> +
> +    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, Directory);
> +}
> +
> +/**
> +  Open the root directory on a volume.
> +
> +  @param[in]   This A pointer to the volume to open the root directory.
> +  @param[out]  Root A pointer to the location to return the opened file handle for the
> +                    root directory.
> +
> +  @retval EFI_SUCCESS          The device was opened.
> +  @retval EFI_UNSUPPORTED      This volume does not support the requested file system type.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_ACCESS_DENIED    The service denied access to the file.
> +  @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of resources.
> +  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
> +                               longer supported. Any existing file handles for this volume are
> +                               no longer valid. To access the files on the new medium, the
> +                               volume must be reopened with OpenVolume().
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4OpenVolume (
> +  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
> +  OUT EFI_FILE_PROTOCOL               **Root
> +  )
> +{
> +  EXT4_INODE      *RootInode;
> +  EFI_STATUS      Status;
> +  EXT4_FILE       *RootDir;
> +  EXT4_PARTITION  *Partition;
> +
> +  Partition = (EXT4_PARTITION *)This;
> +
> +  Status = Ext4ReadInode (Partition, EXT4_ROOT_INODE_NR, &RootInode);
> +
> +  if (EFI_ERROR (Status)) {
> +    DEBUG ((DEBUG_ERROR, "[ext4] Could not open root inode - error %r\n", Status));
> +    return Status;
> +  }
> +
> +  RootDir = AllocateZeroPool (sizeof (EXT4_FILE));
> +
> +  if (RootDir == NULL) {
> +    FreePool (RootInode);
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  RootDir->Inode    = RootInode;
> +  RootDir->InodeNum = EXT4_ROOT_INODE_NR;
> +
> +  Status = Ext4InitExtentsMap (RootDir);
> +
> +  if (EFI_ERROR (Status)) {
> +    FreePool (RootInode);
> +    FreePool (RootDir);
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  Ext4SetupFile (RootDir, Partition);
> +  *Root = &RootDir->Protocol;
> +
> +  InsertTailList (&Partition->OpenFiles, &RootDir->OpenFilesListNode);
> +
> +  ASSERT (Partition->RootDentry != NULL);
> +  RootDir->Dentry = Partition->RootDentry;
> +
> +  Ext4RefDentry (RootDir->Dentry);
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +   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
> +  )
> +{
> +  EXT4_INODE      *DirIno;
> +  EFI_STATUS      Status;
> +  UINT64          DirInoSize;
> +  UINTN           Len;
> +  UINT32          BlockRemainder;
> +  EXT4_DIR_ENTRY  Entry;
> +  EXT4_FILE       *TempFile;
> +  BOOLEAN         ShouldSkip;
> +  BOOLEAN         IsDotOrDotDot;
> +
> +  DirIno     = File->Inode;
> +  Status     = EFI_SUCCESS;
> +  DirInoSize = EXT4_INODE_SIZE (DirIno);
> +
> +  DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
> +  if (BlockRemainder != 0) {
> +    // Directory inodes need to have block aligned sizes
> +    return EFI_VOLUME_CORRUPTED;
> +  }
> +
> +  while (TRUE) {
> +    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 (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 ((DEBUG_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
> +      Status = EFI_VOLUME_CORRUPTED;
> +      goto Out;
> +    }
> +
> +    // Check if the entire dir entry length fits in Len
> +    if (Len < (UINTN)(EXT4_MIN_DIR_ENTRY_LEN + Entry.name_len)) {
> +      Status = EFI_VOLUME_CORRUPTED;
> +      goto Out;
> +    }
> +
> +    // We don't care about passing . or .. entries to the caller of ReadDir(),
> +    // since they're generally useless entries *and* may break things if too
> +    // many callers assume FAT32.
> +
> +    // Entry.name_len may be 0 if it's a nameless entry, like an unused entry
> +    // or a checksum at the end of the directory block.
> +    // memcmp (and CompareMem) return 0 when the passed length is 0.
> +
> +    IsDotOrDotDot = Entry.name_len != 0 &&
> +                    (CompareMem (Entry.name, ".", Entry.name_len) == 0 ||
> +                     CompareMem (Entry.name, "..", Entry.name_len) == 0);
> +
> +    // When inode = 0, it's unused.
> +    ShouldSkip = Entry.inode == 0 || IsDotOrDotDot;
> +
> +    if (ShouldSkip) {
> +      Offset += Entry.rec_len;
> +      continue;
> +    }
> +
> +    Status = Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &Entry, File);
> +
> +    if (EFI_ERROR (Status)) {
> +      goto Out;
> +    }
> +
> +    Status = Ext4GetFileInfo (TempFile, Buffer, OutLength);
> +    if (!EFI_ERROR (Status)) {
> +      File->Position = Offset + Entry.rec_len;
> +    }
> +
> +    Ext4CloseInternal (TempFile);
> +
> +    goto Out;
> +  }
> +
> +  Status = EFI_SUCCESS;
> +Out:
> +  return Status;
> +}
> +
> +/**
> +   Removes a dentry from the other's list.
> +
> +   @param[in out]            Parent       Pointer to the parent EXT4_DENTRY.
> +   @param[in out]            ToBeRemoved  Pointer to the child EXT4_DENTRY.
> +**/
> +STATIC
> +VOID
> +Ext4RemoveDentry (
> +  IN OUT EXT4_DENTRY  *Parent,
> +  IN OUT EXT4_DENTRY  *ToBeRemoved
> +  )
> +{
> +  ASSERT (IsNodeInList (&ToBeRemoved->ListNode, &Parent->Children));
> +  RemoveEntryList (&ToBeRemoved->ListNode);
> +}
> +
> +/**
> +   Adds a dentry to the other's list.
> +
> +   The dentry that is added to the other one's list gets ->Parent set to Parent,
> +   and the parent gets its reference count incremented.
> +
> +   @param[in out]            Parent       Pointer to the parent EXT4_DENTRY.
> +   @param[in out]            ToBeAdded    Pointer to the child EXT4_DENTRY.
> +**/
> +STATIC
> +VOID
> +Ext4AddDentry (
> +  IN OUT EXT4_DENTRY  *Parent,
> +  IN OUT EXT4_DENTRY  *ToBeAdded
> +  )
> +{
> +  ToBeAdded->Parent = Parent;
> +  InsertTailList (&Parent->Children, &ToBeAdded->ListNode);
> +  Ext4RefDentry (Parent);
> +}
> +
> +/**
> +   Creates a new dentry object.
> +
> +   @param[in]              Name        Name of the dentry.
> +   @param[in out opt]      Parent      Parent dentry, if it's not NULL.
> +
> +   @return The new allocated and initialised dentry.
> +           The ref count will be set to 1.
> +**/
> +EXT4_DENTRY *
> +Ext4CreateDentry (
> +  IN CONST CHAR16     *Name,
> +  IN OUT EXT4_DENTRY  *Parent  OPTIONAL
> +  )
> +{
> +  EXT4_DENTRY  *Dentry;
> +  EFI_STATUS   Status;
> +
> +  Dentry = AllocateZeroPool (sizeof (EXT4_DENTRY));
> +
> +  if (Dentry == NULL) {
> +    return NULL;
> +  }
> +
> +  Dentry->RefCount = 1;
> +
> +  // This StrCpyS should not fail.
> +  Status = StrCpyS (Dentry->Name, ARRAY_SIZE (Dentry->Name), Name);
> +
> +  ASSERT_EFI_ERROR (Status);
> +
> +  InitializeListHead (&Dentry->Children);
> +
> +  if (Parent != NULL) {
> +    Ext4AddDentry (Parent, Dentry);
> +  }
> +
> +  DEBUG ((DEBUG_FS, "[ext4] Created dentry %s\n", Name));
> +
> +  return Dentry;
> +}
> +
> +/**
> +   Increments the ref count of the dentry.
> +
> +   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
> +**/
> +VOID
> +Ext4RefDentry (
> +  IN OUT EXT4_DENTRY  *Dentry
> +  )
> +{
> +  UINTN  OldRef;
> +
> +  OldRef = Dentry->RefCount;
> +
> +  Dentry->RefCount++;
> +
> +  // I'm not sure if this (Refcount overflow) is a valid concern,
> +  // but it's better to be safe than sorry.
> +  ASSERT (OldRef < Dentry->RefCount);
> +}
> +
> +/**
> +   Deletes the dentry.
> +
> +   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
> +**/
> +STATIC
> +VOID
> +Ext4DeleteDentry (
> +  IN OUT EXT4_DENTRY  *Dentry
> +  )
> +{
> +  if (Dentry->Parent) {
> +    Ext4RemoveDentry (Dentry->Parent, Dentry);
> +    Ext4UnrefDentry (Dentry->Parent);
> +  }
> +
> +  DEBUG ((DEBUG_FS, "[ext4] Deleted dentry %s\n", Dentry->Name));
> +  FreePool (Dentry);
> +}
> +
> +/**
> +   Decrements the ref count of the dentry.
> +   If the ref count is 0, it's destroyed.
> +
> +   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
> +
> +   @retval True if it was destroyed, false if it's alive.
> +**/
> +BOOLEAN
> +Ext4UnrefDentry (
> +  IN OUT EXT4_DENTRY  *Dentry
> +  )
> +{
> +  Dentry->RefCount--;
> +
> +  if (Dentry->RefCount == 0) {
> +    Ext4DeleteDentry (Dentry);
> +    return TRUE;
> +  }
> +
> +  return FALSE;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c b/Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c
> new file mode 100644
> index 000000000000..32da35f7d9f5
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/DiskUtil.c
> @@ -0,0 +1,113 @@
> +/** @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 EXT4_DISK_IO (Partition)->ReadDisk (
> +                                     EXT4_DISK_IO (Partition),
> +                                     EXT4_MEDIA_ID (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
> +  )
> +{
> +  UINT64  Offset;
> +  UINTN   Length;
> +
> +  Offset = MultU64x32 (BlockNumber, Partition->BlockSize);
> +  Length = NumberBlocks * Partition->BlockSize;
> +
> +  // Check for overflow on the block -> byte conversions.
> +  // Partition->BlockSize is never 0, so we don't need to check for that.
> +
> +  if (Offset > DivU64x32 ((UINT64)-1, Partition->BlockSize)) {
> +    return EFI_INVALID_PARAMETER;
> +  }
> +
> +  if (Length > (UINTN)-1/Partition->BlockSize) {
> +    return EFI_INVALID_PARAMETER;
> +  }
> +
> +  return Ext4ReadDiskIo (Partition, Buffer, Length, Offset);
> +}
> +
> +/**
> +   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;
> +  UINTN  Length;
> +
> +  Length = NumberBlocks * Partition->BlockSize;
> +
> +  if (Length > (UINTN)-1/Partition->BlockSize) {
> +    return NULL;
> +  }
> +
> +  Buf = AllocatePool (Length);
> +
> +  if (Buf == NULL) {
> +    return NULL;
> +  }
> +
> +  if (Ext4ReadBlocks (Partition, Buf, NumberBlocks, BlockNumber) != EFI_SUCCESS) {
> +    FreePool (Buf);
> +    return NULL;
> +  }
> +
> +  return Buf;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> new file mode 100644
> index 000000000000..4fd91a423324
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Disk.h
> @@ -0,0 +1,475 @@
> +/** @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_ENCRYPT_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_FAST_SYMLINK_MAX_SIZE  EXT4_NR_BLOCKS * sizeof(UINT32)
> +
> +#define EXT4_GOOD_OLD_INODE_SIZE  128U
> +
> +typedef 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;
> +} EXT4_OSD2_LINUX;
> +
> +typedef 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;
> +} EXT4_OSD2_HURD;
> +
> +typedef union {
> +  // Note: Toolchain-specific defines (such as "linux") stops us from using
> +  // simpler names down here.
> +  EXT4_OSD2_LINUX    data_linux;
> +  EXT4_OSD2_HURD     data_hurd;
> +} EXT4_OSD2;
> +
> +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;
> +
> +  EXT4_OSD2    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;
> +
> +/**
> + * EXT4 has this feature called uninitialized extents:
> + * An extent has a maximum of 32768 blocks (2^15 or 1 << 15).
> + * When we find an extent with > 32768 blocks, this extent is called
> + * uninitialized. Long story short, it's an extent that behaves as a file hole
> + * but has blocks already allocated.
> + */
> +#define EXT4_EXTENT_MAX_INITIALIZED  (1 << 15)
> +
> +typedef UINT64  EXT4_BLOCK_NR;
> +typedef UINT32  EXT2_BLOCK_NR;
> +typedef UINT32  EXT4_INO_NR;
> +
> +// 2 is always the root inode number in ext4
> +#define EXT4_ROOT_INODE_NR  2
> +
> +#define EXT4_BLOCK_FILE_HOLE  0
> +
> +#endif
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
> new file mode 100644
> index 000000000000..2a4f5a7bd0ef
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.c
> @@ -0,0 +1,844 @@
> +/** @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*
> +
> +/**
> +  Retrieves a Unicode string that is the user-readable name of the EFI Driver.
> +
> +  @param[in]  This       A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
> +  @param[in]  Language   A pointer to a three-character ISO 639-2 language identifier.
> +                     This is the language of the driver name that that the caller
> +                     is requesting, and it must match one of the languages specified
> +                     in SupportedLanguages.  The number of languages supported by a
> +                     driver is up to the driver writer.
> +  @param[out]  DriverName A pointer to the Unicode string to return.  This Unicode string
> +                     is the name of the driver specified by This in the language
> +                     specified by Language.
> +
> +  @retval EFI_SUCCESS           The Unicode string for the Driver specified by This
> +                                and the language specified by Language was returned
> +                                in DriverName.
> +  @retval EFI_INVALID_PARAMETER Language is NULL.
> +  @retval EFI_INVALID_PARAMETER DriverName is NULL.
> +  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
> +                                language specified by Language.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4ComponentNameGetDriverName (
> +  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
> +  IN  CHAR8                        *Language,
> +  OUT CHAR16                       **DriverName
> +  );
> +
> +/**
> +  Retrieves a Unicode string that is the user readable name of the controller
> +  that is being managed by an EFI Driver.
> +
> +  @param[in]  This             A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
> +  @param[in]  ControllerHandle The handle of a controller that the driver specified by
> +                               This is managing.  This handle specifies the controller
> +                               whose name is to be returned.
> +  @param[in]  ChildHandle      The handle of the child controller to retrieve the name
> +                               of.  This is an optional parameter that may be NULL.  It
> +                               will be NULL for device drivers.  It will also be NULL
> +                               for a bus drivers that wish to retrieve the name of the
> +                               bus controller.  It will not be NULL for a bus driver
> +                               that wishes to retrieve the name of a child controller.
> +  @param[in]  Language         A pointer to a three character ISO 639-2 language
> +                               identifier.  This is the language of the controller name
> +                               that the caller is requesting, and it must match one
> +                               of the languages specified in SupportedLanguages.  The
> +                               number of languages supported by a driver is up to the
> +                               driver writer.
> +  @param[out]  ControllerName  A pointer to the Unicode string to return.  This Unicode
> +                               string is the name of the controller specified by
> +                               ControllerHandle and ChildHandle in the language specified
> +                               by Language, from the point of view of the driver specified
> +                               by This.
> +
> +  @retval EFI_SUCCESS           The Unicode string for the user-readable name in the
> +                                language specified by Language for the driver
> +                                specified by This was returned in DriverName.
> +  @retval EFI_INVALID_PARAMETER ControllerHandle is NULL.
> +  @retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid EFI_HANDLE.
> +  @retval EFI_INVALID_PARAMETER Language is NULL.
> +  @retval EFI_INVALID_PARAMETER ControllerName is NULL.
> +  @retval EFI_UNSUPPORTED       The driver specified by This is not currently managing
> +                                the controller specified by ControllerHandle and
> +                                ChildHandle.
> +  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
> +                                language specified by Language.
> +
> +**/
> +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
> +
> +/**
> +  Tests to see if this driver supports a given controller. If a child device is provided,
> +  it further tests to see if this driver supports creating a handle for the specified child device.
> +
> +  This function checks to see if the driver specified by This supports the device specified by
> +  ControllerHandle. Drivers will typically use the device path attached to
> +  ControllerHandle and/or the services from the bus I/O abstraction attached to
> +  ControllerHandle to determine if the driver supports ControllerHandle. This function
> +  may be called many times during platform initialization. In order to reduce boot times, the tests
> +  performed by this function must be very small, and take as little time as possible to execute. This
> +  function must not change the state of any hardware devices, and this function must be aware that the
> +  device specified by ControllerHandle may already be managed by the same driver or a
> +  different driver. This function must match its calls to AllocatePages() with FreePages(),
> +  AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol().
> +  Because ControllerHandle may have been previously started by the same driver, if a protocol is
> +  already in the opened state, then it must not be closed with CloseProtocol(). This is required
> +  to guarantee the state of ControllerHandle is not modified by this function.
> +
> +  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
> +  @param[in]  ControllerHandle     The handle of the controller to test. This handle
> +                                   must support a protocol interface that supplies
> +                                   an I/O abstraction to the driver.
> +  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
> +                                   parameter is ignored by device drivers, and is optional for bus
> +                                   drivers. For bus drivers, if this parameter is not NULL, then
> +                                   the bus driver must determine if the bus controller specified
> +                                   by ControllerHandle and the child controller specified
> +                                   by RemainingDevicePath are both supported by this
> +                                   bus driver.
> +
> +  @retval EFI_SUCCESS              The device specified by ControllerHandle and
> +                                   RemainingDevicePath is supported by the driver specified by This.
> +  @retval EFI_ALREADY_STARTED      The device specified by ControllerHandle and
> +                                   RemainingDevicePath is already being managed by the driver
> +                                   specified by This.
> +  @retval EFI_ACCESS_DENIED        The device specified by ControllerHandle and
> +                                   RemainingDevicePath is already being managed by a different
> +                                   driver or an application that requires exclusive access.
> +                                   Currently not implemented.
> +  @retval EFI_UNSUPPORTED          The device specified by ControllerHandle and
> +                                   RemainingDevicePath is not supported by the driver specified by This.
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4IsBindingSupported (
> +  IN EFI_DRIVER_BINDING_PROTOCOL  *BindingProtocol,
> +  IN EFI_HANDLE                   ControllerHandle,
> +  IN EFI_DEVICE_PATH              *RemainingDevicePath  OPTIONAL
> +  );
> +
> +/**
> +  Starts a device controller or a bus controller.
> +
> +  The Start() function is designed to be invoked from the EFI boot service ConnectController().
> +  As a result, much of the error checking on the parameters to Start() has been moved into this
> +  common boot service. It is legal to call Start() from other locations,
> +  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
> +  1. ControllerHandle must be a valid EFI_HANDLE.
> +  2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned
> +     EFI_DEVICE_PATH_PROTOCOL.
> +  3. Prior to calling Start(), the Supported() function for the driver specified by This must
> +     have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS.
> +
> +  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
> +  @param[in]  ControllerHandle     The handle of the controller to start. This handle
> +                                   must support a protocol interface that supplies
> +                                   an I/O abstraction to the driver.
> +  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
> +                                   parameter is ignored by device drivers, and is optional for bus
> +                                   drivers. For a bus driver, if this parameter is NULL, then handles
> +                                   for all the children of Controller are created by this driver.
> +                                   If this parameter is not NULL and the first Device Path Node is
> +                                   not the End of Device Path Node, then only the handle for the
> +                                   child device specified by the first Device Path Node of
> +                                   RemainingDevicePath is created by this driver.
> +                                   If the first Device Path Node of RemainingDevicePath is
> +                                   the End of Device Path Node, no child handle is created by this
> +                                   driver.
> +
> +  @retval EFI_SUCCESS              The device was started.
> +  @retval EFI_DEVICE_ERROR         The device could not be started due to a device error.Currently not implemented.
> +  @retval EFI_OUT_OF_RESOURCES     The request could not be completed due to a lack of resources.
> +  @retval Others                   The driver failded to start the device.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Bind (
> +  IN EFI_DRIVER_BINDING_PROTOCOL  *BindingProtocol,
> +  IN EFI_HANDLE                   ControllerHandle,
> +  IN EFI_DEVICE_PATH              *RemainingDevicePath  OPTIONAL
> +  );
> +
> +/**
> +  Stops a device controller or a bus controller.
> +
> +  The Stop() function is designed to be invoked from the EFI boot service DisconnectController().
> +  As a result, much of the error checking on the parameters to Stop() has been moved
> +  into this common boot service. It is legal to call Stop() from other locations,
> +  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
> +  1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this
> +     same driver's Start() function.
> +  2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid
> +     EFI_HANDLE. In addition, all of these handles must have been created in this driver's
> +     Start() function, and the Start() function must have called OpenProtocol() on
> +     ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
> +
> +  @param[in]  This              A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
> +  @param[in]  ControllerHandle  A handle to the device being stopped. The handle must
> +                                support a bus specific I/O protocol for the driver
> +                                to use to stop the device.
> +  @param[in]  NumberOfChildren  The number of child device handles in ChildHandleBuffer.
> +  @param[in]  ChildHandleBuffer An array of child handles to be freed. May be NULL
> +                                if NumberOfChildren is 0.
> +
> +  @retval EFI_SUCCESS           The device was stopped.
> +  @retval EFI_DEVICE_ERROR      The device could not be stopped due to a device error.
> +
> +**/
> +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 =
> +{
> +  .Supported           = Ext4IsBindingSupported,
> +  .Start               = Ext4Bind,
> +  .Stop                = Ext4Stop,
> +  .Version             = EXT4_DRIVER_VERSION,
> +  .ImageHandle         = NULL,
> +  .DriverBindingHandle = NULL
> +};
> +
> +/**
> +  Retrieves a Unicode string that is the user readable name of the controller
> +  that is being managed by an EFI Driver.
> +
> +  @param[in]  This             A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
> +  @param[in]  ControllerHandle The handle of a controller that the driver specified by
> +                               This is managing.  This handle specifies the controller
> +                               whose name is to be returned.
> +  @param[in]  ChildHandle      The handle of the child controller to retrieve the name
> +                               of.  This is an optional parameter that may be NULL.  It
> +                               will be NULL for device drivers.  It will also be NULL
> +                               for a bus drivers that wish to retrieve the name of the
> +                               bus controller.  It will not be NULL for a bus driver
> +                               that wishes to retrieve the name of a child controller.
> +  @param[in]  Language         A pointer to a three character ISO 639-2 language
> +                               identifier.  This is the language of the controller name
> +                               that the caller is requesting, and it must match one
> +                               of the languages specified in SupportedLanguages.  The
> +                               number of languages supported by a driver is up to the
> +                               driver writer.
> +  @param[out]  ControllerName   A pointer to the Unicode string to return.  This Unicode
> +                                string is the name of the controller specified by
> +                                ControllerHandle and ChildHandle in the language specified
> +                                by Language, from the point of view of the driver specified
> +                                by This.
> +
> +  @retval EFI_SUCCESS           The Unicode string for the user-readable name in the
> +                                language specified by Language for the driver
> +                                specified by This was returned in DriverName.
> +  @retval EFI_INVALID_PARAMETER ControllerHandle is NULL.
> +  @retval EFI_INVALID_PARAMETER ChildHandle is not NULL and it is not a valid EFI_HANDLE.
> +  @retval EFI_INVALID_PARAMETER Language is NULL.
> +  @retval EFI_INVALID_PARAMETER ControllerName is NULL.
> +  @retval EFI_UNSUPPORTED       The driver specified by This is not currently managing
> +                                the controller specified by ControllerHandle and
> +                                ChildHandle.
> +  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
> +                                language specified by Language.
> +
> +**/
> +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
> +  )
> +{
> +  EFI_STATUS  Status;
> +
> +  if (ChildHandle != NULL) {
> +    return EFI_UNSUPPORTED;
> +  }
> +
> +  // Test if the driver manages ControllHandle
> +  Status = EfiTestManagedDevice (
> +             ControllerHandle,
> +             gExt4BindingProtocol.DriverBindingHandle,
> +             &gEfiDiskIoProtocolGuid
> +             );
> +
> +  if (EFI_ERROR (Status)) {
> +    return Status;
> +  }
> +
> +  return LookupUnicodeString2 (
> +           Language,
> +           This->SupportedLanguages,
> +           mExt4ControllerNameTable,
> +           ControllerName,
> +           (BOOLEAN)(This == &gExt4ComponentName)
> +           );
> +}
> +
> +/**
> +  Retrieves a Unicode string that is the user-readable name of the EFI Driver.
> +
> +  @param[in]  This        A pointer to the EFI_COMPONENT_NAME_PROTOCOL instance.
> +  @param[in]  Language    A pointer to a three-character ISO 639-2 language identifier.
> +                          This is the language of the driver name that that the caller
> +                          is requesting, and it must match one of the languages specified
> +                          in SupportedLanguages.  The number of languages supported by a
> +                          driver is up to the driver writer.
> +  @param[out]  DriverName A pointer to the Unicode string to return.  This Unicode string
> +                          is the name of the driver specified by This in the language
> +                          specified by Language.
> +
> +  @retval EFI_SUCCESS           The Unicode string for the Driver specified by This
> +                                and the language specified by Language was returned
> +                                in DriverName.
> +  @retval EFI_INVALID_PARAMETER Language is NULL.
> +  @retval EFI_INVALID_PARAMETER DriverName is NULL.
> +  @retval EFI_UNSUPPORTED       The driver specified by This does not support the
> +                                language specified by Language.
> +
> +**/
> +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)
> +           );
> +}
> +
> +/**
> +  Stops a device controller or a bus controller.
> +
> +  The Stop() function is designed to be invoked from the EFI boot service DisconnectController().
> +  As a result, much of the error checking on the parameters to Stop() has been moved
> +  into this common boot service. It is legal to call Stop() from other locations,
> +  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
> +  1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this
> +     same driver's Start() function.
> +  2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid
> +     EFI_HANDLE. In addition, all of these handles must have been created in this driver's
> +     Start() function, and the Start() function must have called OpenProtocol() on
> +     ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER.
> +
> +  @param[in]  This              A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
> +  @param[in]  ControllerHandle  A handle to the device being stopped. The handle must
> +                                support a bus specific I/O protocol for the driver
> +                                to use to stop the device.
> +  @param[in]  NumberOfChildren  The number of child device handles in ChildHandleBuffer.
> +  @param[in]  ChildHandleBuffer An array of child handles to be freed. May be NULL
> +                                if NumberOfChildren is 0.
> +
> +  @retval EFI_SUCCESS           The device was stopped.
> +  @retval EFI_DEVICE_ERROR      The device could not be stopped due to a device error.
> +
> +**/
> +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 = EXT4_DISK_IO2 (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;
> +}
> +
> +/**
> +   Ext4Dxe Driver's entry point.
> +
> +   Called at load time.
> +
> +   @param[in]    ImageHandle   Handle to the image.
> +   @param[in]    SystemTable   Pointer to the EFI_SYSTEM_TABLE.
> +   @return Result of the load.
> +**/
> +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);
> +}
> +
> +/**
> +   Ext4Dxe Driver's unload callback.
> +
> +   Called at unload time.
> +
> +   @param[in]    ImageHandle   Handle to the image.
> +   @return Result of the unload.
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Unload (
> +  IN EFI_HANDLE  ImageHandle
> +  )
> +{
> +  EFI_STATUS  Status;
> +  EFI_HANDLE  *DeviceHandleBuffer;
> +  UINTN       DeviceHandleCount;
> +  UINTN       Index;
> +  EFI_HANDLE  Handle;
> +
> +  Status = gBS->LocateHandleBuffer (
> +                  AllHandles,
> +                  NULL,
> +                  NULL,
> +                  &DeviceHandleCount,
> +                  &DeviceHandleBuffer
> +                  );
> +  if (EFI_ERROR (Status)) {
> +    return Status;
> +  }
> +
> +  for (Index = 0; Index < DeviceHandleCount; Index++) {
> +    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;
> +}
> +
> +/**
> +  Tests to see if this driver supports a given controller. If a child device is provided,
> +  it further tests to see if this driver supports creating a handle for the specified child device.
> +
> +  This function checks to see if the driver specified by This supports the device specified by
> +  ControllerHandle. Drivers will typically use the device path attached to
> +  ControllerHandle and/or the services from the bus I/O abstraction attached to
> +  ControllerHandle to determine if the driver supports ControllerHandle. This function
> +  may be called many times during platform initialization. In order to reduce boot times, the tests
> +  performed by this function must be very small, and take as little time as possible to execute. This
> +  function must not change the state of any hardware devices, and this function must be aware that the
> +  device specified by ControllerHandle may already be managed by the same driver or a
> +  different driver. This function must match its calls to AllocatePages() with FreePages(),
> +  AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol().
> +  Because ControllerHandle may have been previously started by the same driver, if a protocol is
> +  already in the opened state, then it must not be closed with CloseProtocol(). This is required
> +  to guarantee the state of ControllerHandle is not modified by this function.
> +
> +  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
> +  @param[in]  ControllerHandle     The handle of the controller to test. This handle
> +                                   must support a protocol interface that supplies
> +                                   an I/O abstraction to the driver.
> +  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
> +                                   parameter is ignored by device drivers, and is optional for bus
> +                                   drivers. For bus drivers, if this parameter is not NULL, then
> +                                   the bus driver must determine if the bus controller specified
> +                                   by ControllerHandle and the child controller specified
> +                                   by RemainingDevicePath are both supported by this
> +                                   bus driver.
> +
> +  @retval EFI_SUCCESS              The device specified by ControllerHandle and
> +                                   RemainingDevicePath is supported by the driver specified by This.
> +  @retval EFI_ALREADY_STARTED      The device specified by ControllerHandle and
> +                                   RemainingDevicePath is already being managed by the driver
> +                                   specified by This.
> +  @retval EFI_ACCESS_DENIED        The device specified by ControllerHandle and
> +                                   RemainingDevicePath is already being managed by a different
> +                                   driver or an application that requires exclusive access.
> +  @retval EFI_UNSUPPORTED          The device specified by ControllerHandle and
> +                                   RemainingDevicePath is not supported by the driver specified by This.
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4IsBindingSupported (
> +  IN EFI_DRIVER_BINDING_PROTOCOL  *BindingProtocol,
> +  IN EFI_HANDLE                   ControllerHandle,
> +  IN EFI_DEVICE_PATH              *RemainingDevicePath  OPTIONAL
> +  )
> +{
> +  EFI_STATUS             Status;
> +  EFI_DISK_IO_PROTOCOL   *DiskIo;
> +  EFI_BLOCK_IO_PROTOCOL  *BlockIo;
> +
> +  DiskIo  = NULL;
> +  BlockIo = NULL;
> +
> +  //
> +  // Open the IO Abstraction(s) needed to perform the supported test
> +  //
> +  Status = gBS->OpenProtocol (
> +                  ControllerHandle,
> +                  &gEfiDiskIoProtocolGuid,
> +                  (VOID **)&DiskIo,
> +                  BindingProtocol->DriverBindingHandle,
> +                  ControllerHandle,
> +                  EFI_OPEN_PROTOCOL_BY_DRIVER
> +                  );
> +
> +  if (EFI_ERROR (Status)) {
> +    return Status;
> +  }
> +
> +  //
> +  // Open the IO Abstraction(s) needed to perform the supported test
> +  //
> +  Status = gBS->OpenProtocol (
> +                  ControllerHandle,
> +                  &gEfiBlockIoProtocolGuid,
> +                  (VOID **)&BlockIo,
> +                  BindingProtocol->DriverBindingHandle,
> +                  ControllerHandle,
> +                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
> +                  );
> +
> +  if (!EFI_ERROR (Status)) {
> +    if (!Ext4SuperblockCheckMagic (DiskIo, BlockIo)) {
> +      Status = EFI_UNSUPPORTED;
> +    }
> +  }
> +
> +  //
> +  // Close the I/O Abstraction(s) used to perform the supported test
> +  //
> +  if (DiskIo != NULL) {
> +    gBS->CloseProtocol (
> +           ControllerHandle,
> +           &gEfiDiskIoProtocolGuid,
> +           BindingProtocol->DriverBindingHandle,
> +           ControllerHandle
> +           );
> +  }
> +
> +  if (BlockIo != NULL) {
> +    gBS->CloseProtocol (
> +           ControllerHandle,
> +           &gEfiBlockIoProtocolGuid,
> +           BindingProtocol->DriverBindingHandle,
> +           ControllerHandle
> +           );
> +  }
> +
> +  return Status;
> +}
> +
> +/**
> +  Starts a device controller or a bus controller.
> +
> +  The Start() function is designed to be invoked from the EFI boot service ConnectController().
> +  As a result, much of the error checking on the parameters to Start() has been moved into this
> +  common boot service. It is legal to call Start() from other locations,
> +  but the following calling restrictions must be followed, or the system behavior will not be deterministic.
> +  1. ControllerHandle must be a valid EFI_HANDLE.
> +  2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned
> +     EFI_DEVICE_PATH_PROTOCOL.
> +  3. Prior to calling Start(), the Supported() function for the driver specified by This must
> +     have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS.
> +
> +  @param[in]  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
> +  @param[in]  ControllerHandle     The handle of the controller to start. This handle
> +                                   must support a protocol interface that supplies
> +                                   an I/O abstraction to the driver.
> +  @param[in]  RemainingDevicePath  A pointer to the remaining portion of a device path.  This
> +                                   parameter is ignored by device drivers, and is optional for bus
> +                                   drivers. For a bus driver, if this parameter is NULL, then handles
> +                                   for all the children of Controller are created by this driver.
> +                                   If this parameter is not NULL and the first Device Path Node is
> +                                   not the End of Device Path Node, then only the handle for the
> +                                   child device specified by the first Device Path Node of
> +                                   RemainingDevicePath is created by this driver.
> +                                   If the first Device Path Node of RemainingDevicePath is
> +                                   the End of Device Path Node, no child handle is created by this
> +                                   driver.
> +
> +  @retval EFI_SUCCESS              The device was started.
> +  @retval EFI_DEVICE_ERROR         The device could not be started due to a device error.Currently not implemented.
> +  @retval EFI_OUT_OF_RESOURCES     The request could not be completed due to a lack of resources.
> +  @retval Others                   The driver failded to start the device.
> +
> +**/
> +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;
> +  BlockIo = NULL;
> +  DiskIo  = NULL;
> +
> +  Status = gBS->OpenProtocol (
> +                  ControllerHandle,
> +                  &gEfiDiskIoProtocolGuid,
> +                  (VOID **)&DiskIo,
> +                  BindingProtocol->ImageHandle,
> +                  ControllerHandle,
> +                  EFI_OPEN_PROTOCOL_BY_DRIVER
> +                  );
> +
> +  if (EFI_ERROR (Status)) {
> +    goto Error;
> +  }
> +
> +  DEBUG ((DEBUG_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 ((DEBUG_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;
> +  }
> +
> +  Status = Ext4OpenPartition (ControllerHandle, DiskIo, DiskIo2, BlockIo);
> +
> +  if (EFI_ERROR (Status)) {
> +    DEBUG ((DEBUG_ERROR, "[ext4] Error mounting: %r\n", Status));
> +    // Falls through to Error
> +  } else {
> +    return 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/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> new file mode 100644
> index 000000000000..adf3c13f6ea9
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.h
> @@ -0,0 +1,1232 @@
> +/** @file
> +  Common header for the driver
> +
> +  Copyright (c) 2021 - 2022 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/BaseLib.h>
> +#include <Library/BaseMemoryLib.h>
> +#include <Library/DebugLib.h>
> +#include <Library/MemoryAllocationLib.h>
> +#include <Library/OrderedCollectionLib.h>
> +#include <Library/PcdLib.h>
> +#include <Library/UefiBootServicesTableLib.h>
> +#include <Library/UefiDriverEntryPoint.h>
> +#include <Library/UefiLib.h>
> +#include <Library/UefiRuntimeServicesTableLib.h>
> +
> +#include "Ext4Disk.h"
> +
> +#define SYMLOOP_MAX    8
> +#define EXT4_NAME_MAX  255
> +//
> +// We need to specify path length limit for security purposes, to prevent possible
> +// overflows and dead-loop conditions. Originally this limit is absent in FS design,
> +// but present in UNIX distros and shell environments, which may varies from 1024 to 4096.
> +//
> +#define EXT4_EFI_PATH_MAX    4096
> +#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_Dentry  EXT4_DENTRY;
> +
> +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_DENTRY                        *RootDentry;
> +} EXT4_PARTITION;
> +
> +/**
> +   This structure represents a directory entry inside our directory entry tree.
> +   For now, it will be used as a way to track file names inside our opening
> +   code, but it may very well be used as a directory cache in the future.
> +   Because it's not being used as a directory cache right now,
> +   an EXT4_DENTRY structure is not necessarily unique name-wise in the list of
> +   children. Therefore, the dentry tree does not accurately reflect the
> +   filesystem structure.
> + */
> +struct _Ext4_Dentry {
> +  UINTN                  RefCount;
> +  CHAR16                 Name[EXT4_NAME_MAX + 1];
> +  EXT4_INO_NR            Inode;
> +  struct _Ext4_Dentry    *Parent;
> +  LIST_ENTRY             Children;
> +  LIST_ENTRY             ListNode;
> +};
> +
> +#define EXT4_DENTRY_FROM_DENTRY_LIST(Node)  BASE_CR(Node, EXT4_DENTRY, ListNode)
> +
> +/**
> +   Creates a new dentry object.
> +
> +   @param[in]              Name        Name of the dentry.
> +   @param[in out opt]      Parent      Parent dentry, if it's not NULL.
> +
> +   @return The new allocated and initialised dentry.
> +           The ref count will be set to 1.
> +**/
> +EXT4_DENTRY *
> +Ext4CreateDentry (
> +  IN CONST CHAR16     *Name,
> +  IN OUT EXT4_DENTRY  *Parent OPTIONAL
> +  );
> +
> +/**
> +   Increments the ref count of the dentry.
> +
> +   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
> +**/
> +VOID
> +Ext4RefDentry (
> +  IN OUT EXT4_DENTRY  *Dentry
> +  );
> +
> +/**
> +   Decrements the ref count of the dentry.
> +   If the ref count is 0, it's destroyed.
> +
> +   @param[in out]            Dentry    Pointer to a valid EXT4_DENTRY.
> +
> +   @retval True if it was destroyed, false if it's alive.
> +**/
> +BOOLEAN
> +Ext4UnrefDentry (
> +  IN OUT EXT4_DENTRY  *Dentry
> +  );
> +
> +/**
> +   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.
> +**/
> +#define EXT4_BLOCK_IO(Partition)  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.
> +**/
> +#define EXT4_DISK_IO(Partition)  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.
> +**/
> +#define EXT4_DISK_IO2(Partition)  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.
> +**/
> +#define EXT4_MEDIA_ID(Partition)  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.
> +**/
> +#define EXT4_IS_64_BIT(Partition)                                              \
> +  ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_64BIT) != 0)
> +
> +/**
> +   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.
> +**/
> +#define EXT4_BLOCK_NR_FROM_HALFS(Partition, Low, High)                         \
> +  EXT4_IS_64_BIT(Partition) ? (Low | LShiftU64(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.
> +**/
> +EXT4_BLOCK_GROUP_DESC *
> +Ext4GetBlockGroupDesc (
> +  IN EXT4_PARTITION  *Partition,
> +  IN UINT32          BlockGroup
> +  );
> +
> +/**
> +   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.
> +**/
> +#define EXT4_BLOCK_TO_BYTES(Partition, Block)                                  \
> +  MultU64x32(Block, Partition->BlockSize)
> +
> +/**
> +   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.
> +**/
> +#define EXT4_INODE_SIZE(Inode)                                                 \
> +  (LShiftU64(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;
> +  UINT32                SymLoops;
> +
> +  EXT4_PARTITION        *Partition;
> +
> +  ORDERED_COLLECTION    *ExtentsMap;
> +
> +  LIST_ENTRY            OpenFilesListNode;
> +
> +  // Owning reference to this file's directory entry.
> +  EXT4_DENTRY           *Dentry;
> +};
> +
> +#define EXT4_FILE_FROM_THIS(This)  BASE_CR ((This), EXT4_FILE, Protocol)
> +
> +#define EXT4_FILE_FROM_OPEN_FILES_NODE(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.
> +   @param[in]      Directory   Pointer to the opened directory.
> +
> +   @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,
> +  IN EXT4_FILE       *Directory
> +  );
> +
> +/**
> +   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
> +
> +/**
> +  Open the root directory on a volume.
> +
> +  @param[in]   This A pointer to the volume to open the root directory.
> +  @param[out]  Root A pointer to the location to return the opened file handle
> +for the root directory.
> +
> +  @retval EFI_SUCCESS          The device was opened.
> +  @retval EFI_UNSUPPORTED      This volume does not support the requested file
> +system type.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_ACCESS_DENIED    The service denied access to the file.
> +  @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of
> +resources.
> +  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the
> +medium is no longer supported. Any existing file handles for this volume are no
> +longer valid. To access the files on the new medium, the volume must be reopened
> +with OpenVolume().
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4OpenVolume (
> +  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
> +  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
> +  );
> +
> +/**
> +  Opens a new file relative to the source file's location.
> +
> +  @param[out] FoundFile  A pointer to the location to return the opened handle for the new
> +                         file.
> +  @param[in]  Source     A pointer to the EXT4_FILE instance that is the file
> +                         handle to the source location. This would typically be an open
> +                         handle to a directory.
> +  @param[in]  FileName   The Null-terminated string of the name of the file to be opened.
> +                         The file name may contain the following path modifiers: "\", ".",
> +                         and "..".
> +  @param[in]  OpenMode   The mode to open the file. The only valid combinations that the
> +                         file may be opened with are: Read, Read/Write, or Create/Read/Write.
> +  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case these are the
> +                         attribute bits for the newly created file.
> +
> +  @retval EFI_SUCCESS          The file was opened.
> +  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
> +                               longer supported.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a file for write
> +                               when the media is write-protected.
> +  @retval EFI_ACCESS_DENIED    The service denied access to the file.
> +  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +
> +**/
> +EFI_STATUS
> +Ext4OpenInternal (
> +  OUT EXT4_FILE  **FoundFile,
> +  IN  EXT4_FILE  *Source,
> +  IN  CHAR16     *FileName,
> +  IN  UINT64     OpenMode,
> +  IN  UINT64     Attributes
> +  );
> +
> +/**
> +   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
> +
> +/**
> +  Opens a new file relative to the source file's location.
> +
> +  @param[in]  This       A pointer to the EFI_FILE_PROTOCOL instance that is the
> +file handle to the source location. This would typically be an open handle to a
> +directory.
> +  @param[out] NewHandle  A pointer to the location to return the opened handle
> +for the new file.
> +  @param[in]  FileName   The Null-terminated string of the name of the file to
> +be opened. The file name may contain the following path modifiers: "\", ".", and
> +"..".
> +  @param[in]  OpenMode   The mode to open the file. The only valid combinations
> +that the file may be opened with are: Read, Read/Write, or Create/Read/Write.
> +  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case
> +these are the attribute bits for the newly created file.
> +
> +  @retval EFI_SUCCESS          The file was opened.
> +  @retval EFI_NOT_FOUND        The specified file could not be found on the
> +device.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the
> +medium is no longer supported.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a
> +file for write when the media is write-protected.
> +  @retval EFI_ACCESS_DENIED    The service denied access to the file.
> +  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the
> +file.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Open (
> +  IN EFI_FILE_PROTOCOL   *This,
> +  OUT EFI_FILE_PROTOCOL  **NewHandle,
> +  IN CHAR16              *FileName,
> +  IN UINT64              OpenMode,
> +  IN UINT64              Attributes
> +  );
> +
> +/**
> +  Closes a specified file handle.
> +
> +  @param[in]  This          A pointer to the EFI_FILE_PROTOCOL instance that is
> +the file handle to close.
> +
> +  @retval EFI_SUCCESS   The file was closed.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Close (
> +  IN EFI_FILE_PROTOCOL  *This
> +  );
> +
> +/**
> +  Close and delete the file handle.
> +
> +  @param[in]  This                     A pointer to the EFI_FILE_PROTOCOL
> +instance that is the handle to the file to delete.
> +
> +  @retval EFI_SUCCESS              The file was closed and deleted, and the
> +handle was closed.
> +  @retval EFI_WARN_DELETE_FAILURE  The handle was closed, but the file was not
> +deleted.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Delete (
> +  IN EFI_FILE_PROTOCOL  *This
> +  );
> +
> +/**
> +  Reads data from a file.
> +
> +  @param[in]      This             A pointer to the EFI_FILE_PROTOCOL instance
> +that is the file handle to read data from.
> +  @param[in out]  BufferSize       On input, the size of the Buffer. On output,
> +the amount of data returned in Buffer. In both cases, the size is measured in
> +bytes.
> +  @param[out]     Buffer           The buffer into which the data is read.
> +
> +  @retval EFI_SUCCESS          Data was read.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_DEVICE_ERROR     An attempt was made to read from a deleted file.
> +  @retval EFI_DEVICE_ERROR     On entry, the current file position is beyond the
> +end of the file.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current
> +directory entry. BufferSize has been updated with the size needed to complete
> +the request.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4ReadFile (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN OUT UINTN          *BufferSize,
> +  OUT VOID              *Buffer
> +  );
> +
> +/**
> +  Writes data to a file.
> +
> +  @param[in]      This        A pointer to the EFI_FILE_PROTOCOL instance that
> +is the file handle to write data to.
> +  @param[in out]  BufferSize  On input, the size of the Buffer. On output, the
> +amount of data actually written. In both cases, the size is measured in bytes.
> +  @param[in]      Buffer      The buffer of data to write.
> +
> +  @retval EFI_SUCCESS          Data was written.
> +  @retval EFI_UNSUPPORTED      Writes to open directory files are not supported.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_DEVICE_ERROR     An attempt was made to write to a deleted file.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  The file or medium is write-protected.
> +  @retval EFI_ACCESS_DENIED    The file was opened read only.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4WriteFile (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN OUT UINTN          *BufferSize,
> +  IN VOID               *Buffer
> +  );
> +
> +/**
> +  Returns a file's current position.
> +
> +  @param[in]   This            A pointer to the EFI_FILE_PROTOCOL instance that
> +is the file handle to get the current position on.
> +  @param[out]  Position        The address to return the file's current position
> +value.
> +
> +  @retval EFI_SUCCESS      The position was returned.
> +  @retval EFI_UNSUPPORTED  The request is not valid on open directories.
> +  @retval EFI_DEVICE_ERROR An attempt was made to get the position from a
> +deleted file.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4GetPosition (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  OUT UINT64            *Position
> +  );
> +
> +/**
> +  Sets a file's current position.
> +
> +  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that
> +is the file handle to set the requested position on.
> +  @param[in] Position        The byte position from the start of the file to
> +set.
> +
> +  @retval EFI_SUCCESS      The position was set.
> +  @retval EFI_UNSUPPORTED  The seek request for nonzero is not valid on open
> +                           directories.
> +  @retval EFI_DEVICE_ERROR An attempt was made to set the position of a deleted
> +file.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4SetPosition (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN UINT64             Position
> +  );
> +
> +/**
> +  Returns information about a file.
> +
> +  @param[in]      This            A pointer to the EFI_FILE_PROTOCOL instance
> +that is the file handle the requested information is for.
> +  @param[in]      InformationType The type identifier for the information being
> +requested.
> +  @param[in out]  BufferSize      On input, the size of Buffer. On output, the
> +amount of data returned in Buffer. In both cases, the size is measured in bytes.
> +  @param[out]     Buffer          A pointer to the data buffer to return. The
> +buffer's type is indicated by InformationType.
> +
> +  @retval EFI_SUCCESS          The information was returned.
> +  @retval EFI_UNSUPPORTED      The InformationType is not known.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current
> +directory entry. BufferSize has been updated with the size needed to complete
> +                               the request.
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4GetInfo (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN EFI_GUID           *InformationType,
> +  IN OUT UINTN          *BufferSize,
> +  OUT VOID              *Buffer
> +  );
> +
> +/**
> +  Sets information about a file.
> +
> +  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that
> +is the file handle the information is for.
> +  @param[in]  InformationType The type identifier for the information being set.
> +  @param[in]  BufferSize      The size, in bytes, of Buffer.
> +  @param[in]  Buffer          A pointer to the data buffer to write. The
> +buffer's type is indicated by InformationType.
> +
> +  @retval EFI_SUCCESS          The information was set.
> +  @retval EFI_UNSUPPORTED      The InformationType is not known.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_INFO_ID and the media
> +is read-only.
> +  @retval EFI_WRITE_PROTECTED  InformationType is
> +EFI_FILE_PROTOCOL_SYSTEM_INFO_ID and the media is read only.
> +  @retval EFI_WRITE_PROTECTED  InformationType is
> +EFI_FILE_SYSTEM_VOLUME_LABEL_ID and the media is read-only.
> +  @retval EFI_ACCESS_DENIED    An attempt is made to change the name of a file
> +to a file that is already present.
> +  @retval EFI_ACCESS_DENIED    An attempt is being made to change the
> +EFI_FILE_DIRECTORY Attribute.
> +  @retval EFI_ACCESS_DENIED    An attempt is being made to change the size of a
> +directory.
> +  @retval EFI_ACCESS_DENIED    InformationType is EFI_FILE_INFO_ID and the file
> +was opened read-only and an attempt is being made to modify a field other than
> +Attribute.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +  @retval EFI_BAD_BUFFER_SIZE  BufferSize is smaller than the size of the type
> +indicated by InformationType.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4SetInfo (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN EFI_GUID           *InformationType,
> +  IN UINTN              BufferSize,
> +  IN 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 symlink.
> +
> +   @param[in]      File          Pointer to the opened file.
> +
> +   @return BOOLEAN         Whether file is a symlink
> +**/
> +BOOLEAN
> +Ext4FileIsSymlink (
> +  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.
> +**/
> +#define Ext4FileIsOpenable(File)  (Ext4FileIsReg (File) || Ext4FileIsDir (File) || Ext4FileIsSymlink (File))
> +
> +#define EXT4_INODE_HAS_FIELD(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 (
> +  IN 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 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.
> +**/
> +#define EXT4_HAS_RO_COMPAT(Partition, RoCompatFeatureSet)                      \
> +  ((Partition->FeaturesRoCompat & RoCompatFeatureSet) == RoCompatFeatureSet)
> +
> +/**
> +   Verifies the existance of a particular compat feature set.
> +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> +   @param[in]      CompatFeatureSet  Feature set to test.
> +
> +   @return TRUE if all features are supported, else FALSE.
> +**/
> +#define EXT4_HAS_COMPAT(Partition, CompatFeatureSet)                           \
> +  ((Partition->FeaturesCompat & CompatFeatureSet) == CompatFeatureSet)
> +
> +/**
> +   Verifies the existance of a particular compat feature set.
> +   @param[in]      Partition           Pointer to the opened EXT4 partition.
> +   @param[in]      IncompatFeatureSet  Feature set to test.
> +
> +   @return TRUE if all features are supported, else FALSE.
> +**/
> +#define EXT4_HAS_INCOMPAT(Partition, IncompatFeatureSet)                       \
> +  ((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.
> +
> +   @return TRUE if the metadata_csum is supported, else FALSE.
> +**/
> +#define EXT4_HAS_METADATA_CSUM(Partition)                                      \
> +  EXT4_HAS_RO_COMPAT(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.
> +
> +   @return TRUE if the gdt_csum is supported, else FALSE.
> +**/
> +#define EXT4_HAS_GDT_CSUM(Partition)                                           \
> +  EXT4_HAS_RO_COMPAT(Partition, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)
> +
> +/**
> +   Retrieves the volume name.
> +
> +   @param[in]      Part           Pointer to the opened partition.
> +   @param[out]     Info           Pointer to a CHAR16*.
> +   @param[out]     BufferSize     Pointer to a UINTN, where the string length
> +                                  of the name will be put.
> +
> +   @return Status of the volume name request.
> +**/
> +EFI_STATUS
> +Ext4GetVolumeName (
> +  IN EXT4_PARTITION  *Partition,
> +  OUT CHAR16         **OutVolName,
> +  OUT UINTN          *VolNameLen
> +  );
> +
> +/**
> +   Checks the superblock's magic value.
> +
> +   @param[in] DiskIo      Pointer to the DiskIo.
> +   @param[in] BlockIo     Pointer to the BlockIo.
> +
> +   @returns Whether the partition has a valid EXT4 superblock magic value.
> +**/
> +BOOLEAN
> +Ext4SuperblockCheckMagic (
> +  IN EFI_DISK_IO_PROTOCOL   *DiskIo,
> +  IN EFI_BLOCK_IO_PROTOCOL  *BlockIo
> +  );
> +
> +/**
> +   Check if the extent is uninitialized
> +
> +   @param[in] Extent    Pointer to the EXT4_EXTENT
> +
> +   @returns True if uninitialized, else false.
> +**/
> +#define EXT4_EXTENT_IS_UNINITIALIZED(Extent)                                   \
> +  ((Extent)->ee_len > EXT4_EXTENT_MAX_INITIALIZED)
> +
> +/**
> +   Retrieves the extent's length, dealing with uninitialized extents in the
> +process.
> +
> +   @param[in] Extent      Pointer to the EXT4_EXTENT
> +
> +   @returns Extent's length, in filesystem blocks.
> +**/
> +EXT4_BLOCK_NR
> +Ext4GetExtentLength (
> +  IN CONST EXT4_EXTENT  *Extent
> +  );
> +
> +/**
> +   Retrieves an extent from an EXT2/3 inode (with a blockmap).
> +   @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
> +Ext4GetBlocks (
> +  IN  EXT4_PARTITION  *Partition,
> +  IN  EXT4_FILE       *File,
> +  IN  EXT2_BLOCK_NR   LogicalBlock,
> +  OUT EXT4_EXTENT     *Extent
> +  );
> +
> +#endif
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> new file mode 100644
> index 000000000000..a153fc41ccd6
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> @@ -0,0 +1,149 @@
> +## @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                      = Ext4Dxe
> +  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
> +  Symlink.c
> +  Collation.c
> +  Ext4Disk.h
> +  Ext4Dxe.h
> +  BlockMap.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/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni b/Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.uni
> new file mode 100644
> index 000000000000..7476fbf9bd70
> --- /dev/null
> +++ b/Filesystems/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/Filesystems/Ext4Pkg/Ext4Dxe/Extents.c b/Filesystems/Ext4Pkg/Ext4Dxe/Extents.c
> new file mode 100644
> index 000000000000..f1964426d276
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Extents.c
> @@ -0,0 +1,624 @@
> +/** @file
> +  Extent related routines
> +
> +  Copyright (c) 2021 - 2022 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 ((DEBUG_ERROR, "[ext4] Invalid extent header depth %u\n", Header->eh_depth));
> +    return FALSE;
> +  }
> +
> +  if (Header->eh_magic != EXT4_EXTENT_HEADER_MAGIC) {
> +    DEBUG ((DEBUG_ERROR, "[ext4] Invalid extent header magic %x\n", Header->eh_magic));
> +    return FALSE;
> +  }
> +
> +  if (Header->eh_max < Header->eh_entries) {
> +    DEBUG ((
> +      DEBUG_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 LShiftU64 (Index->ei_leaf_hi, 32) | Index->ei_leaf_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
> +  )
> +{
> +  EXT4_INODE          *Inode;
> +  VOID                *Buffer;
> +  EXT4_EXTENT         *Ext;
> +  UINT32              CurrentDepth;
> +  EXT4_EXTENT_HEADER  *ExtHeader;
> +  EXT4_EXTENT_INDEX   *Index;
> +  EFI_STATUS          Status;
> +
> +  Inode  = File->Inode;
> +  Ext    = NULL;
> +  Buffer = NULL;
> +
> +  DEBUG ((DEBUG_FS, "[ext4] Looking up extent for block %lu\n", LogicalBlock));
> +
> +  // ext4 does not have support for logical block numbers bigger than UINT32_MAX
> +  if (LogicalBlock > (UINT32)-1) {
> +    return EFI_NO_MAPPING;
> +  }
> +
> +  // 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;
> +
> +    return EFI_SUCCESS;
> +  }
> +
> +  if ((Inode->i_flags & EXT4_EXTENTS_FL) == 0) {
> +    // If this is an older ext2/ext3 filesystem, emulate Ext4GetExtent using the block map
> +    // By specification files using block maps are limited to 2^32 blocks,
> +    // so we can safely cast LogicalBlock to uint32
> +    Status = Ext4GetBlocks (Partition, File, (UINT32)LogicalBlock, Extent);
> +
> +    if (!EFI_ERROR (Status)) {
> +      Ext4CacheExtents (File, Extent, 1);
> +    }
> +
> +    return Status;
> +  }
> +
> +  // 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) {
> +    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 ((DEBUG_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 + Ext4GetExtentLength (Ext) > 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;
> +  CONST EXT4_EXTENT  *Extent2;
> +
> +  Extent1 = UserStruct1;
> +  Extent2 = UserStruct2;
> +
> +  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;
> +  UINT32             Block;
> +
> +  // Note that logical blocks are 32-bits in size so no truncation can happen here
> +  // with regards to 32-bit architectures.
> +  Extent = UserStruct;
> +  Block  = (UINT32)(UINTN)StandaloneKey;
> +
> +  if ((Block >= Extent->ee_block) && (Block - Extent->ee_block < Ext4GetExtentLength (Extent))) {
> +    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;
> +  EXT4_EXTENT               *Ext;
> +
> +  MinEntry = NULL;
> +
> +  while ((MinEntry = OrderedCollectionMin (File->ExtentsMap)) != NULL) {
> +    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       Idx;
> +  EXT4_EXTENT  *Extent;
> +  EFI_STATUS   Status;
> +
> +  /* 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 (Idx = 0; Idx < NumberExtents; Idx++, Extents++) {
> +    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;
> +    }
> +  }
> +}
> +
> +/**
> +   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 (!EXT4_HAS_METADATA_CSUM (Partition)) {
> +    return TRUE;
> +  }
> +
> +  Tail = (EXT4_EXTENT_TAIL *)((CONST CHAR8 *)ExtHeader + (Partition->BlockSize - 4));
> +
> +  return Tail->eb_checksum == Ext4CalculateExtentChecksum (ExtHeader, File);
> +}
> +
> +/**
> +   Retrieves the extent's length, dealing with uninitialized extents in the process.
> +
> +   @param[in] Extent      Pointer to the EXT4_EXTENT
> +
> +   @returns Extent's length, in filesystem blocks.
> +**/
> +EXT4_BLOCK_NR
> +Ext4GetExtentLength (
> +  IN CONST EXT4_EXTENT  *Extent
> +  )
> +{
> +  // If it's an unintialized extent, the true length is ee_len - 2^15
> +  if (EXT4_EXTENT_IS_UNINITIALIZED (Extent)) {
> +    return Extent->ee_len - EXT4_EXTENT_MAX_INITIALIZED;
> +  }
> +
> +  return Extent->ee_len;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/File.c b/Filesystems/Ext4Pkg/Ext4Dxe/File.c
> new file mode 100644
> index 000000000000..04198a53bfc0
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/File.c
> @@ -0,0 +1,1001 @@
> +/** @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>
> +
> +/**
> +  Reads a symlink file.
> +
> +  @param[in]      Partition   Pointer to the ext4 partition.
> +  @param[in]      File        Pointer to the open symlink file.
> +  @param[out]     Symlink     Pointer to the output unicode symlink string.
> +
> +  @retval EFI_SUCCESS           Symlink was read.
> +  @retval EFI_ACCESS_DENIED     Symlink is encrypted.
> +  @retval EFI_OUT_OF_RESOURCES  Memory allocation error.
> +  @retval EFI_INVALID_PARAMETER Symlink path has incorrect length
> +  @retval EFI_VOLUME_CORRUPTED  Symlink read block size differ from inode value
> +**/
> +EFI_STATUS
> +Ext4ReadSymlink (
> +  IN     EXT4_PARTITION  *Partition,
> +  IN     EXT4_FILE       *File,
> +  OUT    CHAR16          **Symlink
> +  );
> +
> +/**
> +   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;
> +  CONST CHAR16  *End;
> +
> +  Start = Path;
> +  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;
> +
> +  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;
> +}
> +
> +/**
> +  Opens a new file relative to the source file's location.
> +
> +  @param[out] FoundFile  A pointer to the location to return the opened handle for the new
> +                         file.
> +  @param[in]  Source     A pointer to the EXT4_FILE instance that is the file
> +                         handle to the source location. This would typically be an open
> +                         handle to a directory.
> +  @param[in]  FileName   The Null-terminated string of the name of the file to be opened.
> +                         The file name may contain the following path modifiers: "\", ".",
> +                         and "..".
> +  @param[in]  OpenMode   The mode to open the file. The only valid combinations that the
> +                         file may be opened with are: Read, Read/Write, or Create/Read/Write.
> +  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case these are the
> +                         attribute bits for the newly created file.
> +
> +  @retval EFI_SUCCESS          The file was opened.
> +  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
> +                               longer supported.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a file for write
> +                               when the media is write-protected.
> +  @retval EFI_ACCESS_DENIED    The service denied access to the file.
> +  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +
> +**/
> +EFI_STATUS
> +Ext4OpenInternal (
> +  OUT EXT4_FILE  **FoundFile,
> +  IN  EXT4_FILE  *Source,
> +  IN  CHAR16     *FileName,
> +  IN  UINT64     OpenMode,
> +  IN  UINT64     Attributes
> +  )
> +{
> +  EXT4_FILE       *Current;
> +  EXT4_PARTITION  *Partition;
> +  UINTN           Level;
> +  CHAR16          PathSegment[EXT4_NAME_MAX + 1];
> +  UINTN           Length;
> +  EXT4_FILE       *File;
> +  CHAR16          *Symlink;
> +  EFI_STATUS      Status;
> +
> +  Current   = Source;
> +  Partition = Current->Partition;
> +  Level     = 0;
> +
> +  DEBUG ((DEBUG_FS, "[ext4] Ext4OpenInternal %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') {
> +    if (Partition->Root->SymLoops > SYMLOOP_MAX) {
> +      DEBUG ((DEBUG_FS, "[ext4] Symloop limit is hit !\n"));
> +      return EFI_ACCESS_DENIED;
> +    }
> +
> +    // 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;
> +
> +    if (StrCmp (PathSegment, L".") == 0) {
> +      // Opens of "." are a no-op
> +      continue;
> +    }
> +
> +    DEBUG ((DEBUG_FS, "[ext4] Opening %s\n", PathSegment));
> +
> +    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) {
> +      // We explicitly ignore the EFI_FILE_MODE_CREATE flag, since we don't have write support
> +      /// If/ we add write support, this should be changed.
> +      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;
> +    }
> +
> +    //
> +    // Reading symlink and then trying to follow it
> +    //
> +    if (Ext4FileIsSymlink (File)) {
> +      Partition->Root->SymLoops++;
> +      DEBUG ((DEBUG_FS, "[ext4] File %s is symlink, trying to read it\n", PathSegment));
> +      Status = Ext4ReadSymlink (Partition, File, &Symlink);
> +      if (EFI_ERROR (Status)) {
> +        DEBUG ((DEBUG_FS, "[ext4] Error reading %s symlink!\n", PathSegment));
> +        return Status;
> +      }
> +
> +      DEBUG ((DEBUG_FS, "[ext4] File %s is linked to %s\n", PathSegment, Symlink));
> +      //
> +      // Close symlink file
> +      //
> +      Ext4CloseInternal (File);
> +      //
> +      // Open linked file by recursive call of Ext4OpenFile
> +      //
> +      Status = Ext4OpenInternal (FoundFile, Current, Symlink, OpenMode, Attributes);
> +      FreePool (Symlink);
> +      if (EFI_ERROR (Status)) {
> +        DEBUG ((DEBUG_FS, "[ext4] Error opening linked file %s\n", Symlink));
> +        return Status;
> +      }
> +
> +      //
> +      // Set File to newly opened
> +      //
> +      File = *FoundFile;
> +    }
> +
> +    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;
> +  }
> +
> +  *FoundFile = Current;
> +
> +  DEBUG ((DEBUG_FS, "[ext4] Opened filename %s\n", Current->Dentry->Name));
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Opens a new file relative to the source file's location.
> +  @param[in]  This       A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                         handle to the source location. This would typically be an open
> +                         handle to a directory.
> +  @param[out] NewHandle  A pointer to the location to return the opened handle for the new
> +                         file.
> +  @param[in]  FileName   The Null-terminated string of the name of the file to be opened.
> +                         The file name may contain the following path modifiers: "\", ".",
> +                         and "..".
> +  @param[in]  OpenMode   The mode to open the file. The only valid combinations that the
> +                         file may be opened with are: Read, Read/Write, or Create/Read/Write.
> +  @param[in]  Attributes Only valid for EFI_FILE_MODE_CREATE, in which case these are the
> +                         attribute bits for the newly created file.
> +  @retval EFI_SUCCESS          The file was opened.
> +  @retval EFI_NOT_FOUND        The specified file could not be found on the device.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_MEDIA_CHANGED    The device has a different medium in it or the medium is no
> +                               longer supported.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  An attempt was made to create a file, or open a file for write
> +                               when the media is write-protected.
> +  @retval EFI_ACCESS_DENIED    The service denied access to the file.
> +  @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +**/
> +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  Status;
> +  EXT4_FILE   *FoundFile;
> +  EXT4_FILE   *Source;
> +
> +  Source = EXT4_FILE_FROM_THIS (This);
> +
> +  //
> +  // Reset SymLoops counter
> +  //
> +  Source->Partition->Root->SymLoops = 0;
> +
> +  Status = Ext4OpenInternal (
> +             &FoundFile,
> +             Source,
> +             FileName,
> +             OpenMode,
> +             Attributes
> +             );
> +
> +  if (!EFI_ERROR (Status)) {
> +    *NewHandle = &FoundFile->Protocol;
> +  }
> +
> +  return Status;
> +}
> +
> +/**
> +  Closes a specified file handle.
> +
> +  @param[in]  This          A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                            handle to close.
> +
> +  @retval EFI_SUCCESS   The file was closed.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Close (
> +  IN EFI_FILE_PROTOCOL  *This
> +  )
> +{
> +  return Ext4CloseInternal (EXT4_FILE_FROM_THIS (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 ((DEBUG_FS, "[ext4] Closed file %p (inode %lu)\n", File, File->InodeNum));
> +  RemoveEntryList (&File->OpenFilesListNode);
> +  FreePool (File->Inode);
> +  Ext4FreeExtentsMap (File);
> +  Ext4UnrefDentry (File->Dentry);
> +  FreePool (File);
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Close and delete the file handle.
> +
> +  @param[in]  This                     A pointer to the EFI_FILE_PROTOCOL instance that is the
> +                                       handle to the file to delete.
> +
> +  @retval EFI_SUCCESS              The file was closed and deleted, and the handle was closed.
> +  @retval EFI_WARN_DELETE_FAILURE  The handle was closed, but the file was not deleted.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4Delete (
> +  IN EFI_FILE_PROTOCOL  *This
> +  )
> +{
> +  // We do a regular close here since we don't have write support.
> +  Ext4Close (This);
> +  return EFI_WARN_DELETE_FAILURE;
> +}
> +
> +/**
> +  Reads data from a file.
> +
> +  @param[in]      This             A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                                   handle to read data from.
> +  @param[in out]  BufferSize       On input, the size of the Buffer. On output, the amount of data
> +                                   returned in Buffer. In both cases, the size is measured in bytes.
> +  @param[out]     Buffer           The buffer into which the data is read.
> +
> +  @retval EFI_SUCCESS          Data was read.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_DEVICE_ERROR     An attempt was made to read from a deleted file.
> +  @retval EFI_DEVICE_ERROR     On entry, the current file position is beyond the end of the file.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory
> +                               entry. BufferSize has been updated with the size
> +                               needed to complete the request.
> +
> +**/
> +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_FROM_THIS (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);
> +
> +    return Status;
> +  }
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Writes data to a file.
> +
> +  @param[in]      This        A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                              handle to write data to.
> +  @param[in out]  BufferSize  On input, the size of the Buffer. On output, the amount of data
> +                              actually written. In both cases, the size is measured in bytes.
> +  @param[in]      Buffer      The buffer of data to write.
> +
> +  @retval EFI_SUCCESS          Data was written.
> +  @retval EFI_UNSUPPORTED      Writes to open directory files are not supported.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_DEVICE_ERROR     An attempt was made to write to a deleted file.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  The file or medium is write-protected.
> +  @retval EFI_ACCESS_DENIED    The file was opened read only.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4WriteFile (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN OUT UINTN          *BufferSize,
> +  IN VOID               *Buffer
> +  )
> +{
> +  EXT4_FILE  *File;
> +
> +  File = EXT4_FILE_FROM_THIS (This);
> +
> +  if (!(File->OpenMode & EFI_FILE_MODE_WRITE)) {
> +    return EFI_ACCESS_DENIED;
> +  }
> +
> +  return EFI_WRITE_PROTECTED;
> +}
> +
> +/**
> +  Returns a file's current position.
> +
> +  @param[in]   This            A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                               handle to get the current position on.
> +  @param[out]  Position        The address to return the file's current position value.
> +
> +  @retval EFI_SUCCESS      The position was returned.
> +  @retval EFI_UNSUPPORTED  The request is not valid on open directories.
> +  @retval EFI_DEVICE_ERROR An attempt was made to get the position from a deleted file.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4GetPosition (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  OUT UINT64            *Position
> +  )
> +{
> +  EXT4_FILE  *File;
> +
> +  File = EXT4_FILE_FROM_THIS (This);
> +
> +  if (Ext4FileIsDir (File)) {
> +    return EFI_UNSUPPORTED;
> +  }
> +
> +  *Position = File->Position;
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Sets a file's current position.
> +
> +  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that is the
> +                              file handle to set the requested position on.
> +  @param[in] Position        The byte position from the start of the file to set.
> +
> +  @retval EFI_SUCCESS      The position was set.
> +  @retval EFI_UNSUPPORTED  The seek request for nonzero is not valid on open
> +                           directories.
> +  @retval EFI_DEVICE_ERROR An attempt was made to set the position of a deleted file.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4SetPosition (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN UINT64             Position
> +  )
> +{
> +  EXT4_FILE  *File;
> +
> +  File = EXT4_FILE_FROM_THIS (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
> +  )
> +{
> +  UINTN         FileNameLen;
> +  UINTN         FileNameSize;
> +  UINTN         NeededLength;
> +  CONST CHAR16  *FileName;
> +
> +  if (File->InodeNum == EXT4_ROOT_INODE_NR) {
> +    // Root inode gets a filename of "", regardless of how it was opened.
> +    FileName = L"";
> +  } else {
> +    FileName = File->Dentry->Name;
> +  }
> +
> +  FileNameLen  = StrLen (FileName);
> +  FileNameSize = StrSize (FileName);
> +
> +  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, FileName);
> +}
> +
> +/**
> +   Retrieves the volume name.
> +
> +   @param[in]      Part           Pointer to the opened partition.
> +   @param[out]     Info           Pointer to a CHAR16*.
> +   @param[out]     BufferSize     Pointer to a UINTN, where the string length
> +                                  of the name will be put.
> +
> +   @return Status of the volume name request.
> +**/
> +EFI_STATUS
> +Ext4GetVolumeName (
> +  IN EXT4_PARTITION  *Partition,
> +  OUT CHAR16         **OutVolName,
> +  OUT UINTN          *VolNameLen
> +  )
> +{
> +  CHAR8       TempVolName[16 + 1];
> +  CHAR16      *VolumeName;
> +  UINTN       VolNameLength;
> +  EFI_STATUS  Status;
> +
> +  VolNameLength = 0;
> +  VolumeName    = NULL;
> +
> +  // s_volume_name is only valid on dynamic revision; old filesystems don't support this
> +  if (Partition->SuperBlock.s_rev_level == EXT4_DYNAMIC_REV) {
> +    CopyMem (TempVolName, Partition->SuperBlock.s_volume_name, 16);
> +    TempVolName[16] = '\0';
> +
> +    Status = UTF8StrToUCS2 (TempVolName, &VolumeName);
> +
> +    if (EFI_ERROR (Status)) {
> +      return Status;
> +    }
> +
> +    VolNameLength = StrLen (VolumeName);
> +  } else {
> +    VolumeName    = AllocateZeroPool (sizeof (CHAR16));
> +    VolNameLength = 0;
> +  }
> +
> +  *OutVolName = VolumeName;
> +  *VolNameLen = VolNameLength;
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +   Retrieves information about the filesystem and stores it in the EFI_FILE_SYSTEM_INFO format.
> +
> +   @param[in]      Part           Pointer to the opened partition.
> +   @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
> +  EFI_STATUS     Status;
> +  UINTN          NeededLength;
> +  EXT4_BLOCK_NR  TotalBlocks;
> +  EXT4_BLOCK_NR  FreeBlocks;
> +  CHAR16         *VolumeName;
> +  UINTN          VolNameLength;
> +
> +  Status = Ext4GetVolumeName (Part, &VolumeName, &VolNameLength);
> +
> +  if (EFI_ERROR (Status)) {
> +    return Status;
> +  }
> +
> +  NeededLength = SIZE_OF_EFI_FILE_SYSTEM_INFO;
> +
> +  NeededLength += StrSize (VolumeName);
> +
> +  if (*BufferSize < NeededLength) {
> +    *BufferSize = NeededLength;
> +
> +    FreePool (VolumeName);
> +
> +    return EFI_BUFFER_TOO_SMALL;
> +  }
> +
> +  TotalBlocks = Part->NumberBlocks;
> +
> +  FreeBlocks = EXT4_BLOCK_NR_FROM_HALFS (
> +                 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 = MultU64x32 (TotalBlocks, Part->BlockSize);
> +  Info->FreeSpace  = MultU64x32 (FreeBlocks, Part->BlockSize);
> +
> +  StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
> +
> +  FreePool (VolumeName);
> +
> +  *BufferSize = NeededLength;
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +   Retrieves the volume label and stores it in the EFI_FILE_SYSTEM_VOLUME_LABEL format.
> +
> +   @param[in]      Part           Pointer to the opened partition.
> +   @param[out]     Info           Pointer to a EFI_FILE_SYSTEM_VOLUME_LABEL.
> +   @param[in out]  BufferSize     Pointer to the buffer size
> +
> +   @return Status of the file information request.
> +**/
> +STATIC
> +EFI_STATUS
> +Ext4GetVolumeLabelInfo (
> +  IN EXT4_PARTITION                 *Part,
> +  OUT EFI_FILE_SYSTEM_VOLUME_LABEL  *Info,
> +  IN OUT UINTN                      *BufferSize
> +  )
> +{
> +  // Length of s_volume_name + null terminator
> +  CHAR16      *VolumeName;
> +  UINTN       VolNameLength;
> +  EFI_STATUS  Status;
> +  UINTN       NeededLength;
> +
> +  Status = Ext4GetVolumeName (Part, &VolumeName, &VolNameLength);
> +
> +  if (EFI_ERROR (Status)) {
> +    return Status;
> +  }
> +
> +  NeededLength = (VolNameLength + 1) * sizeof (CHAR16);
> +
> +  if (NeededLength > *BufferSize) {
> +    *BufferSize = NeededLength;
> +
> +    FreePool (VolumeName);
> +
> +    return EFI_BUFFER_TOO_SMALL;
> +  }
> +
> +  Status = StrCpyS (Info->VolumeLabel, VolNameLength + 1, VolumeName);
> +
> +  ASSERT_EFI_ERROR (Status);
> +
> +  FreePool (VolumeName);
> +
> +  *BufferSize = NeededLength;
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Returns information about a file.
> +
> +  @param[in]      This            A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                                  handle the requested information is for.
> +  @param[in]      InformationType The type identifier for the information being requested.
> +  @param[in out]  BufferSize      On input, the size of Buffer. On output, the amount of data
> +                                  returned in Buffer. In both cases, the size is measured in bytes.
> +  @param[out]     Buffer          A pointer to the data buffer to return. The buffer's type is
> +                                  indicated by InformationType.
> +
> +  @retval EFI_SUCCESS          The information was returned.
> +  @retval EFI_UNSUPPORTED      The InformationType is not known.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current directory entry.
> +                               BufferSize has been updated with the size needed to complete
> +                               the request.
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4GetInfo (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN EFI_GUID           *InformationType,
> +  IN OUT UINTN          *BufferSize,
> +  OUT VOID              *Buffer
> +  )
> +{
> +  EXT4_FILE       *File;
> +  EXT4_PARTITION  *Partition;
> +
> +  File      = EXT4_FILE_FROM_THIS (This);
> +  Partition = File->Partition;
> +
> +  if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
> +    return Ext4GetFileInfo (File, Buffer, BufferSize);
> +  }
> +
> +  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
> +    return Ext4GetFilesystemInfo (Partition, Buffer, BufferSize);
> +  }
> +
> +  if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
> +    return Ext4GetVolumeLabelInfo (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;
> +  EFI_STATUS      Status;
> +
> +  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->Position = 0;
> +  Ext4SetupFile (File, Partition);
> +  File->InodeNum = Original->InodeNum;
> +  File->OpenMode = 0; // Will be filled by other code
> +
> +  Status = Ext4InitExtentsMap (File);
> +  if (EFI_ERROR (Status)) {
> +    FreePool (File->Inode);
> +    FreePool (File);
> +    return NULL;
> +  }
> +
> +  File->Dentry = Original->Dentry;
> +
> +  Ext4RefDentry (File->Dentry);
> +
> +  InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
> +
> +  return File;
> +}
> +
> +/**
> +  Sets information about a file.
> +
> +  @param[in]  This            A pointer to the EFI_FILE_PROTOCOL instance that is the file
> +                              handle the information is for.
> +  @param[in]  InformationType The type identifier for the information being set.
> +  @param[in]  BufferSize      The size, in bytes, of Buffer.
> +  @param[in]  Buffer          A pointer to the data buffer to write. The buffer's type is
> +                              indicated by InformationType.
> +
> +  @retval EFI_SUCCESS          The information was set.
> +  @retval EFI_UNSUPPORTED      The InformationType is not known.
> +  @retval EFI_NO_MEDIA         The device has no medium.
> +  @retval EFI_DEVICE_ERROR     The device reported an error.
> +  @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
> +  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_INFO_ID and the media is
> +                               read-only.
> +  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_PROTOCOL_SYSTEM_INFO_ID
> +                               and the media is read only.
> +  @retval EFI_WRITE_PROTECTED  InformationType is EFI_FILE_SYSTEM_VOLUME_LABEL_ID
> +                               and the media is read-only.
> +  @retval EFI_ACCESS_DENIED    An attempt is made to change the name of a file to a
> +                               file that is already present.
> +  @retval EFI_ACCESS_DENIED    An attempt is being made to change the EFI_FILE_DIRECTORY
> +                               Attribute.
> +  @retval EFI_ACCESS_DENIED    An attempt is being made to change the size of a directory.
> +  @retval EFI_ACCESS_DENIED    InformationType is EFI_FILE_INFO_ID and the file was opened
> +                               read-only and an attempt is being made to modify a field
> +                               other than Attribute.
> +  @retval EFI_VOLUME_FULL      The volume is full.
> +  @retval EFI_BAD_BUFFER_SIZE  BufferSize is smaller than the size of the type indicated
> +                               by InformationType.
> +
> +**/
> +EFI_STATUS
> +EFIAPI
> +Ext4SetInfo (
> +  IN EFI_FILE_PROTOCOL  *This,
> +  IN EFI_GUID           *InformationType,
> +  IN UINTN              BufferSize,
> +  IN VOID               *Buffer
> +  )
> +{
> +  EXT4_FILE       *File;
> +  EXT4_PARTITION  *Partition;
> +
> +  File      = EXT4_FILE_FROM_THIS (This);
> +  Partition = File->Partition;
> +
> +  if (Partition->ReadOnly) {
> +    return EFI_WRITE_PROTECTED;
> +  }
> +
> +  // There's no write support just yet.
> +  return EFI_UNSUPPORTED;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Inode.c b/Filesystems/Ext4Pkg/Ext4Dxe/Inode.c
> new file mode 100644
> index 000000000000..5ccb4d2bfc42
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Inode.c
> @@ -0,0 +1,490 @@
> +/** @file
> +  Inode related routines
> +
> +  Copyright (c) 2021 - 2022 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"
> +
> +/**
> +   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;
> +  UINTN       Length;
> +
> +  HasSecondChecksumField = EXT4_INODE_HAS_FIELD (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) {
> +    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
> +  )
> +{
> +  EXT4_INODE   *Inode;
> +  UINT64       InodeSize;
> +  UINT64       CurrentSeek;
> +  UINTN        RemainingRead;
> +  UINTN        BeenRead;
> +  UINTN        WasRead;
> +  EXT4_EXTENT  Extent;
> +  UINT32       BlockOff;
> +  EFI_STATUS   Status;
> +  BOOLEAN      HasBackingExtent;
> +  UINT32       HoleOff;
> +  UINT64       HoleLen;
> +  UINT64       ExtentStartBytes;
> +  UINT64       ExtentLengthBytes;
> +  UINT64       ExtentLogicalBytes;
> +
> +  // Our extent offset is the difference between CurrentSeek and ExtentLogicalBytes
> +  UINT64  ExtentOffset;
> +  UINTN   ExtentMayRead;
> +
> +  Inode         = File->Inode;
> +  InodeSize     = EXT4_INODE_SIZE (Inode);
> +  CurrentSeek   = Offset;
> +  RemainingRead = *Length;
> +  BeenRead      = 0;
> +
> +  DEBUG ((DEBUG_FS, "[ext4] Ext4Read(%s, Offset %lu, Length %lu)\n", File->Dentry->Name, Offset, *Length));
> +
> +  if (Offset > InodeSize) {
> +    return EFI_DEVICE_ERROR;
> +  }
> +
> +  if (RemainingRead > InodeSize - Offset) {
> +    RemainingRead = (UINTN)(InodeSize - Offset);
> +  }
> +
> +  while (RemainingRead != 0) {
> +    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 || EXT4_EXTENT_IS_UNINITIALIZED (&Extent)) {
> +      HoleOff = BlockOff;
> +
> +      if (!HasBackingExtent) {
> +        HoleLen = Partition->BlockSize - HoleOff;
> +      } else {
> +        // Uninitialized extents behave exactly the same as file holes, except they have
> +        // blocks already allocated to them.
> +        HoleLen = (Ext4GetExtentLength (&Extent) * Partition->BlockSize) - HoleOff;
> +      }
> +
> +      WasRead = HoleLen > RemainingRead ? RemainingRead : (UINTN)HoleLen;
> +      // Potential improvement: In the future, we could get the file hole's total
> +      // size and memset all that
> +      ZeroMem (Buffer, WasRead);
> +    } else {
> +      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;
> +      ExtentMayRead      = (UINTN)(ExtentLengthBytes - ExtentOffset);
> +
> +      WasRead = ExtentMayRead > RemainingRead ? RemainingRead : ExtentMayRead;
> +
> +      Status = Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBytes + ExtentOffset);
> +
> +      if (EFI_ERROR (Status)) {
> +        DEBUG ((
> +          DEBUG_ERROR,
> +          "[ext4] Error %r 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;
> +
> +  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;
> +
> +  // 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.
> +  // It's important to note that EXT4_INODE includes fields that may not exist in an actual
> +  // filesystem (the minimum inode size is 128 byte and at the moment the size of EXT4_INODE
> +  // is 160 bytes).
> +
> +  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 symlink.
> +
> +   @param[in]      File          Pointer to the opened file.
> +
> +   @return BOOLEAN         Whether file is a symlink
> +**/
> +BOOLEAN
> +Ext4FileIsSymlink (
> +  IN CONST EXT4_FILE  *File
> +  )
> +{
> +  return (File->Inode->i_mode & EXT4_INO_TYPE_SYMLINK) == EXT4_INO_TYPE_SYMLINK;
> +}
> +
> +/**
> +   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 (
> +  IN EXT4_FILE  *File
> +  )
> +{
> +  BOOLEAN  HugeFile;
> +  UINT64   Blocks;
> +
> +  HugeFile = EXT4_HAS_RO_COMPAT (File->Partition, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
> +  Blocks   = File->Inode->i_blocks;
> +
> +  if (HugeFile) {
> +    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) != 0) {
> +      return MultU64x32 (Blocks, File->Partition->BlockSize);
> +    }
> +  }
> +
> +  // Else, each i_blocks unit corresponds to 512 bytes
> +  return MultU64x32 (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[in]   EpochSeconds   Epoch seconds.
> +  @param[out]  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 (EXT4_INODE_HAS_FIELD (Inode, Field ## _extra)) {          \
> +      SecondsEpoch |= LShiftU64 ((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 (!EXT4_INODE_HAS_FIELD (Inode, i_crtime)) {
> +    ZeroMem (Time, sizeof (EFI_TIME));
> +    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 (!EXT4_HAS_METADATA_CSUM (Partition)) {
> +    return TRUE;
> +  }
> +
> +  Csum = Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);
> +
> +  DiskCsum = Inode->i_osd2.data_linux.l_i_checksum_lo;
> +
> +  if (EXT4_INODE_HAS_FIELD (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;
> +  }
> +
> +  return Csum == DiskCsum;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Partition.c b/Filesystems/Ext4Pkg/Ext4Dxe/Partition.c
> new file mode 100644
> index 000000000000..316807497dd4
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Partition.c
> @@ -0,0 +1,132 @@
> +/** @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,
> +  IN EXT4_PARTITION  *Partition
> +  )
> +{
> +  // Note: We don't yet support revision 2 of the file protocol
> +  // (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->Protocol.SetInfo     = Ext4SetInfo;
> +
> +  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;
> +  EXT4_FILE   *File;
> +  BOOLEAN     DeletedRootDentry;
> +
> +  Partition->Unmounting = TRUE;
> +  Ext4CloseInternal (Partition->Root);
> +
> +  BASE_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Partition->OpenFiles) {
> +    File = EXT4_FILE_FROM_OPEN_FILES_NODE (Entry);
> +
> +    Ext4CloseInternal (File);
> +  }
> +
> +  DeletedRootDentry = Ext4UnrefDentry (Partition->RootDentry);
> +
> +  if (!DeletedRootDentry) {
> +    DEBUG ((DEBUG_ERROR, "[ext4] Failed to delete root dentry - resource leak present.\n"));
> +  }
> +
> +  FreePool (Partition->BlockGroups);
> +  FreePool (Partition);
> +
> +  return EFI_SUCCESS;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c b/Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c
> new file mode 100644
> index 000000000000..edee051c41e8
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Superblock.c
> @@ -0,0 +1,355 @@
> +/** @file
> +  Superblock managing routines
> +
> +  Copyright (c) 2021 - 2022 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;
> +
> +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 | EXT4_FEATURE_INCOMPAT_RECOVER;
> +
> +// Future features that may be nice additions in the future:
> +// 1) Btree support: Required for write support and would speed up lookups in large directories.
> +// 2) meta_bg: Required to mount meta_bg-enabled partitions.
> +
> +// 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).
> +
> +// Note on corruption signaling:
> +// We (Ext4Dxe) could signal corruption by setting s_state to |= EXT4_FS_STATE_ERRORS_DETECTED.
> +// I've decided against that, because right now the driver is read-only, and
> +// that would mean we would need to writeback the superblock. If something like
> +// this is desired, it's fairly trivial to look for EFI_VOLUME_CORRUPTED
> +// references and add some Ext4SignalCorruption function + function call.
> +
> +/**
> +   Checks the superblock's magic value.
> +
> +   @param[in] DiskIo      Pointer to the DiskIo.
> +   @param[in] BlockIo     Pointer to the BlockIo.
> +
> +   @returns Whether the partition has a valid EXT4 superblock magic value.
> +**/
> +BOOLEAN
> +Ext4SuperblockCheckMagic (
> +  IN EFI_DISK_IO_PROTOCOL   *DiskIo,
> +  IN EFI_BLOCK_IO_PROTOCOL  *BlockIo
> +  )
> +{
> +  UINT16      Magic;
> +  EFI_STATUS  Status;
> +
> +  Status = DiskIo->ReadDisk (
> +                     DiskIo,
> +                     BlockIo->Media->MediaId,
> +                     EXT4_SUPERBLOCK_OFFSET + OFFSET_OF (EXT4_SUPERBLOCK, s_magic),
> +                     sizeof (Magic),
> +                     &Magic
> +                     );
> +  if (EFI_ERROR (Status)) {
> +    return FALSE;
> +  }
> +
> +  if (Magic != EXT4_SIGNATURE) {
> +    return FALSE;
> +  }
> +
> +  return TRUE;
> +}
> +
> +/**
> +   Does brief validation of the ext4 superblock.
> +
> +   @param[in] Sb     Pointer to the read superblock.
> +
> +   @return TRUE if a valid ext4 superblock, else FALSE.
> +**/
> +BOOLEAN
> +Ext4SuperblockValidate (
> +  CONST EXT4_SUPERBLOCK  *Sb
> +  )
> +{
> +  if (Sb->s_magic != EXT4_SIGNATURE) {
> +    return FALSE;
> +  }
> +
> +  if ((Sb->s_rev_level != EXT4_DYNAMIC_REV) && (Sb->s_rev_level != EXT4_GOOD_OLD_REV)) {
> +    return FALSE;
> +  }
> +
> +  if ((Sb->s_state & EXT4_FS_STATE_UNMOUNTED) == 0) {
> +    DEBUG ((DEBUG_WARN, "[ext4] Filesystem was not unmounted cleanly\n"));
> +  }
> +
> +  return TRUE;
> +}
> +
> +/**
> +   Calculates the superblock's checksum.
> +
> +   @param[in] Partition    Pointer to the opened partition.
> +   @param[in] Sb           Pointer to the superblock.
> +
> +   @return The superblock's checksum.
> +**/
> +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;
> +
> +  Checksum = Ext4CalculateChecksum (
> +               Partition,
> +               Sb,
> +               OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),
> +               ~0U
> +               );
> +
> +  return Checksum;
> +}
> +
> +/**
> +   Verifies that the superblock's checksum is valid.
> +
> +   @param[in] Partition    Pointer to the opened partition.
> +   @param[in] Sb           Pointer to the superblock.
> +
> +   @return The superblock's checksum.
> +**/
> +STATIC
> +BOOLEAN
> +Ext4VerifySuperblockChecksum (
> +  EXT4_PARTITION         *Partition,
> +  CONST EXT4_SUPERBLOCK  *Sb
> +  )
> +{
> +  if (!EXT4_HAS_METADATA_CSUM (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;
> +  EXT4_BLOCK_GROUP_DESC  *Desc;
> +
> +  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;
> +
> +    // Check for proper alignment of InodeSize and that InodeSize is indeed larger than
> +    // the minimum size, 128 bytes.
> +    if (((Partition->InodeSize % 4) != 0) || (Partition->InodeSize < EXT4_GOOD_OLD_INODE_SIZE)) {
> +      return EFI_VOLUME_CORRUPTED;
> +    }
> +  } 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 ((
> +      DEBUG_ERROR,
> +      "[ext4] Unsupported features %lx\n",
> +      Partition->FeaturesIncompat & ~gSupportedIncompatFeat
> +      ));
> +    return EFI_UNSUPPORTED;
> +  }
> +
> +  if (EXT4_HAS_INCOMPAT (Partition, EXT4_FEATURE_INCOMPAT_RECOVER)) {
> +    DEBUG ((DEBUG_WARN, "[ext4] Needs journal recovery, mounting read-only\n"));
> +    Partition->ReadOnly = TRUE;
> +  }
> +
> +  // 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) != 0) {
> +    Partition->InitialSeed = Sb->s_checksum_seed;
> +  } else {
> +    Partition->InitialSeed = Ext4CalculateChecksum (Partition, Sb->s_uuid, 16, ~0U);
> +  }
> +
> +  UnsupportedRoCompat = Partition->FeaturesRoCompat & ~gSupportedRoCompatFeat;
> +
> +  if (UnsupportedRoCompat != 0) {
> +    DEBUG ((DEBUG_WARN, "[ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
> +    Partition->ReadOnly = TRUE;
> +  }
> +
> +  // gSupportedCompatFeat is documentation-only since we never need to access it.
> +  // The line below avoids unused variable warnings.
> +  (VOID)gSupportedCompatFeat;
> +
> +  DEBUG ((DEBUG_FS, "Read only = %u\n", Partition->ReadOnly));
> +
> +  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) {
> +    return EFI_UNSUPPORTED;
> +  }
> +
> +  Partition->NumberBlocks      = EXT4_BLOCK_NR_FROM_HALFS (Partition, Sb->s_blocks_count, Sb->s_blocks_count_hi);
> +  Partition->NumberBlockGroups = DivU64x32 (Partition->NumberBlocks, Sb->s_blocks_per_group);
> +
> +  DEBUG ((
> +    DEBUG_FS,
> +    "[ext4] Number of blocks = %lu\n[ext4] Number of block groups: %lu\n",
> +    Partition->NumberBlocks,
> +    Partition->NumberBlockGroups
> +    ));
> +
> +  if (EXT4_IS_64_BIT (Partition)) {
> +    // s_desc_size should be 4 byte aligned and
> +    // 64 bit filesystems need DescSize to be 64 bytes
> +    if (((Sb->s_desc_size % 4) != 0) || (Sb->s_desc_size < EXT4_64BIT_BLOCK_DESC_SIZE)) {
> +      return EFI_VOLUME_CORRUPTED;
> +    }
> +
> +    Partition->DescSize = Sb->s_desc_size;
> +  } else {
> +    Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
> +  }
> +
> +  if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {
> +    DEBUG ((DEBUG_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
> +    return EFI_VOLUME_CORRUPTED;
> +  }
> +
> +  NrBlocks = (UINTN)DivU64x32Remainder (
> +                      MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
> +                      Partition->BlockSize,
> +                      &NrBlocksRem
> +                      );
> +
> +  if (NrBlocksRem != 0) {
> +    NrBlocks++;
> +  }
> +
> +  Partition->BlockGroups = Ext4AllocAndReadBlocks (Partition, NrBlocks, Partition->BlockSize == 1024 ? 2 : 1);
> +
> +  if (Partition->BlockGroups == NULL) {
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  for (Index = 0; Index < Partition->NumberBlockGroups; Index++) {
> +    Desc = Ext4GetBlockGroupDesc (Partition, Index);
> +    if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {
> +      DEBUG ((DEBUG_ERROR, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
> +      FreePool (Partition->BlockGroups);
> +      return EFI_VOLUME_CORRUPTED;
> +    }
> +  }
> +
> +  // RootDentry will serve as the basis of our directory entry tree.
> +  Partition->RootDentry = Ext4CreateDentry (L"\\", NULL);
> +
> +  if (Partition->RootDentry == NULL) {
> +    FreePool (Partition->BlockGroups);
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  // 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);
> +
> +  if (EFI_ERROR (Status)) {
> +    Ext4UnrefDentry (Partition->RootDentry);
> +    FreePool (Partition->BlockGroups);
> +  }
> +
> +  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 (!EXT4_HAS_METADATA_CSUM (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:
> +      ASSERT (FALSE);
> +      return 0;
> +  }
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c b/Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c
> new file mode 100644
> index 000000000000..0905417ffb88
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Dxe/Symlink.c
> @@ -0,0 +1,261 @@
> +/** @file
> +  Symbolic links routines
> +
> +  Copyright (c) 2022 Savva Mitrofanov All rights reserved.
> +  SPDX-License-Identifier: BSD-2-Clause-Patent
> +**/
> +
> +#include "Ext4Dxe.h"
> +
> +/**
> +   Detects if a symlink is a fast symlink.
> +
> +   @param[in]      File          Pointer to the opened file.
> +
> +   @return BOOLEAN         Whether symlink is a fast symlink
> +**/
> +STATIC
> +BOOLEAN
> +Ext4SymlinkIsFastSymlink (
> +  IN CONST EXT4_FILE  *File
> +  )
> +{
> +  //
> +  // Detection logic of the fast-symlink splits into two behaviors - old and new.
> +  // The old behavior is based on comparing the extended attribute blocks
> +  // with the inode's i_blocks, and if it's zero we know the inode isn't storing
> +  // the link in filesystem blocks, so we look to the inode->i_data.
> +  // The new behavior is apparently needed only with the large EA inode feature.
> +  // In this case we check that inode size less than maximum fast symlink size.
> +  // So, we revert to the old behavior if the large EA inode feature is not set.
> +  //
> +  UINT32  FileAcl;
> +  UINT32  ExtAttrBlocks;
> +
> +  if ((File->Inode->i_flags & EXT4_EA_INODE_FL) == 0) {
> +    FileAcl = File->Inode->i_file_acl;
> +    if (EXT4_IS_64_BIT (File->Partition)) {
> +      //
> +      // We don't care about final value, we are just checking for any bit is set
> +      // so, thats why we neglect LShiftU64(.., 32)
> +      //
> +      FileAcl |= File->Inode->i_osd2.data_linux.l_i_file_acl_high;
> +    }
> +
> +    ExtAttrBlocks = FileAcl != 0 ? (File->Partition->BlockSize >> 9) : 0;
> +
> +    return File->Inode->i_blocks == ExtAttrBlocks;
> +  }
> +
> +  return EXT4_INODE_SIZE (File->Inode) <= EXT4_FAST_SYMLINK_MAX_SIZE;
> +}
> +
> +/**
> +  Reads a fast symlink file.
> +
> +  @param[in]      Partition   Pointer to the ext4 partition.
> +  @param[in]      File        Pointer to the open symlink file.
> +  @param[out]     AsciiSymlink     Pointer to the output ascii symlink string.
> +  @param[out]     AsciiSymlinkSize Pointer to the output ascii symlink string length.
> +
> +  @retval EFI_SUCCESS            Fast symlink was read.
> +  @retval EFI_OUT_OF_RESOURCES   Memory allocation error.
> +**/
> +STATIC
> +EFI_STATUS
> +Ext4ReadFastSymlink (
> +  IN     EXT4_PARTITION  *Partition,
> +  IN     EXT4_FILE       *File,
> +  OUT    CHAR8           **AsciiSymlink,
> +  OUT    UINT32          *AsciiSymlinkSize
> +  )
> +{
> +  UINT32  SymlinkSize;
> +  CHAR8   *AsciiSymlinkTmp;
> +
> +  //
> +  // Fast-symlink's EXT4_INODE_SIZE is not necessarily validated when we checked it in
> +  // Ext4SymlinkIsFastSymlink(), so truncate if necessary.
> +  //
> +  SymlinkSize = (UINT32)MIN (EXT4_INODE_SIZE (File->Inode), EXT4_FAST_SYMLINK_MAX_SIZE);
> +
> +  AsciiSymlinkTmp = AllocatePool (SymlinkSize + 1);
> +  if (AsciiSymlinkTmp == NULL) {
> +    DEBUG ((DEBUG_ERROR, "[ext4] Failed to allocate symlink ascii string buffer\n"));
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  CopyMem (AsciiSymlinkTmp, File->Inode->i_data, SymlinkSize);
> +
> +  //
> +  // Add null-terminator
> +  //
> +  AsciiSymlinkTmp[SymlinkSize] = '\0';
> +
> +  *AsciiSymlink     = AsciiSymlinkTmp;
> +  *AsciiSymlinkSize = SymlinkSize + 1;
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Reads a slow symlink file.
> +
> +  @param[in]      Partition        Pointer to the ext4 partition.
> +  @param[in]      File             Pointer to the open symlink file.
> +  @param[out]     AsciiSymlink     Pointer to the output ascii symlink string.
> +  @param[out]     AsciiSymlinkSize Pointer to the output ascii symlink string length.
> +
> +  @retval EFI_SUCCESS           Slow symlink was read.
> +  @retval EFI_OUT_OF_RESOURCES  Memory allocation error.
> +  @retval EFI_INVALID_PARAMETER Slow symlink path has incorrect length
> +  @retval EFI_VOLUME_CORRUPTED  Symlink read block size differ from inode value
> +**/
> +STATIC
> +EFI_STATUS
> +Ext4ReadSlowSymlink (
> +  IN     EXT4_PARTITION  *Partition,
> +  IN     EXT4_FILE       *File,
> +  OUT    CHAR8           **AsciiSymlink,
> +  OUT    UINT32          *AsciiSymlinkSize
> +  )
> +{
> +  EFI_STATUS  Status;
> +  CHAR8       *SymlinkTmp;
> +  UINT64      SymlinkSizeTmp;
> +  UINT32      SymlinkAllocateSize;
> +  UINTN       ReadSize;
> +
> +  SymlinkSizeTmp = EXT4_INODE_SIZE (File->Inode);
> +
> +  //
> +  // Allocate EXT4_INODE_SIZE + 1
> +  //
> +  if (SymlinkSizeTmp >= EXT4_EFI_PATH_MAX) {
> +    DEBUG ((
> +      DEBUG_WARN,
> +      "[ext4] Warn: symlink path maximum length was hit!\n"
> +      ));
> +    return EFI_INVALID_PARAMETER;
> +  }
> +
> +  SymlinkAllocateSize = (UINT32)SymlinkSizeTmp + 1;
> +
> +  SymlinkTmp = AllocatePool (SymlinkAllocateSize);
> +  if (SymlinkTmp == NULL) {
> +    DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink ascii string buffer\n"));
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  ReadSize = (UINTN)SymlinkSizeTmp;
> +  Status   = Ext4Read (Partition, File, SymlinkTmp, File->Position, &ReadSize);
> +  if (EFI_ERROR (Status)) {
> +    DEBUG ((DEBUG_FS, "[ext4] Failed to read symlink from blocks with status %r\n", Status));
> +    FreePool (SymlinkTmp);
> +    return Status;
> +  }
> +
> +  //
> +  // Add null-terminator
> +  //
> +  SymlinkTmp[SymlinkSizeTmp] = '\0';
> +
> +  if (SymlinkSizeTmp != ReadSize) {
> +    DEBUG ((
> +      DEBUG_FS,
> +      "[ext4] Error! The size of the read block doesn't match the value from the inode!\n"
> +      ));
> +    return EFI_VOLUME_CORRUPTED;
> +  }
> +
> +  *AsciiSymlinkSize = SymlinkAllocateSize;
> +  *AsciiSymlink     = SymlinkTmp;
> +
> +  return EFI_SUCCESS;
> +}
> +
> +/**
> +  Reads a symlink file.
> +
> +  @param[in]      Partition   Pointer to the ext4 partition.
> +  @param[in]      File        Pointer to the open symlink file.
> +  @param[out]     Symlink     Pointer to the output unicode symlink string.
> +
> +  @retval EFI_SUCCESS           Symlink was read.
> +  @retval EFI_ACCESS_DENIED     Symlink is encrypted.
> +  @retval EFI_OUT_OF_RESOURCES  Memory allocation error.
> +  @retval EFI_INVALID_PARAMETER Symlink path has incorrect length
> +  @retval EFI_VOLUME_CORRUPTED  Symlink read block size differ from inode value
> +**/
> +EFI_STATUS
> +Ext4ReadSymlink (
> +  IN     EXT4_PARTITION  *Partition,
> +  IN     EXT4_FILE       *File,
> +  OUT    CHAR16          **Symlink
> +  )
> +{
> +  EFI_STATUS  Status;
> +  CHAR8       *SymlinkTmp;
> +  UINT32      SymlinkSize;
> +  CHAR16      *Symlink16Tmp;
> +  CHAR16      *Needle;
> +
> +  //
> +  // Assume that we alread read Inode via Ext4ReadInode
> +  // Skip reading, just check encryption flag
> +  //
> +  if ((File->Inode->i_flags & EXT4_ENCRYPT_FL) != 0) {
> +    DEBUG ((DEBUG_WARN, "[ext4] Warn: symlink is encrypted\n"));
> +    return EFI_ACCESS_DENIED;
> +  }
> +
> +  if (Ext4SymlinkIsFastSymlink (File)) {
> +    Status = Ext4ReadFastSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
> +  } else {
> +    Status = Ext4ReadSlowSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
> +  }
> +
> +  if (EFI_ERROR (Status)) {
> +    DEBUG ((DEBUG_FS, "[ext4] Symlink read error with Status %r\n", Status));
> +    return Status;
> +  }
> +
> +  Symlink16Tmp = AllocatePool (SymlinkSize * sizeof (CHAR16));
> +  if (Symlink16Tmp == NULL) {
> +    DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink unicode string buffer\n"));
> +    FreePool (SymlinkTmp);
> +    return EFI_OUT_OF_RESOURCES;
> +  }
> +
> +  Status = AsciiStrToUnicodeStrS (
> +             SymlinkTmp,
> +             Symlink16Tmp,
> +             SymlinkSize
> +             );
> +
> +  FreePool (SymlinkTmp);
> +
> +  if (EFI_ERROR (Status)) {
> +    DEBUG ((
> +      DEBUG_FS,
> +      "[ext4] Failed to convert ascii symlink to unicode with Status %r\n",
> +      Status
> +      ));
> +    FreePool (Symlink16Tmp);
> +    FreePool (SymlinkTmp);
> +    return Status;
> +  }
> +
> +  //
> +  // Convert to UEFI slashes
> +  //
> +  for (Needle = Symlink16Tmp; *Needle != L'\0'; Needle++) {
> +    if (*Needle == L'/') {
> +      *Needle = L'\\';
> +    }
> +  }
> +
> +  *Symlink = Symlink16Tmp;
> +
> +  return Status;
> +}
> diff --git a/Filesystems/Ext4Pkg/Ext4Libs.dsc.inc b/Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
> new file mode 100644
> index 000000000000..078183e0ccc6
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Libs.dsc.inc
> @@ -0,0 +1,11 @@
> +## @file
> +# Ext4 DSC include file for [LibraryClasses] section of all Architectures.
> +#
> +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> +# SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +##
> +
> +!if $(EXT4_ENABLE) == TRUE
> +  BaseUcs2Utf8Lib|RedfishPkg/Library/BaseUcs2Utf8Lib/BaseUcs2Utf8Lib.inf
> +!endif
> diff --git a/Filesystems/Ext4Pkg/Ext4Pkg.dec b/Filesystems/Ext4Pkg/Ext4Pkg.dec
> new file mode 100644
> index 000000000000..f1f8b39c3c7e
> --- /dev/null
> +++ b/Filesystems/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/Filesystems/Ext4Pkg/Ext4Pkg.dsc b/Filesystems/Ext4Pkg/Ext4Pkg.dsc
> new file mode 100644
> index 000000000000..482965bb7757
> --- /dev/null
> +++ b/Filesystems/Ext4Pkg/Ext4Pkg.dsc
> @@ -0,0 +1,75 @@
> +## @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
> +
> +!include MdePkg/MdeLibs.dsc.inc
> +
> +[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
> +  
> +  #
> +  # Required for stack protector support
> +  #
> +  NULL|MdePkg/Library/BaseStackCheckLib/BaseStackCheckLib.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]
> +  Filesystems/Ext4Pkg/Ext4Dxe/Ext4Dxe.inf
> diff --git a/Filesystems/Ext4Pkg/Ext4Pkg.uni b/Filesystems/Ext4Pkg/Ext4Pkg.uni
> new file mode 100644
> index 000000000000..abeadd8fd926
> --- /dev/null
> +++ b/Filesystems/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."
> diff --git a/Maintainers.txt b/Maintainers.txt
> index 7e98083685bc..81346b0caddf 100644
> --- a/Maintainers.txt
> +++ b/Maintainers.txt
> @@ -218,6 +218,13 @@ M: Ray Ni <ray.ni@intel.com> [niruiyu]
> T: svn - https://svn.code.sf.net/p/edk2-fatdriver2/code/trunk/EnhancedFat
> T: git - https://github.com/tianocore/edk2-FatPkg.git
> 
> +Filesystems/Ext4Pkg
> +F: Filesystems/Ext4Pkg/
> +M: Pedro Falcato <pedro.falcato@gmail.com> [heatd]
> +R: Marvin Häuser <mhaeuser@posteo.de> [mhaeuser]
> +T: git (maintainer's tree) - https://github.com/heatd/edk2.git
> +S: Maintained
> +
> FmpDevicePkg
> F: FmpDevicePkg/
> W: https://github.com/tianocore/tianocore.github.io/wiki/FmpDevicePkg
> -- 
> 2.38.1
> 


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

end of thread, other threads:[~2022-11-17 10:28 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-11-16 23:34 [RFC PATCH 1/1] Ext4Pkg: Add Ext4Pkg to edk2.git Pedro Falcato
2022-11-17 10:28 ` Marvin Häuser

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