综述
开源的EDK代码中,仅支持FAT32文件系统,包括读和写操作。
而实际的使用当中,需要支持的文件系统有很多,比如NTFS、EXT4等,通常BIOS不需要去支持写操作,但是一般的读操作最好还是能够支持一下。
GRUB是BIOS会调用的一个BootLoader,它实际上支持很多的文件系统读操作,如下图所示:
所以通过对GRUB进行包装,使EDK支持这些文件系统成为一种可能。事实上也已经有开源的项目支持该操作,对应代码路径是GitHub - pbatard/EfiFs: EFI FileSystem drivers。
本文介绍如何将上述的项目放到EDK代码中进行编译。
编译
使用的EDK代码仓库是edk2-beni: 用于学习和验证UEFI BIOS。,对应EfiFsPkg,它依赖的GRUB代码也已经包含。
EfiFsPkg有独立的dsc文件可以进行编译,它包含了需要支持的库和模块:
[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
# 中间略
[Components]
EfiFsPkg/EfiFsPkg/Afs.inf
EfiFsPkg/EfiFsPkg/Affs.inf
# 后面略
由于编译需要一些特定的语法,所以对Visual Studio的版本有要求,这里直接使用最新的VS2022 Community版本。
编译直接使用Build.cmd脚本即可进行:
Build.cmd efifs
最终生成EFI二进制:
使用
这里测试ext2.efi文件,将它包含在BIOS二进制中:
FILE DRIVER = 7DDA7772-B8F5-4859-9DBA-0D6F2DBA4AF1 {
SECTION PE32 = Build/EfiFs/$(COMPILE_DIR)/X64/ext2.efi
}
然后创建一个disk.img文件,然后将它格式化成ext4格式,执行如下的脚本:
Build.cmd start disk
执行QEMU,可以看到ext4格式的disk.img可以被识别:
实现原理
BIOS下文件系统的实现主要依赖于两个重要的结构体,一个是EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
:
struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
///
/// The version of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. The version
/// specified by this specification is 0x00010000. All future revisions
/// must be backwards compatible.
///
UINT64 Revision;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME OpenVolume;
};
OpenVolume()
用来打开一个文件系统的根目录,得到另一个重要的结构体:
struct _EFI_FILE_PROTOCOL {
///
/// The version of the EFI_FILE_PROTOCOL interface. The version specified
/// by this specification is EFI_FILE_PROTOCOL_LATEST_REVISION.
/// Future versions are required to be backward compatible to version 1.0.
///
UINT64 Revision;
EFI_FILE_OPEN Open;
EFI_FILE_CLOSE Close;
EFI_FILE_DELETE Delete;
EFI_FILE_READ Read;
EFI_FILE_WRITE Write;
EFI_FILE_GET_POSITION GetPosition;
EFI_FILE_SET_POSITION SetPosition;
EFI_FILE_GET_INFO GetInfo;
EFI_FILE_SET_INFO SetInfo;
EFI_FILE_FLUSH Flush;
EFI_FILE_OPEN_EX OpenEx;
EFI_FILE_READ_EX ReadEx;
EFI_FILE_WRITE_EX WriteEx;
EFI_FILE_FLUSH_EX FlushEx;
};
这里就包含了文件的相关操作,所以它是需要实现的重点,而其中就包含了GRUB的文件操作代码。
This->RootFile->EfiFile.Open = FileOpen;
This->RootFile->EfiFile.Close = FileClose;
This->RootFile->EfiFile.Delete = FileDelete;
This->RootFile->EfiFile.Read = FileRead;
This->RootFile->EfiFile.Write = FileWrite;
This->RootFile->EfiFile.GetPosition = FileGetPosition;
This->RootFile->EfiFile.SetPosition = FileSetPosition;
This->RootFile->EfiFile.GetInfo = FileGetInfo;
This->RootFile->EfiFile.SetInfo = FileSetInfo;
This->RootFile->EfiFile.Flush = FileFlush;
This->RootFile->EfiFile.OpenEx = FileOpenEx;
This->RootFile->EfiFile.ReadEx = FileReadEx;
This->RootFile->EfiFile.WriteEx = FileWriteEx;
This->RootFile->EfiFile.FlushEx = FileFlushEx;
以FileOpen()
为例:
static EFI_STATUS EFIAPI
FileOpen(EFI_FILE_HANDLE This, EFI_FILE_HANDLE *New,
CHAR16 *Name, UINT64 Mode, UINT64 Attributes)
{
// 其它略
/* Finally we can call on GRUB open() if it's a regular file */
if (!NewFile->IsDir) {
Status = GrubOpen(NewFile);
}
}
可以看到其中包含了很多Grub
开头的代码,它们都位于EfiFsPkg\src\grub_file.c,这里面就会调用到GRUB的代码:
EFI_STATUS
GrubOpen(EFI_GRUB_FILE *File)
{
grub_fs_t p = grub_fs_list;
grub_file_t f = (grub_file_t) File->GrubFile;
grub_err_t rc;
grub_errno = 0;
rc = p->fs_open(f, File->path);
return GrubErrToEFIStatus(rc);
}