Step to UEFI (54) ----- EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 写文件

通常,每个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;
}

 

运行结果:

esptest

比较有意思的是,我们按照上面的程序写入之后,使用 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);

 

运行结果:

esptest2

可以看到经过这样的处理,都可以正常显示内容了。

完整代码下载
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;
}

 

 

《Step to UEFI (54) ----- EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 写文件》有25个想法

    1. 楼主你好,还有一个问题,我发现BufSize = StrLen (Textbuf) * 2;这行代码,这里如果不乘2,就只能写入数据的一半,这里为什么要乘以2呢?不好意思,我可能问的问题很菜,抱歉!

      1. StrLen 给出来的是字符的长度,比如 “abc” 长度是3,但是如果定义的是 char16 "abc",那么实际占用的长度就是 6. 其实这里如果写成 BufSize = StrLen (Textbuf) * sizeof(Char16)会比较好理解

        1. 感谢楼主,因为我在写File System的training,所以找到你这里的资料,很幸运看到你分享的知识和经验,感谢!

  1. 你好`~
    想請問,如果再執行一次程式.輸出字串要如何顯示在第2行呢?也就是每執行一次就往下多顯示一次字串.

        1. 就是你想每次执行之后显示的次数不同?
          那就把本次的显示次数存放在另外一个文件中呗。
          每次主动去读另外一个文件的内容,然后加一。

  2. 您好,請請問一下,EFI_SIMPLE_FILE_SYSTEM_PROTOCOL可以已預設ASCII格式寫檔案嗎?
    因為我有個程式需要讀取ASCII格式的內容,若EFI_SIMPLE_FILE_SYSTEM_PROTOCOL預設是Unicode寫檔就arˋ不能用了,請問我該如何讓它寫檔跟c語言的fwrite一樣直接以ascii char寫入檔案?
    謝謝您...

    1. 可以,这个是 buffer 的类型决定的,如果你是 char16 那么就写入的是 unicode,如果是 char8 就是 ascii。

      特别注意,长度不要计算错误就好。

  3. 我有一个问题,系统里的 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL可能不止一个(比如我电脑里插着Shell U盘),那我该用哪个protocol instance呢?有办法区分吗?

    1. 具体你的目标我不太清楚,不过我觉得你可以试试查 protocol 所在的 handle,然后从 Handle 查设备类型,就可以得知是 SATA 还是 USB 这样的。但是如果你同时插上2个 U盘,这样的方法又没有办法继续区分了。另外,之前的 Microsoft 的一个 UEFI 截屏的项目是通过要求你在目标的 ESP 分区上放置特定名称的文件来实现区分的,你可以参考一下。

    1. 我看了一下 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
      );

  4. 我使用CHAR8写txt文件,但是文件出现了乱码,当时的编码为UTF-6LE,另存为时选择utf-8后再写入就不会乱码,请问怎样在程序中设置txt默认编码形式呢?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注