通常,每个UEFI系统至少有一个 ESP (EFI System Partition)分区,在这个分区上存放启动文件。EFI内置了EFI_SIMPLE_FILE_SYSTEM_PROTOCOL(简称 FileSystemIo)可以用来操作FAT文件系统【参考1】。
相关的介绍:
\MdePkg\Include\Protocol\SimpleFileSystem.h
/// /// The EFI_FILE_PROTOCOL provides file IO access to supported file systems. /// An EFI_FILE_PROTOCOL provides access to a file's or directory's contents, /// and is also a reference to a location in the directory tree of the file system /// in which the file resides. With any given file handle, other files may be opened /// relative to this file's location, yielding new file handles. /// 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; };
\MdePkg\Include\Protocol\SimpleFileSystem.h
#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \ { \ 0x964e5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \ }
\EdkCompatibilityPkg\Foundation\Efi\Protocol\SimpleFileSystem\SimpleFileSystem.c
EFI_GUID gEfiSimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
从原理上来说,先通过这个Protocol OpenVolume,即可获得FAT文件系统上的根目录句柄。目录句柄(EFI_FILE_PROTOCOL)包含了操作该目录里文件的文件操作接口。之后我们再用 Open打开这个目录,选定需要操作的文件即可写入。
\MdePkg\Include\Protocol\SimpleFileSystem.h
/** Writes data to a file. @param This A pointer to the EFI_FILE_PROTOCOL instance that is the file handle to write data to. @param 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 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. **/ typedef EFI_STATUS (EFIAPI *EFI_FILE_WRITE)( IN EFI_FILE_PROTOCOL *This, IN OUT UINTN *BufferSize, IN VOID *Buffer );
完整代码:
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Protocol/EfiShell.h> #include <Library/ShellLib.h> #include <Protocol/SimpleFileSystem.h> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; int EFIAPI main ( IN int Argc, IN char **Argv ) { EFI_STATUS Status; EFI_FILE_PROTOCOL *Root; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem; UINTN BufSize; CHAR16 *Textbuf = (CHAR16*) L"www.lab-z.com"; EFI_FILE_PROTOCOL *FileHandle=0; Status = gBS->LocateProtocol( &gEfiSimpleFileSystemProtocolGuid, NULL, (VOID **)&SimpleFileSystem); if (EFI_ERROR(Status)) { Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n"); return Status; } Status = SimpleFileSystem->OpenVolume(SimpleFileSystem,&Root); if (EFI_ERROR(Status)) { Print(L"OpenVolume error \r\n"); return Status; } Status = Root -> Open(Root, &FileHandle, (CHAR16 *) L"atest.txt", EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0); if (EFI_ERROR(Status) || (FileHandle==0)) { Print(L"Open error \r\n"); return Status; } BufSize = StrLen (Textbuf) * 2; Print(L"[%d]\r\n",BufSize); Print(L"[%s]\r\n",Textbuf); Status = FileHandle -> Write(FileHandle, &BufSize, Textbuf); Print(L"Write Done \r\n"); Status = FileHandle -> Close (FileHandle); return EFI_SUCCESS; }
运行结果:
比较有意思的是,我们按照上面的程序写入之后,使用 type 文件名 来显示文件内容和我们的预期有差别,原因是Type命令默认使用 ASCII 来显内容,而我们的文件中写入的是 Unicode。使用 type -u 强制使用 Unicode即可看到我们期望的内容。
根据【参考2】的提示,我们还可以通过给TXT文件加一个头来通知当前内容格式的方法克服这个问题。
关键代码:
UINT16 TextHeader =0xFEFF;
BufSize = 2; Status = FileHandle -> Write(FileHandle, &BufSize, &TextHeader); BufSize = StrLen (Textbuf) * 2; Print(L"[%d]\r\n",BufSize); Print(L"[%s]\r\n",Textbuf); Status = FileHandle -> Write(FileHandle, &BufSize, Textbuf);
运行结果:
可以看到经过这样的处理,都可以正常显示内容了。
完整代码下载
ESPTest
参考:
1.UEFI 原理与编程 戴正华 著 P152页
2.http://www.biosren.com/thread-6541-2-1.html UEFI下使用EFI_FILE_PROTOCOL 檔案操作能多次讀寫嗎?
=============================================================
2024年9月20日
下面这个代码段能够实现将指定内存内容保存为文件,方便调试,有需要的朋友可以试试。
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/SimpleFileSystem.h>
extern EFI_HANDLE gImageHandle;
EFI_STATUS
WriteMemoryToFile(
IN EFI_HANDLE ImageHandle,
IN CHAR16 *FileName,
IN VOID *Buffer,
IN UINTN BufferSize
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
EFI_FILE_PROTOCOL *Root;
EFI_FILE_PROTOCOL *File;
EFI_HANDLE *Handles = NULL;
UINTN HandleCount = 0;
// Locate all handles that support the Simple File System Protocol
Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &HandleCount, &Handles);
if (EFI_ERROR(Status)) {
Print(L"Failed to locate handles for Simple File System Protocol: %r\n", Status);
return Status;
}
// Open the first Simple File System Protocol
Status = gBS->HandleProtocol(Handles[0], &gEfiSimpleFileSystemProtocolGuid, (VOID **)&SimpleFileSystem);
if (EFI_ERROR(Status)) {
Print(L"Failed to open Simple File System Protocol: %r\n", Status);
return Status;
}
// Open the root directory
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
if (EFI_ERROR(Status)) {
Print(L"Failed to open root directory: %r\n", Status);
return Status;
}
// Create or open the file
Status = Root->Open(Root, &File, FileName, EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(Status)) {
Print(L"Failed to open or create file: %r\n", Status);
return Status;
}
// Write the buffer to the file
Status = File->Write(File, &BufferSize, Buffer);
if (EFI_ERROR(Status)) {
Print(L"Failed to write to file: %r\n", Status);
File->Close(File);
return Status;
}
// Close the file
Status = File->Close(File);
if (EFI_ERROR(Status)) {
Print(L"Failed to close file: %r\n", Status);
return Status;
}
Print(L"Memory successfully written to file: %s\n", FileName);
return EFI_SUCCESS;
}
你好,能否问一下,这里
UINT16 TextHeader =0xFEFF
为什么是0xFEFF?这个值怎么得来的,有点看不懂
楼主你好,还有一个问题,我发现BufSize = StrLen (Textbuf) * 2;这行代码,这里如果不乘2,就只能写入数据的一半,这里为什么要乘以2呢?不好意思,我可能问的问题很菜,抱歉!
StrLen 给出来的是字符的长度,比如 “abc” 长度是3,但是如果定义的是 char16 "abc",那么实际占用的长度就是 6. 其实这里如果写成 BufSize = StrLen (Textbuf) * sizeof(Char16)会比较好理解
感谢楼主,因为我在写File System的training,所以找到你这里的资料,很幸运看到你分享的知识和经验,感谢!
简单的说 txt 文件也有一个简单的格式,具体可以参考 https://www.zhihu.com/question/20650946?sort=created
感谢!
你好`~
想請問,如果再執行一次程式.輸出字串要如何顯示在第2行呢?也就是每執行一次就往下多顯示一次字串.
不好意思,不明白你的问题。你是想在窗口的绝对位置显示一个字符串吗?
你好...
我如果一直執行了您的程式後.開啟atest.txt檔案後,都只是顯示www.lab-z.com在左上角.
如果我想要在第2次執行程式後,把www.lab-z.com字串顯示中第二行呢.?
如下說明:
第一次執行ESPTest,顯示如下.
http://www.lab-z.com
第二次再執行ESPTest顯示如下.
http://www.lab-z.com
http://www.lab-z.com
第3次~N次.依此類推...把www.lab-z.com一直往下寫...
就是你想每次执行之后显示的次数不同?
那就把本次的显示次数存放在另外一个文件中呗。
每次主动去读另外一个文件的内容,然后加一。
是的...
感謝您...我再來研究FileSystemIo
您好,請請問一下,EFI_SIMPLE_FILE_SYSTEM_PROTOCOL可以已預設ASCII格式寫檔案嗎?
因為我有個程式需要讀取ASCII格式的內容,若EFI_SIMPLE_FILE_SYSTEM_PROTOCOL預設是Unicode寫檔就arˋ不能用了,請問我該如何讓它寫檔跟c語言的fwrite一樣直接以ascii char寫入檔案?
謝謝您...
可以,这个是 buffer 的类型决定的,如果你是 char16 那么就写入的是 unicode,如果是 char8 就是 ascii。
特别注意,长度不要计算错误就好。
能不能用c模式下的fopen函数
我有一个问题,系统里的 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL可能不止一个(比如我电脑里插着Shell U盘),那我该用哪个protocol instance呢?有办法区分吗?
具体你的目标我不太清楚,不过我觉得你可以试试查 protocol 所在的 handle,然后从 Handle 查设备类型,就可以得知是 SATA 还是 USB 这样的。但是如果你同时插上2个 U盘,这样的方法又没有办法继续区分了。另外,之前的 Microsoft 的一个 UEFI 截屏的项目是通过要求你在目标的 ESP 分区上放置特定名称的文件来实现区分的,你可以参考一下。
非常感谢,我看device path 的确是不同的,可以通过device path来区分是for shell U盘 还是SSD的。
使用 UnicodeStrToAsciStr 函数
File write Ascii text
知道方法吗?
不明白你的问题,你遇到什么具体错误了么?
您知道如何用 Ascii 取代 FILE Write 的 Unicode ? 从UEFI
我会再回复你
1. EFI_FILE_PROTOCOL.Write 函数使用
2.不是Unicode而是ASCII Write
你有办法吗?
我会再回复你
1. EFI_FILE_PROTOCOL.Write 函数使用
2.不是Unicode而是ASCII Write
你有办法吗?
我看了一下 write 定义如下,就是说它写的是一个 buffer ,如果你buffer 里面是 unicode 的,那么结果就是 unicode 的内容;反之就是 ascii 格式的,
typedef
EFI_STATUS
(EFIAPI *EFI_FILE_WRITE)(
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
);
我使用CHAR8写txt文件,但是文件出现了乱码,当时的编码为UTF-6LE,另存为时选择utf-8后再写入就不会乱码,请问怎样在程序中设置txt默认编码形式呢?
一般来说,不同编码会在 TXT 头部写入几个字符。对于UTF-16 LE)编码来说需要写入 0xFF, 0xFE
你可以试试。参考: https://www.cnblogs.com/lkpp/p/save_text_unicode.html