edk2 ファイル入出力

Mikan 本に沿いながら勝手に作っていくシリーズの続きです。

前回までで文字列を表示する Hello world ができたので、ファイルの入出力をやっていくのがいいだろうということでやっていきます。

UEFI 起動の段階でファイルの入出力ができれば、カーネルをロードするための設定を書き込んだファイルを読み取ることなんかができるようになるはず。

Mikan 本では、メモリマップを作成して memfile? だったかの一時ファイルに書き込んでいるが、デバッグ用途でファイルに書き出したいこともあるかもしれないのでファイル IO をやっておいて悪いことはないだろう。

ファイル構成

  • edk2 フォルダ
    • Hello2Pkg フォルダ
      • Hello2.inf
      • Hello2Pkg.dec
      • Hello2Pkg.dsc
      • Main.c
  • mnt フォルダ
  • disk.img

最終的なツリー構造は上のような感じになる、disk.img は qeum-img で作成したイメージファイルここにビルドしたファイルをコピーして qemu で起動するようにする。

Main.c はファイル読み書きの処理が入るのでほとんど原型をとどめていない。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>

// A: GetInfo() で仕様
//#include <Guid/FileInfo.h>
//#include <Guid/FileSystemInfo.h>


EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
    EFI_FILE_PROTOCOL *Root;
    EFI_FILE_PROTOCOL *File;
    CHAR16 *FileName = L"\\test.txt";

    // Load Protocol
    Status = SystemTable->BootServices->LocateProtocol(
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        (VOID**)&FileSystem
    );
    if (EFI_ERROR(Status)) {
        Print(L"faile: locate file system protocol\n");
        return Status;
    }

    Status = FileSystem->OpenVolume(FileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"faile: open root directory\n");
        return Status;
    }

/* B: GetInfo() で書き込み可能か調べる。デバッグで仕様した。
    EFI_FILE_SYSTEM_INFO *FSInfo;
    UINTN FSInfoSize = 0;
    Status = Root->GetInfo(
        Root,
        &gEfiFileSystemInfoGuid,
        &FSInfoSize,
        NULL
    );
    if (Status == EFI_BUFFER_TOO_SMALL) {
        FSInfo = AllocatePool(FSInfoSize);
        Status = Root->GetInfo(
            Root,
            &gEfiFileSystemInfoGuid,
            &FSInfoSize,
            FSInfo
        );
        if (!EFI_ERROR(Status)) {
            Print(L"Volume Label: %s\n", FSInfo->VolumeLabel);
            Print(L"Read Only: %d\n", FSInfo->ReadOnly);
            Print(L"Free Space: %d\n", FSInfo->FreeSpace);
        }
        FreePool(FSInfo);
    }
*/


    CHAR16 *WriteData = L"Hello file system\n";
    UINTN WriteSize = StrLen(WriteData) * sizeof(CHAR16);

    // create 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"fail: to create file\n");
        Root->Close(Root);
        return Status;
    }

    Status = File->Write(File, &WriteSize, WriteData);
    if (EFI_ERROR(Status)) {
        Print(L"fail: write to file\n");
        File->Close(File);
        Root->Close(Root);
        return Status;
    }

    File->Close(File);

    // open for read
    Status = Root->Open(
        Root,
        &File,
        FileName,
        EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to open file for reading: %r\n", Status);
        Root->Close(Root);
        return Status;
    }

    // read
    CHAR16 buf[128];
    UINTN ReadSize = sizeof(buf);
    Status = File->Read(File, &ReadSize, buf);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        Root->Close(Root);
        return Status;
    }

    Print(L"Read from file: %s\n",  buf);

    File->Close(File);
    Root->Close(Root);

    return EFI_SUCCESS;
}

コード中のコメントA:、B:、部分はプログラム作成中にファイル書き込み失敗していた際に調査した残骸。こういうふうにすると開いたハンドル?が書き込み可能か属性を見れるらしい。

Hello2.inf はこんな感じになる。[Guids] の2行は Main.c と同じGetInfo で gEfiFileSystemInfoGuid を使うための記述です。ハイライト行が HelloPkg から追加されたファイルIO の為に追加した部分になります。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello2
FILE_GUID       = BA807105-6F81-4BB6-ACE5-38FB4480C2A5
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Main.c

[Guids]
# gEfiFileInfoGuid
# gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello2Pkg/Hello2Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello2Pkg.dec の方は変わらない。(Inlucde フォルダを使っていないので [Includes] の行は削除してしまった。)

C_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello2Pkg
PACKAGE_GUID        = 6FA2CBDB-9033-491F-B17C-58CB2EB69CD9
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello2Pkg.dsc ファイルの方も変更はありません。

[Defines]
PLATFORM_NAME           = Hello2Pkg
PLATFORM_GUID           = 43B8BF63-3328-4A67-83C5-57EA4FAF43DA
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello2Pkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS           = DEBUG | RELEASE | NOOPT
SKUID_IDENTIFIER        = DEFAULT

[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf


BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf

[Components]
Hello2Pkg/Hello2.inf

ビルドは、こんな感じ。

上手く動くと、test.txt がイメージファイルのルートディレクトリに作成され、QEMU の画面に test.txt から読み込んだ Hello file system\n が表示されるはず。

test.txt が存在する状態で実行してもファイルを作り直しているのかファイルのタイムスタンプは更新されて問題なく動作している。

$ cd edk2
$ build -p Hello2Pkg/Hello2Pkg.dsc
$ cd ..

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello2Pkg/DEBUG_CLANGDWARF/X64/Hello2.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

# QEMU で実行
$ qemu-system-x86_64 \
		-drive if=pflash,format=raw,readonly=on,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_CODE.fd \
		-drive if=pflash,format=raw,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_VARS.fd \
		-drive format=raw,file=disk.img

次はメモリマップの作成とカーネルのロードにすすんで行きたい。

Mikan 本では、カーネルロード前に、UEFI のサービスを停止するので、カーネル起動後は UEFI の機能は一切使えなくなる。ロード時に画面のフレームバッファを直接渡して、画面の描画ができるようになる。ビデオカードのドライバがない以上そうするしかないんだよな。きっと。
この状態では文字列表示さえ自前で作る必要があるのでなかなか OS って大変だ。