在UEFI shell下,BIOS的更新、EC的更新等都需要用到读取文件,从UEFI原理与编程中初步了解怎么读写,那么接下来就进入实践环节
先把后面需要的变量先定义好(便于理解):
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *ptSFS;
EFI_FILE_PROTOCOL *ptRootFile;
EFI_FILE_PROTOCOL *ptFile;
EFI_FILE_INFO *FileInfo;
UINTN FileInfoSize;
EFI_HANDLE *HandleBuffer;
UINTN NumberOfHandles;
UINTN Index;
UINT8 *Buffer;
Status = EFI_SUCCESS;
ptRootFile = NULL;
ptFile = NULL;
FileInfo = NULL;
HandleBuffer = NULL;
1、首先通过使用gBS->LocateHandleBuffer找到所有支持FileSystemIo的设备
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiSimpleFileSystemProtocolGuid,
NULL,
&NumberOfHandles,
&HandleBuffer
);
if (EFI_ERROR (Status)) {
return Status;
}
2、对于每个支持FileSystemIo的设备,打开设备上的的SimpleFileSystemProtocol
for (Index = 0; Index < NumberOfHandles; Index ++) {
Status = gBS->HandleProtocol(
HandleBuffer[Index],
&gEfiSimpleFileSystemProtocolGuid,
(VOID **)&ptSFS
);
3、通过SimpleFileSystemProtocol的OpenVolume,可以获得FAT文件系统上的根目录句柄,然后我们就可以根据这个句柄操作文件了,打开文件进行读写:
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;
};
Status = ptRootFile->Open(
ptRootFile,
&ptFile,
FileName,
EFI_FILE_MODE_READ,
0
);
4、打开之后按理来说就可以读文件了,但是有个问题,你读的文件到底有多大,需要多大的缓存,这是一个未知数,因此在读文件之前,我们还需要先获取这个文件的一些信息,比如FileSize
typedef struct {
///
/// The size of the EFI_FILE_INFO structure, including the Null-terminated FileName string.
///
UINT64 Size;
///
/// The size of the file in bytes.
///
UINT64 FileSize;
///
/// PhysicalSize The amount of physical space the file consumes on the file system volume.
///
UINT64 PhysicalSize;
///
/// The time the file was created.
///
EFI_TIME CreateTime;
///
/// The time when the file was last accessed.
///
EFI_TIME LastAccessTime;
///
/// The time when the file's contents were last modified.
///
EFI_TIME ModificationTime;
///
/// The attribute bits for the file.
///
UINT64 Attribute;
///
/// The Null-terminated name of the file.
///
CHAR16 FileName[1];
} EFI_FILE_INFO;
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;
};
Status = ptFile->GetInfo (
ptFile,
&gEfiFileInfoGuid,
&FileInfoSize,
FileInfo
);
if(Status == EFI_BUFFER_TOO_SMALL){
FileInfo = AllocateZeroPool(FileInfoSize);
if(FileInfo == NULL){
Status = EFI_OUT_OF_RESOURCES;
} else {
Status = ptFile->GetInfo(
ptFile,
&gEfiFileInfoGuid,
&FileInfoSize,
FileInfo
);
最后就是真正的读取文件
Buffer = AllocateZeroPool((UINTN)FileInfo->FileSize);
Status = ptFile->Read(ptFile, &FileInfo->FileSize, Buffer);
整体流程如下:
EFI_STATUS
ReadFileInFS (
IN CHAR16 *FileName,
OUT UINT8 **FileData,
IN OUT UINTN *BufferSize
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *ptSFS;
EFI_FILE_PROTOCOL *ptRootFile;
EFI_FILE_PROTOCOL *ptFile;
EFI_FILE_INFO *FileInfo;
UINTN FileInfoSize;
EFI_HANDLE *HandleBuffer;
UINTN NumberOfHandles;
UINTN Index;
UINT8 *Buffer;
Status = EFI_SUCCESS;
ptRootFile = NULL;
ptFile = NULL;
FileInfo = NULL;
HandleBuffer = NULL;
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiSimpleFileSystemProtocolGuid,
NULL,
&NumberOfHandles,
&HandleBuffer
);
if (EFI_ERROR (Status)) {
return Status;
}
for (Index = 0; Index < NumberOfHandles; Index ++) {
Status = gBS->HandleProtocol(
HandleBuffer[Index],
&gEfiSimpleFileSystemProtocolGuid,
(VOID **)&ptSFS
);
if (!EFI_ERROR (Status)) {
Status = ptSFS->OpenVolume(ptSFS, &ptRootFile);
if (!EFI_ERROR (Status)) {
Status = ptRootFile->Open(
ptRootFile,
&ptFile,
FileName,
EFI_FILE_MODE_READ,
0
);
if (!EFI_ERROR (Status)) {
break;
} else {
if (ptRootFile != NULL){
ptRootFile->Close(ptRootFile);
ptRootFile = NULL;
}
}
}
}
}
if (HandleBuffer != NULL) {
FreePool(HandleBuffer);
HandleBuffer = NULL;
}
if (!EFI_ERROR (Status)) {
FileInfo = NULL;
FileInfoSize = 0;
Status = ptFile->GetInfo (
ptFile,
&gEfiFileInfoGuid,
&FileInfoSize,
FileInfo
);
if(Status == EFI_BUFFER_TOO_SMALL){
FileInfo = AllocateZeroPool(FileInfoSize);
if(FileInfo == NULL){
Status = EFI_OUT_OF_RESOURCES;
} else {
Status = ptFile->GetInfo(
ptFile,
&gEfiFileInfoGuid,
&FileInfoSize,
FileInfo
);
}
}
}
if (EFI_ERROR (Status)) {
goto ProcExit;
}
if(FileInfo->Attribute & EFI_FILE_DIRECTORY){
Status = EFI_INVALID_PARAMETER;
goto ProcExit;
}
Buffer = AllocateZeroPool((UINTN)FileInfo->FileSize);
if (Buffer == NULL) {
goto ProcExit;
}
Status = ptFile->Read(ptFile, &FileInfo->FileSize, Buffer);
if (EFI_ERROR (Status)) {
FreePool (Buffer);
goto ProcExit;
}
*FileData = Buffer;
*BufferSize = FileInfo->FileSize;
ProcExit:
if (ptFile != NULL){
ptFile->Close(ptFile);
}
if (ptRootFile != NULL){
ptRootFile->Close(ptRootFile);
}
if (FileInfo != NULL) {
FreePool (FileInfo);
}
return Status;
}
在APP中,只要加入这个函数,就能读取文件,我们可以读取BIOS文件,EC文件,图片文件等等。但是这个函数有个缺点,比如我编译了一个DisplayBmpTest.efi,运行显示bmp图片,那么这个图片与DisplayBmpTest.efi必须在根目录,不能在子目录,如果在子目录,必须添加路径,比如在根目录,我直接使用DisplayBmpTest.efi Tianocore.bmp就ok了,但是在子目录,就需要运行DisplayBmpTest.efi \xxx\xxx\Tianocore.bmp,那么怎么样才能在子目录运行DisplayBmpTest.efi Tianocore.bmp就可以呢,那么我们就需要通过代码将这个路径找出来,ok,下一节接着找路径。