UEFI完全入門シリーズ
Part1【環境構築】 | Part2【GOP】 | Part3【ファイル読み込み】| Part4【メモリマップ】| Part5【カーネルロード】
はじめに
前回はGOPでグラフィックスを描画しました。
今回は Simple File System Protocol を使って、ディスクからファイルを読み込みます。
ブートローダーの最も重要な仕事は「カーネルをロードすること」。そのためにはファイルシステムへのアクセスが必須です。
Simple File System Protocol
UEFIのファイルシステムアクセスは2つのプロトコルで構成されています:
- EFI_SIMPLE_FILE_SYSTEM_PROTOCOL - ボリュームを開く
- EFI_FILE_PROTOCOL - ファイル/ディレクトリ操作
アクセスの流れ
ImageHandle
↓ HandleProtocol (LOADED_IMAGE_PROTOCOL_GUID)
LoadedImage->DeviceHandle
↓ HandleProtocol (SIMPLE_FILE_SYSTEM_PROTOCOL_GUID)
FileSystem
↓ OpenVolume()
Root (EFI_FILE_PROTOCOL)
↓ Open()
File (EFI_FILE_PROTOCOL)
実装
必要なGUID
EFI_GUID LoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID SimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_GUID FileInfoGuid = EFI_FILE_INFO_ID;
ファイルシステムを取得
// 1. LoadedImage Protocolを取得
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
Status = uefi_call_wrapper(
BS->HandleProtocol, 3,
ImageHandle,
&LoadedImageProtocolGuid,
(VOID **)&LoadedImage
);
// 2. ファイルシステムプロトコルを取得
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
Status = uefi_call_wrapper(
BS->HandleProtocol, 3,
LoadedImage->DeviceHandle,
&SimpleFileSystemProtocolGuid,
(VOID **)&FileSystem
);
// 3. ルートディレクトリを開く
EFI_FILE_PROTOCOL *Root;
Status = uefi_call_wrapper(
FileSystem->OpenVolume, 2,
FileSystem,
&Root
);
LoadedImage->DeviceHandle は、このブートローダーが起動されたデバイス(ESPがあるディスク)を指します。
実行結果
=== Simple File System Protocol Demo ===
LoadedImage obtained
ImageBase: 0x6155000
ImageSize: 69632 bytes
FileSystem protocol obtained
Root directory opened
Files in root directory:
[DIR] EFI
[FILE] NvVars (11741 bytes)
ImageBaseとImageSizeは、現在実行中のUEFIアプリケーション自身のメモリ上の位置とサイズです。
ディレクトリの読み取り
EFI_FILE_PROTOCOL->Read() でディレクトリの内容を列挙できます。
UINT8 Buffer[512];
UINTN BufferSize;
while (1) {
BufferSize = sizeof(Buffer);
Status = uefi_call_wrapper(
Root->Read, 3,
Root,
&BufferSize,
Buffer
);
if (EFI_ERROR(Status) || BufferSize == 0) {
break; // エントリがなくなった
}
EFI_FILE_INFO *FileInfo = (EFI_FILE_INFO *)Buffer;
if (FileInfo->Attribute & EFI_FILE_DIRECTORY) {
Print(L" [DIR] %s\n", FileInfo->FileName);
} else {
Print(L" [FILE] %s (%d bytes)\n",
FileInfo->FileName,
FileInfo->FileSize);
}
}
EFI_FILE_INFO構造体
typedef struct {
UINT64 Size; // この構造体のサイズ
UINT64 FileSize; // ファイルサイズ
UINT64 PhysicalSize; // 物理サイズ(クラスタ境界)
EFI_TIME CreateTime; // 作成日時
EFI_TIME LastAccessTime;
EFI_TIME ModificationTime;
UINT64 Attribute; // ファイル属性
CHAR16 FileName[]; // ファイル名(可変長)
} EFI_FILE_INFO;
ファイルの作成と書き込み
ファイルを作成
EFI_FILE_PROTOCOL *File;
Status = uefi_call_wrapper(
Root->Open, 5,
Root,
&File,
L"test.txt",
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE,
0 // 属性(0 = 通常ファイル)
);
ファイルモード
| フラグ | 値 | 説明 |
|---|---|---|
EFI_FILE_MODE_READ |
0x01 | 読み取り |
EFI_FILE_MODE_WRITE |
0x02 | 書き込み |
EFI_FILE_MODE_CREATE |
0x8000000000000000 | 存在しなければ作成 |
ファイルに書き込み
CHAR8 TestData[] = "Hello from UEFI!\nThis is a test file.\n";
UINTN DataSize = sizeof(TestData) - 1; // NULL終端を除く
Status = uefi_call_wrapper(
File->Write, 3,
File,
&DataSize,
TestData
);
Print(L"Written %d bytes to test.txt\n", DataSize);
// ファイルを閉じる
uefi_call_wrapper(File->Close, 1, File);
実行結果:
--- Creating test file ---
Written 38 bytes to test.txt
ファイルの読み込み
ファイルを開く
Status = uefi_call_wrapper(
Root->Open, 5,
Root,
&File,
L"test.txt",
EFI_FILE_MODE_READ,
0
);
ファイルサイズを取得
EFI_FILE_INFO *Info;
UINT8 InfoBuffer[256];
UINTN InfoSize = sizeof(InfoBuffer);
Status = uefi_call_wrapper(
File->GetInfo, 4,
File,
&FileInfoGuid,
&InfoSize,
InfoBuffer
);
Info = (EFI_FILE_INFO *)InfoBuffer;
Print(L"File size: %d bytes\n", Info->FileSize);
ファイル内容を読み込み
CHAR8 ReadBuffer[256];
UINTN ReadSize = sizeof(ReadBuffer) - 1;
Status = uefi_call_wrapper(
File->Read, 3,
File,
&ReadSize,
ReadBuffer
);
ReadBuffer[ReadSize] = '\0';
Print(L"Content: %a\n", ReadBuffer); // %a は ASCII文字列用
uefi_call_wrapper(File->Close, 1, File);
実行結果:
--- Reading test file ---
File size: 38 bytes
Content: Hello from UEFI!
This is a test file.
完全なサンプルコード
#include <efi.h>
#include <efilib.h>
EFI_GUID LoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID SimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_GUID FileInfoGuid = EFI_FILE_INFO_ID;
EFI_STATUS
EFIAPI
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
EFI_FILE_PROTOCOL *Root;
EFI_FILE_PROTOCOL *File;
InitializeLib(ImageHandle, SystemTable);
ST->ConOut->ClearScreen(ST->ConOut);
Print(L"=== Simple File System Protocol Demo ===\n\n");
// LoadedImage Protocol
Status = uefi_call_wrapper(
BS->HandleProtocol, 3,
ImageHandle, &LoadedImageProtocolGuid, (VOID **)&LoadedImage
);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Failed to get LoadedImage: %r\n", Status);
goto done;
}
Print(L"LoadedImage obtained\n");
Print(L" ImageBase: 0x%lx\n", LoadedImage->ImageBase);
Print(L" ImageSize: %d bytes\n\n", LoadedImage->ImageSize);
// FileSystem Protocol
Status = uefi_call_wrapper(
BS->HandleProtocol, 3,
LoadedImage->DeviceHandle,
&SimpleFileSystemProtocolGuid, (VOID **)&FileSystem
);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Failed to get FileSystem: %r\n", Status);
goto done;
}
// Root Directory
Status = uefi_call_wrapper(
FileSystem->OpenVolume, 2, FileSystem, &Root
);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Failed to open root: %r\n", Status);
goto done;
}
// ディレクトリを列挙
Print(L"Files in root directory:\n");
UINT8 Buffer[512];
UINTN BufferSize;
while (1) {
BufferSize = sizeof(Buffer);
Status = uefi_call_wrapper(Root->Read, 3, Root, &BufferSize, Buffer);
if (EFI_ERROR(Status) || BufferSize == 0) break;
EFI_FILE_INFO *FileInfo = (EFI_FILE_INFO *)Buffer;
if (FileInfo->Attribute & EFI_FILE_DIRECTORY) {
Print(L" [DIR] %s\n", FileInfo->FileName);
} else {
Print(L" [FILE] %s (%d bytes)\n",
FileInfo->FileName, FileInfo->FileSize);
}
}
// ファイルを作成して書き込み
uefi_call_wrapper(Root->Close, 1, Root);
uefi_call_wrapper(FileSystem->OpenVolume, 2, FileSystem, &Root);
Print(L"\n--- Creating test file ---\n");
Status = uefi_call_wrapper(
Root->Open, 5, Root, &File, L"test.txt",
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0
);
if (!EFI_ERROR(Status)) {
CHAR8 TestData[] = "Hello from UEFI!\nThis is a test file.\n";
UINTN DataSize = sizeof(TestData) - 1;
uefi_call_wrapper(File->Write, 3, File, &DataSize, TestData);
Print(L"Written %d bytes to test.txt\n", DataSize);
uefi_call_wrapper(File->Close, 1, File);
}
// ファイルを読み込み
Print(L"\n--- Reading test file ---\n");
Status = uefi_call_wrapper(
Root->Open, 5, Root, &File, L"test.txt", EFI_FILE_MODE_READ, 0
);
if (!EFI_ERROR(Status)) {
CHAR8 ReadBuffer[256];
UINTN ReadSize = sizeof(ReadBuffer) - 1;
uefi_call_wrapper(File->Read, 3, File, &ReadSize, ReadBuffer);
ReadBuffer[ReadSize] = '\0';
Print(L"Content: %a\n", ReadBuffer);
uefi_call_wrapper(File->Close, 1, File);
}
uefi_call_wrapper(Root->Close, 1, Root);
done:
Print(L"\nPress any key to exit...\n");
UINTN Index;
uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &Index);
return EFI_SUCCESS;
}
EFI_FILE_PROTOCOLの主要関数
| 関数 | 説明 |
|---|---|
Open |
ファイル/ディレクトリを開く |
Close |
ファイルを閉じる |
Delete |
ファイルを削除 |
Read |
読み込み |
Write |
書き込み |
GetPosition |
読み書き位置を取得 |
SetPosition |
読み書き位置を設定 |
GetInfo |
ファイル情報を取得 |
SetInfo |
ファイル情報を設定 |
Flush |
バッファをフラッシュ |
ファイルパスについて
UEFIでは、パス区切りにバックスラッシュ \ を使用します(Windowsと同じ)。
// サブディレクトリのファイルを開く
Root->Open(Root, &File, L"\\EFI\\BOOT\\config.txt", EFI_FILE_MODE_READ, 0);
ルートからの相対パスとして指定します。
まとめ
今回学んだこと:
- Simple File System Protocol でボリュームにアクセス
- EFI_FILE_PROTOCOL でファイル/ディレクトリを操作
-
ディレクトリの列挙 -
Read()でEFI_FILE_INFOを取得 -
ファイルの作成と書き込み -
Open()+Write() -
ファイルの読み込み -
Open()+Read()
次回はメモリマップを取得して、カーネルをロードするためのメモリ管理を学びます。ExitBootServices()を呼んでOSに制御を移すまであと少し!