WPP
LM22: Mozc の辞書ツールにキーボードショートカットを割り当てて、ユーザ辞書を追加した

最近、日本語変換で積極的に単語登録するようにしている。特別な方法ではなくよくある「よろ」で「よろしくお願いします」に変換するような短縮を登録している。

で、辞書に単語登録するのが面倒な理由の一つが辞書ツールや登録ツールを呼び出すのに IME のアイコンから右クリックしてメニューを選択するというアクションがいちいち面倒なためだと思う。
この面倒さは、Linux のデスクトップでも Windows でもほとんど変わらない。しかの最終的に変換できてしまうものだからなんとなく後回しにしがち。

でだ、辞書ツールのコマンドラインがわからないので Archi Linux の Wiki を調べるとユーザー辞書を追加したほうがいいよ的なことが書いてあるのであわせて実施してみた

辞書ツールにショートカット割り当て

LM22 (Linux Mint) のCinnamon で行っているので Ubuntu とか他のデスクトップ環境では若干異なると思うがほぼ同じようなことはできるはずだ。

辞書ツールのコマンド

Mozc – ArchWiki によると設定ツール、辞書ツール、単語登録というのは mozc_tool という一つのファイルで引数を切り替えることで使い分けているようです。

実際のコマンドはこんな感じらしい。

設定ツール:
/usr/lib/mozc/mozc_tool –mode=config_dialog

辞書ツール
/usr/lib/mozc/mozc_tool –mode=dictionary_tool

単語登録
/usr/lib/mozc/mozc_tool –mode=word_register_dialog

今回は、ラウンチャアイコンは作成せずに、辞書ツールに直接キーボード・ショートカットを割り当てることにした。

  1. 設定アプリからキーボードを開く
  2. ショートカットタブを選択する
  3. カスタムショートカットを選択
  4. [Add custom shortcut] ボタンをクリック
  5. Name: を適当に決める
  6. Command: に /usr/lib/mozc/mozc_tool –mode=dictionary_tool を指定する
  7. 登録して、ダイアログを閉じる。
  8. 下段のキーボードバインディングで unassigned の行をダブルクリックする。
  9. 割り当てたいキーボードショートカットを押す。

自分は、Shift + Ctrl + D をショートカットに割り当てましたが、このへんは好みで変えてほしい。
また、個人的な好みでダブリ登録したくないので辞書ツールを起動しているが、単語登録でもいいと思う。

ユーザー辞書登録

ネット上では Mozc UT ってのを使うっていう記事がたくさん見つかりますがこれ、Mozc そのものをビルドしなきゃいけないんですね。ちょっと面倒なので、他の方法でいくことにした。

Mozc UT Dictionaries

utuhiro78/merge-ut-dictionaries: Merge multiple Mozc UT dictionaries into one and modify the costs.

だいぶ古い記事になるのですが、

Google日本語入力 強化辞書の構成を変えてみた。 – 黒MacBookが好き の辞書をインポートして使っている。

Mozcに辞書を追加した: arcadia’s blog のやり方が参考になるかもしれない。

考えるのも面倒だったので、自分の場合はそのまま辞書ツールでインポートした。

英和と和英辞書はインポートしなくてもよかったかもしれない。

Remmina が mdns で名前解決できるようにした

最近は、いちいち VNC クライアントをインストールがめんどうなので LM22 にはじめから入っている Remmina を 使っている。

がしかし、Remmina だと mdns による名前解決が使えなくて困っていた。

Remmina 自身は flatpak でインストールしていたが、D-Bus のパーミッション (socket=system-bus、socket=session-bus) を許可しても現象に変化がなかった。

Claude に聞いてみると /etc/nsswitch.conf を書き換えろってことでやってみた。

変更前:
hosts: files mdns4_minimal [NOTFOUND=return] dns myhostname mymachines

変更後:
hosts: files mdns4_minimal mdns4 [NOTFOUND=return] dns myhostname mymachines

[NOTFOUND] の前に mdns4 を追加すると Remmina でも名前解決できるようになった。

edk2 メモリマップをファイルに保存 #2

前回は、メモリマップをテキストファイルに保存しました。

ReadFile() と WriteFile() は使っていないので別ファイルに逃がすことにします。プログラムの動作は変わりません。それに伴い Include フォルダと Library フォルダが増えています。

ファイル構成

  • edk2 フォルダ
    • Hello7Pkg フォルダ
      • Hello7.inf
      • Hello7Pkg.dec
      • Hello7Pkg.dsc
      • Hello7.c
    • Include フォルダ
      • FileLib.h
    • Library フォルダ
      • FileLib.c
      • FileLib.inf
  • mnt フォルダ
  • disk.img

Hello6.c は前回までファイル読み書き用の関数は後で使う予定なのでそのまま残してメモリマップまわりの処理を追加していく。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Library/PrintLib.h>

#define MEMORY_MAP_BUFFER_PADDING 2

typedef struct {
    UINTN BufferSize;
    VOID* Buffer;
    UINTN MapSize;
    UINTN MapKey;
    UINTN DescriptorSize;
    UINT32 DescriptorVersion;
} MemoryMap;

VOID
InitializeMemoryMap(
    OUT MemoryMap* map
)
{
    if (map == NULL) return;

    map->BufferSize = 0;
    map->Buffer = NULL;
    map->MapSize = 0;
    map->MapKey = 0;
    map->DescriptorSize = 0;
    map->DescriptorVersion =0;
}

VOID
FreeMemoryMap(
    IN OUT MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) return;

    FreePool(map->Buffer);
    map->Buffer = NULL;
    map->BufferSize = 0;
}

const CHAR16*
GetMemoryTypeString(
    IN EFI_MEMORY_TYPE type
)
{
    switch (type) {
        case EfiReservedMemoryType:     return L"Reserved";
        case EfiLoaderCode:             return L"LoaderCode";
        case EfiLoaderData:             return L"LoaderData";
        case EfiBootServicesCode:       return L"BootCode";
        case EfiBootServicesData:       return L"BootData";
        case EfiRuntimeServicesCode:    return L"RuntimeCode";
        case EfiRuntimeServicesData:    return L"RuntimeData";
        case EfiConventionalMemory:     return L"Conventional";
        case EfiUnusableMemory:         return L"Unusable";
        case EfiACPIReclaimMemory:      return L"ACPIReclaim";
        case EfiACPIMemoryNVS:          return L"ACPINVS";
        case EfiMemoryMappedIO:         return L"MMIO";
        case EfiMemoryMappedIOPortSpace: return L"MMIOPort";
        case EfiPalCode:                return L"PalCode";
        default:                        return L"Unknown";
    }
}


VOID
PrintMemoryMapEntry(
    IN UINTN index,
    IN CONST EFI_MEMORY_DESCRIPTOR* desc
)
{
    Print(L"%03d: Type=%s  Phys=0x%016lx-0x%016lx  Pages=%ld  Attr=0x%016lx\n",
        index,
        GetMemoryTypeString(desc->Type),
        desc->PhysicalStart,
        desc->PhysicalStart + desc->NumberOfPages * EFI_PAGE_SIZE - 1,
        desc->NumberOfPages,
        desc->Attribute
    );
}

VOID
PrintMemoryMap(
    IN CONST  MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) {
        Print(L"Error: invalid memory map\n");
        return;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    Print(L"Memory Map: %d entries\n", Entries);
    Print(L"DescriptorSize: %d\n",map->DescriptorSize);

    for (UINTN i = 0; i < Entries; i++) {
        PrintMemoryMapEntry(i, Desc);
        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }
}

EFI_STATUS
SaveMemoryMap(
    IN CONST MemoryMap* map,
    IN CONST EFI_FILE_PROTOCOL* root,
    IN CONST CHAR16* fileName
)
{
    if (map == NULL || root == NULL || fileName ==NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL* File;
    UINTN WriteSize;

    Status = root->Open(
        (EFI_FILE_PROTOCOL*)root,
        &File,
        (CHAR16*)fileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create memory map file: %r\n", Status);
        return Status;
    }

    // CSV header
    CHAR8* Header = "Index, Type, PhysicalStart, NumberOfPages, Attribute\r\n";
    WriteSize = AsciiStrLen(Header);
    Status = File->Write(File, &WriteSize, Header);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write header%r\n", Status);
        File->Close(File);
        return Status;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    for (UINTN i = 0; i < Entries; i++) {
        
        // setup string
        CHAR8 Line[256];

        WriteSize = AsciiSPrint(Line, sizeof(Line),
            "%d, %d, %-ls, 0x%08lx, %ld, 0x%lx\r\n",
            i,
            Desc->Type,
            GetMemoryTypeString(Desc->Type),
            Desc->PhysicalStart,
            Desc->NumberOfPages,
            Desc->Attribute
        );

        // write string to file
        Status = File->Write(File, &WriteSize, Line);
        if (EFI_ERROR(Status)) {
            Print(L"fail: to write entry: %d%r\n", Status);
            File->Close(File);
            return Status;
        }

        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }

    // add Summary Info.
    CHAR8 Summary[256];
    WriteSize = AsciiSPrint(Summary, sizeof(Summary),
        "\r\nMemory Map Summary\r\nEntries: %d\r\nDescriptor Size: %d\r\n",
        Entries,
        map->DescriptorSize
    );

    Status = File->Write(File, &WriteSize, Summary);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write MemMap summary%r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
GetMemoryMap(MemoryMap* map) {
    EFI_STATUS Status;

    if (map == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 初期サイズ 0 でメモリマップの大きさを取得
    map->MapSize = 0;
    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error: Unexpected GetMemory Status: %r\n", Status);
        return Status;
    }

    map->BufferSize = map->MapSize + MEMORY_MAP_BUFFER_PADDING * map->DescriptorSize;

    if (map->Buffer != NULL) {
        FreePool(map->Buffer);
    }

    map->Buffer = AllocatePool(map->BufferSize);
    if (map->Buffer == NULL) {
        Print(L"fail: to allocate memory for map buffer: %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (EFI_ERROR(Status)) {
        FreePool(map->Buffer);
        map->Buffer = NULL;
        Print(L"fail: to get memory map: %r\n", Status);
        return Status;
    }

    return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    MemoryMap Map;

    InitializeMemoryMap(&Map);

    Status = GetMemoryMap(&Map);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to get memory map %r\n", Status);
        return Status;
    }

    PrintMemoryMap(&Map);

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

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

    Status = SaveMemoryMap(&Map, Root, L"\\memmap.csv");
    if (EFI_ERROR(Status)) {
        Print(L"%r\n");
        Root->Close(Root);
        FreeMemoryMap(&Map);
        return Status;
    }

    Print(L"\nMemMap ha benn saved to \\memmap.csv\n");


    Root->Close(Root);
    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

WriteFile() とReadFile() を削除しました。

Hello7.inf は FileLib を追加していますが、実際には呼び出していないのでコメントアウトしています。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello7
FILE_GUID       = 21E44BD4-AC20-4D08-9265-87CA1AA79BCC
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello7.c

[Guids]
# gEfiFileInfoGuid
# gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello7Pkg/Hello7Pkg.dec

[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
MemoryAllocationLib
PrintLib
# FileLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello7Pkg.dec にも FileLib.h をインクルードすることを知らせています。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello7Pkg
PACKAGE_GUID        = 092DE508-84C6-4FA3-884C-1C5E702CB38E
PACKAGE_VERSION     = 0.0.1

[Includes]
Include

[Guids]

[LibraryClasses]
FileLib|Include/FileLib.h

[Protocols]

[PcdsFixedAtBuild]

Hello7Pkg.dsc ファイルにも FileLib を使うことを知らせています。

[Defines]
PLATFORM_NAME           = Hello7Pkg
PLATFORM_GUID           = DA50351D-D7B7-45DD-911C-B54BAB54F702
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello7Pkg
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

FileLib|Hello7Pkg/Library/FileLib/FileLib.inf

[Components]
Hello7Pkg/Hello7.inf

追加した Include フォルダには、FileLib.h を置きます。まあ普通の C のヘッダーです。

#ifndef FILE_LIB_H_
#define FILE_LIB_H_

#include <Uefi.h>
#include <Protocol/SimpleFileSystem.h>

EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL* Root,
    IN CONST CHAR16* FileName,
    IN CONST CHAR8* Data
    );

EFI_STATUS
ReadFile(
    IN CONST EFI_FILE_PROTOCOL* Root,
    IN CONST CHAR16* FileName,
    OUT CHAR8* Buffer,
    IN UINTN BufferSize,
    OUT UINTN* ReadSize
    );

#endif

Library フォルダには 2 つファイルを追加します。まずは FileLib.c

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Protocol/SimpleFileSystem.h>
#include "FileLib.h"

EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL *Root,
    IN CONST CHAR16 *FileName,
    IN CONST CHAR8 *Data
)
{
    if (Root == NULL || FileName == NULL || Data == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        (EFI_FILE_PROTOCOL*)Root,
        &File,
        (CHAR16*)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");
        return Status;
    }

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

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR8 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    if (Root == NULL || FileName == NULL || Buffer == NULL || 
        BufferSize == 0 || ReadSize == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

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

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

次は Library/FileLib.inc です。 Claude がいうには、こっちにも Hello7Pkg/Hello7Pkg.inf を入れろとあるが、いらないようなきもする

[Defines]
INF_VERSION                    = 0x00010005
BASE_NAME                      = FileLib
FILE_GUID                      = F0022AC1-CEE2-495D-AA5E-5C620186A083
MODULE_TYPE                    = BASE
VERSION_STRING                 = 1.0
LIBRARY_CLASS                  = FileLib

[Sources]
FileLib.c

[Packages]
MdePkg/MdePkg.dec

[LibraryClasses]
UefiLib
PrintLib

ビルドはいつもと同じ感じです。

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

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

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

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello7Pkg/DEBUG_CLANGDWARF/X64/Hello7.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

ソースもきれいになったことだし、カーネルロードに取り掛かろう

edk2 メモリマップをファイルに保存

前回は、メモリマップを画面にダンプしたので今回は、メモリマップをテキストファイルに保存します。

Mikan 本にならい、メモリマップ用の構造体を作り、UEFI コンソールにメモリマップを表示してみます。

ファイル構成

  • edk2 フォルダ
    • Hello6Pkg フォルダ
      • Hello6.inf
      • Hello6Pkg.dec
      • Hello6Pkg.dsc
      • Hello6.c
  • mnt フォルダ
  • disk.img

Hello6.c は前回までファイル読み書き用の関数は後で使う予定なのでそのまま残してメモリマップまわりの処理を追加していく。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Library/PrintLib.h>

#define MEMORY_MAP_BUFFER_PADDING 2

typedef struct {
    UINTN BufferSize;
    VOID* Buffer;
    UINTN MapSize;
    UINTN MapKey;
    UINTN DescriptorSize;
    UINT32 DescriptorVersion;
} MemoryMap;

VOID
InitializeMemoryMap(
    OUT MemoryMap* map
)
{
    if (map == NULL) return;

    map->BufferSize = 0;
    map->Buffer = NULL;
    map->MapSize = 0;
    map->MapKey = 0;
    map->DescriptorSize = 0;
    map->DescriptorVersion =0;
}

VOID
FreeMemoryMap(
    IN OUT MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) return;

    FreePool(map->Buffer);
    map->Buffer = NULL;
    map->BufferSize = 0;
}

const CHAR16*
GetMemoryTypeString(
    IN EFI_MEMORY_TYPE type
)
{
    switch (type) {
        case EfiReservedMemoryType:     return L"Reserved";
        case EfiLoaderCode:             return L"LoaderCode";
        case EfiLoaderData:             return L"LoaderData";
        case EfiBootServicesCode:       return L"BootCode";
        case EfiBootServicesData:       return L"BootData";
        case EfiRuntimeServicesCode:    return L"RuntimeCode";
        case EfiRuntimeServicesData:    return L"RuntimeData";
        case EfiConventionalMemory:     return L"Conventional";
        case EfiUnusableMemory:         return L"Unusable";
        case EfiACPIReclaimMemory:      return L"ACPIReclaim";
        case EfiACPIMemoryNVS:          return L"ACPINVS";
        case EfiMemoryMappedIO:         return L"MMIO";
        case EfiMemoryMappedIOPortSpace: return L"MMIOPort";
        case EfiPalCode:                return L"PalCode";
        default:                        return L"Unknown";
    }
}


VOID
PrintMemoryMapEntry(
    IN UINTN index,
    IN CONST EFI_MEMORY_DESCRIPTOR* desc
)
{
    Print(L"%03d: Type=%s  Phys=0x%016lx-0x%016lx  Pages=%ld  Attr=0x%016lx\n",
        index,
        GetMemoryTypeString(desc->Type),
        desc->PhysicalStart,
        desc->PhysicalStart + desc->NumberOfPages * EFI_PAGE_SIZE - 1,
        desc->NumberOfPages,
        desc->Attribute
    );
}

VOID
PrintMemoryMap(
    IN CONST  MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) {
        Print(L"Error: invalid memory map\n");
        return;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    Print(L"Memory Map: %d entries\n", Entries);
    Print(L"DescriptorSize: %d\n",map->DescriptorSize);

    for (UINTN i = 0; i < Entries; i++) {
        PrintMemoryMapEntry(i, Desc);
        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }
}

EFI_STATUS
SaveMemoryMap(
    IN CONST MemoryMap* map,
    IN CONST EFI_FILE_PROTOCOL* root,
    IN CONST CHAR16* fileName
)
{
    if (map == NULL || root == NULL || fileName ==NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL* File;
    UINTN WriteSize;

    Status = root->Open(
        (EFI_FILE_PROTOCOL*)root,
        &File,
        (CHAR16*)fileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create memory map file: %r\n", Status);
        return Status;
    }

    // CSV header
    CHAR8* Header = "Index, Type, PhysicalStart, NumberOfPages, Attribute\r\n";
    WriteSize = AsciiStrLen(Header);
    Status = File->Write(File, &WriteSize, Header);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write header%r\n", Status);
        File->Close(File);
        return Status;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    for (UINTN i = 0; i < Entries; i++) {
        
        // setup string
        CHAR8 Line[256];

        WriteSize = AsciiSPrint(Line, sizeof(Line),
            "%d, %d, %-ls, 0x%08lx, %ld, 0x%lx\r\n",
            i,
            Desc->Type,
            GetMemoryTypeString(Desc->Type),
            Desc->PhysicalStart,
            Desc->NumberOfPages,
            Desc->Attribute
        );

        // write string to file
        Status = File->Write(File, &WriteSize, Line);
        if (EFI_ERROR(Status)) {
            Print(L"fail: to write entry: %d%r\n", Status);
            File->Close(File);
            return Status;
        }

        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }

    // add Summary Info.
    CHAR8 Summary[256];
    WriteSize = AsciiSPrint(Summary, sizeof(Summary),
        "\r\nMemory Map Summary\r\nEntries: %d\r\nDescriptor Size: %d\r\n",
        Entries,
        map->DescriptorSize
    );

    Status = File->Write(File, &WriteSize, Summary);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write MemMap summary%r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
GetMemoryMap(MemoryMap* map) {
    EFI_STATUS Status;

    if (map == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 初期サイズ 0 でメモリマップの大きさを取得
    map->MapSize = 0;
    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error: Unexpected GetMemory Status: %r\n", Status);
        return Status;
    }

    map->BufferSize = map->MapSize + MEMORY_MAP_BUFFER_PADDING * map->DescriptorSize;

    if (map->Buffer != NULL) {
        FreePool(map->Buffer);
    }

    map->Buffer = AllocatePool(map->BufferSize);
    if (map->Buffer == NULL) {
        Print(L"fail: to allocate memory for map buffer: %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (EFI_ERROR(Status)) {
        FreePool(map->Buffer);
        map->Buffer = NULL;
        Print(L"fail: to get memory map: %r\n", Status);
        return Status;
    }

    return EFI_SUCCESS;
}



EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL *Root,
    IN CONST CHAR16 *FileName,
    IN CONST CHAR8 *Data
)
{
    if (Root == NULL || FileName == NULL || Data == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        (EFI_FILE_PROTOCOL*)Root,
        &File,
        (CHAR16*)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");
        return Status;
    }

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

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR8 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    if (Root == NULL || FileName == NULL || Buffer == NULL || 
        BufferSize == 0 || ReadSize == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

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

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    MemoryMap Map;

    InitializeMemoryMap(&Map);

    Status = GetMemoryMap(&Map);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to get memory map %r\n", Status);
        return Status;
    }

    PrintMemoryMap(&Map);

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

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

    Status = SaveMemoryMap(&Map, Root, L"\\memmap.csv");
    if (EFI_ERROR(Status)) {
        Print(L"%r\n");
        Root->Close(Root);
        FreeMemoryMap(&Map);
        return Status;
    }

    Print(L"\nMemMap ha benn saved to \\memmap.csv\n");


    Root->Close(Root);
    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

Hello6.inf に、 PrintLib を追加した。PrintLib の中に AsciiSPrint() が含まれているらしい。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello6
FILE_GUID       = A522DAB7-30AE-4F3D-85D1-92D1C24BEF30
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello6.c

[Guids]
gEfiFileInfoGuid
gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello6Pkg/Hello6Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib
PrintLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello6Pkg.dec はほとんどわからない。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello6Pkg
PACKAGE_GUID        = A1D6D642-D269-44DF-B225-5FFA7AB8E958
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello6Pkg.dsc ファイルは変更はありません。ということは、以前のものは、PrintLib 行はいらなかったのかもしれない。

[Defines]
PLATFORM_NAME           = Hello6Pkg
PLATFORM_GUID           = DA50351D-D7B7-45DD-911C-B54BAB54F702
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello6Pkg
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]
Hello6Pkg/Hello6.inf

ビルドはいつもと同じ感じです。

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

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

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

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello6Pkg/DEBUG_CLANGDWARF/X64/Hello6.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

次こそカーネルのロードにに進める。

memmap.csv は fs0: の ルートに作成される。上手く行けばこんな感じになる。QEMU を同じように起動すれば恐らく同じようなメモリマップになるはず。

Index, Type, PhysicalStart, NumberOfPages, Attribute
0, 7, Conventional, 0x00000000, 160, 0xF
1, 7, Conventional, 0x00100000, 1792, 0xF
2, 10, ACPINVS, 0x00800000, 8, 0xF
3, 7, Conventional, 0x00808000, 3, 0xF
4, 10, ACPINVS, 0x0080B000, 1, 0xF
5, 7, Conventional, 0x0080C000, 5, 0xF
6, 10, ACPINVS, 0x00811000, 239, 0xF
7, 4, BootData, 0x00900000, 3712, 0xF
8, 7, Conventional, 0x01780000, 9196, 0xF
9, 4, BootData, 0x03B6C000, 32, 0xF
10, 7, Conventional, 0x03B8C000, 9325, 0xF
11, 1, LoaderCode, 0x05FF9000, 227, 0xF
12, 7, Conventional, 0x060DC000, 90, 0xF
13, 4, BootData, 0x06136000, 31, 0xF
14, 7, Conventional, 0x06155000, 15, 0xF
15, 1, LoaderCode, 0x06164000, 3, 0xF
16, 7, Conventional, 0x06167000, 3, 0xF
17, 4, BootData, 0x0616A000, 1569, 0xF
18, 3, BootCode, 0x0678B000, 180, 0xF
19, 4, BootData, 0x0683F000, 108, 0xF
20, 3, BootCode, 0x068AB000, 30, 0xF
21, 4, BootData, 0x068C9000, 1, 0xF
22, 3, BootCode, 0x068CA000, 22, 0xF
23, 4, BootData, 0x068E0000, 2, 0xF
24, 3, BootCode, 0x068E2000, 21, 0xF
25, 4, BootData, 0x068F7000, 4, 0xF
26, 3, BootCode, 0x068FB000, 30, 0xF
27, 4, BootData, 0x06919000, 5, 0xF
28, 3, BootCode, 0x0691E000, 47, 0xF
29, 4, BootData, 0x0694D000, 6, 0xF
30, 3, BootCode, 0x06953000, 40, 0xF
31, 4, BootData, 0x0697B000, 1, 0xF
32, 3, BootCode, 0x0697C000, 21, 0xF
33, 4, BootData, 0x06991000, 8, 0xF
34, 3, BootCode, 0x06999000, 19, 0xF
35, 4, BootData, 0x069AC000, 1, 0xF
36, 3, BootCode, 0x069AD000, 43, 0xF
37, 4, BootData, 0x069D8000, 5, 0xF
38, 3, BootCode, 0x069DD000, 35, 0xF
39, 4, BootData, 0x06A00000, 514, 0xF
40, 3, BootCode, 0x06C02000, 3, 0xF
41, 4, BootData, 0x06C05000, 3, 0xF
42, 3, BootCode, 0x06C08000, 11, 0xF
43, 4, BootData, 0x06C13000, 1, 0xF
44, 3, BootCode, 0x06C14000, 4, 0xF
45, 4, BootData, 0x06C18000, 6, 0xF
46, 3, BootCode, 0x06C1E000, 47, 0xF
47, 4, BootData, 0x06C4D000, 9, 0xF
48, 3, BootCode, 0x06C56000, 33, 0xF
49, 4, BootData, 0x06C77000, 4, 0xF
50, 3, BootCode, 0x06C7B000, 28, 0xF
51, 4, BootData, 0x06C97000, 5, 0xF
52, 3, BootCode, 0x06C9C000, 7, 0xF
53, 4, BootData, 0x06CA3000, 3, 0xF
54, 3, BootCode, 0x06CA6000, 36, 0xF
55, 4, BootData, 0x06CCA000, 3, 0xF
56, 3, BootCode, 0x06CCD000, 15, 0xF
57, 4, BootData, 0x06CDC000, 2, 0xF
58, 3, BootCode, 0x06CDE000, 3, 0xF
59, 4, BootData, 0x06CE1000, 3, 0xF
60, 3, BootCode, 0x06CE4000, 38, 0xF
61, 4, BootData, 0x06D0A000, 5, 0xF
62, 3, BootCode, 0x06D0F000, 2, 0xF
63, 4, BootData, 0x06D11000, 1042, 0xF
64, 3, BootCode, 0x07123000, 6, 0xF
65, 4, BootData, 0x07129000, 2, 0xF
66, 3, BootCode, 0x0712B000, 10, 0xF
67, 4, BootData, 0x07135000, 6, 0xF
68, 3, BootCode, 0x0713B000, 23, 0xF
69, 4, BootData, 0x07152000, 923, 0xF
70, 6, RuntimeData, 0x074ED000, 256, 0x800000000000000F
71, 5, RuntimeCode, 0x075ED000, 256, 0x800000000000000F
72, 0, Reserved, 0x076ED000, 128, 0xF
73, 9, ACPIReclaim, 0x0776D000, 18, 0xF
74, 10, ACPINVS, 0x0777F000, 128, 0xF
75, 4, BootData, 0x077FF000, 1537, 0xF
76, 7, Conventional, 0x07E00000, 96, 0xF
77, 4, BootData, 0x07E60000, 32, 0xF
78, 3, BootCode, 0x07E80000, 47, 0xF
79, 0, Reserved, 0x07EAF000, 4, 0xF
80, 10, ACPINVS, 0x07EB3000, 2, 0xF
81, 3, BootCode, 0x07EB5000, 1, 0xF
82, 4, BootData, 0x07EB6000, 17, 0xF
83, 3, BootCode, 0x07EC7000, 45, 0xF
84, 6, RuntimeData, 0x07EF4000, 132, 0x800000000000000F
85, 10, ACPINVS, 0x07F78000, 136, 0xF
86, 11, MMIO, 0xFFC00000, 1024, 0x8000000000000001
87, 0, Reserved, 0xFD00000000, 3145728, 0x0

Memory Map Summary
Entries: 88
Descriptor Size: 48

edk2 メモリマップの表示

前回は、ファイル IO のリファクタリングをしたので今回は、メモリマップの表示をしていきます。

Mikan 本にならい、メモリマップ用の構造体を作り、UEFI コンソールにメモリマップを表示してみます。

ファイル構成

  • edk2 フォルダ
    • Hello5Pkg フォルダ
      • Hello5.inf
      • Hello5Pkg.dec
      • Hello5Pkg.dsc
      • Hello5.c
  • mnt フォルダ
  • disk.img

Hello5.c は前回までファイル読み書き用の関数は後で使う予定なのでそのまま残してメモリマップまわりの処理を追加していく。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>



typedef struct __MemoryMap {
    UINTN BufferSize;
    VOID* Buffer;
    UINTN MapSize;
    UINTN MapKey;
    UINTN DescriptorSize;
    UINT32 DescriptorVersion;
} MemoryMap;

VOID
InitializeMemoryMap(
    OUT MemoryMap* map
)
{
    if (map == NULL) return;

    map->BufferSize = 0;
    map->Buffer = NULL;
    map->MapSize = 0;
    map->MapKey = 0;
    map->DescriptorSize = 0;
    map->DescriptorVersion =0;
}

VOID
FreeMemoryMap(
    IN OUT MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) return;

    FreePool(map->Buffer);
    map->Buffer = NULL;
    map->BufferSize = 0;
}

const CHAR16*
GetMemoryTypeString(
    IN EFI_MEMORY_TYPE type
)
{
    switch (type) {
        case EfiReservedMemoryType:     return L"Reserved";
        case EfiLoaderCode:             return L"LoaderCode";
        case EfiLoaderData:             return L"LoaderData";
        case EfiBootServicesCode:       return L"BootCode";
        case EfiBootServicesData:       return L"BootData";
        case EfiRuntimeServicesCode:    return L"RuntimeCode";
        case EfiRuntimeServicesData:    return L"RuntimeData";
        case EfiConventionalMemory:     return L"Conventional";
        case EfiUnusableMemory:         return L"Unusable";
        case EfiACPIReclaimMemory:      return L"ACPIReclaim";
        case EfiACPIMemoryNVS:          return L"ACPINVS";
        case EfiMemoryMappedIO:         return L"MMIO";
        case EfiMemoryMappedIOPortSpace: return L"MMIOPort";
        case EfiPalCode:                return L"PalCode";
        default:                        return L"Unknown";
    }
}


VOID
PrintMemoryMapEntry(
    IN UINTN index,
    IN CONST EFI_MEMORY_DESCRIPTOR* desc
)
{
    Print(L"%03d: Type=%s  Phys=0x%016lx-0x%016lx  Pages=%ld  Attr=0x%016lx\n",
        index,
        GetMemoryTypeString(desc->Type),
        desc->PhysicalStart,
        desc->PhysicalStart + desc->NumberOfPages * 4096 - 1,
        desc->NumberOfPages,
        desc->Attribute
    );
}

VOID
PrintMemoryMap(
    IN CONST  MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) {
        Print(L"Error: invalid memory map\n");
        return;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    Print(L"Memory Map: %d entries\n", Entries);
    Print(L"DescriptorSize: %d\n",map->DescriptorSize);

    for (UINTN i = 0; i < Entries; i++) {
        PrintMemoryMapEntry(i, Desc);
        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }
}

EFI_STATUS
GetMemoryMap(MemoryMap* map) {
    EFI_STATUS Status;

    if (map == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 初期サイズ 0 でメモリマップの大きさを取得
    map->MapSize = 0;
    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error: Unexpected GetMemory Status: %r\n", Status);
        return Status;
    }

    map->BufferSize = map->MapSize + 2 * map->DescriptorSize;

    if (map->Buffer != NULL) {
        FreePool(map->Buffer);
    }

    map->Buffer = AllocatePool(map->BufferSize);
    if (map->Buffer == NULL) {
        Print(L"fail: to allocate memory for map buffer: %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (EFI_ERROR(Status)) {
        FreePool(map->Buffer);
        map->Buffer = NULL;
        Print(L"fail: to get memory map: %r\n", Status);
        return Status;
    }

    return EFI_SUCCESS;
}


EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL *Root,
    IN CONST CHAR16 *FileName,
    IN CONST CHAR16 *Data
)
{
    if (Root == NULL || FileName == NULL || Data == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        (EFI_FILE_PROTOCOL*)Root,
        &File,
        (CHAR16*)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");
        return Status;
    }

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

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR16 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    if (Root == NULL || FileName == NULL || Buffer == NULL || 
        BufferSize == 0 || ReadSize == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

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

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    MemoryMap Map;

    InitializeMemoryMap(&Map);

    Status = GetMemoryMap(&Map);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to get memory map %r\n", Status);
        return Status;
    }

    PrintMemoryMap(&Map);

    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

ところどころ本と異なるところがあるが、これは、Claude との壁打ちの結果修正していった結果です。

関数の引数と内部で宣言した変数とで大文字・小文字を気分で分けちゃっていますが、どういう規約が一般的なのかあとで調べて修正すると思います。
個人的には、何らかの方法で関数内の変数と外から来た引数がパット見でわかったほうが読みやすいとかんじているのですが、世間的には非常識なんでしょうか?

Hello5.inf は特段変化はない。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello5
FILE_GUID       = 33EA5F56-E88C-4926-9E64-0A61E753DE4D
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello5.c

[Guids]
gEfiFileInfoGuid
gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello5Pkg/Hello5Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello5Pkg.dec の方も同じくほとんどわからない。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello5Pkg
PACKAGE_GUID        = 64F39E0B-37D3-40F9-85A5-26387029161E
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

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

[Defines]
PLATFORM_NAME           = Hello5Pkg
PLATFORM_GUID           = 1AF3A6F8-9F8E-4FF0-809E-0140EB1C7FCA
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello5Pkg
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]
Hello5Pkg/Hello5.inf

ビルドも同じこんな感じです。

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

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

$ cd edk2
$ build -p Hello5Pkg/Hello5Pkg.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 って大変だ。

QEMU で自動起動するには、 /EFI/BOOT/BOOTX64.EFI とフォルダ名、ファイル名ともにすべて大文字にする必要があった。なんでこんな簡単なことに気づかなかったのか・・・・。

ちなみにこのプログラムは(以前のものもそうかも) UEFI で自動起動しないほうがいいです。
というのも最後 return で抜けてしまっているので自動起動に成功すると、そのままリセットがかかってしまいます。

UEFI シェルで実行するとこんな感じの表示がされるはずです。
手動実行するには、下のようにします。

>fs0:\EFI\BOOT\hello5.efi

次は、メモリマップをファイル保存するのか、本の少し先まででは、ファイルに保存したメモリマップは使っていなそう?なきがするので飛ばしてもいいのかもしれない。

edk2 ファイル入出力 – その2

前回、edk2 のファイル入出力をやったがちょっと見づらいので書き込みと読み込みを別関数に切り出すことにした。

Hello4Pkg という別のパッケージにしてやってみる。

ファイル構成

  • edk2
    • Hello4Pkg フォルダ
      • Hello4.c
      • Hello4.inf
      • Hello4Pkg.dec
      • Hello4Pkg.dsc
  • disk.img

Hello4.c はこんな感じ。ハイライト行が切り出した「書き込み」、「読み込み」処理。

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

EFI_STATUS
WriteFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    IN CHAR16 *Data
)
{
    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    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");
        return Status;
    }

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

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR16 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

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

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

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;
    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;
    }

    CHAR16 *WriteData = L"Hello file system\n";

    Status = WriteFile(Root, FileName, WriteData);
    if (EFI_ERROR(Status)) {
        Root->Close(Root);
        return Status;
    }

    // read
    CHAR16 buf[128];
    UINTN ReadSize = sizeof(buf);
    Status = ReadFile(Root, FileName, buf, ReadSize, &ReadSize);
    if (EFI_ERROR(Status)) {
        Root->Close(Root);
        return Status;
    }

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

    Root->Close(Root);

    return EFI_SUCCESS;
}

Hello4Pkg.dec ファイルはほとんど変わらない。PACKAGE_NAME は何でもいいのですが、混乱しないように Hello4Pkg に揃えておきます。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello4Pkg
PACKAGE_GUID        = 08F29A4C-BB2E-4417-B8E9-3854083B9CC0
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello4Pkg.dsc も Hello4Pkg に変える以外は変更なし。

[Defines]
PLATFORM_NAME           = Hello4Pkg
PLATFORM_GUID           = 43B8BF63-3328-4A67-83C5-57EA4FAF43DA
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello4Pkg
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]
Hello4Pkg/Hello4.inf

最後にビルドと実行

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

# イメージにファイルコピー
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello4Pkg/DEBUG_CLANGDWARF/X64/Hello4.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

# 実行
$ 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

# qemu で自動起動しないとき
> fs0:\EFI\BOOT\hello4.efi

それにしても、hello4.efi を BOOTX64.efi というファイル名にリネームしても自動起動しないのはなぜなんだろう。まだまだ謎は多い。

2025-2-12 追記:
> BOOTX64.EFI が正解。イメージディスクは FAT32 なので一度作成してしまったあとで UEFIシェルでは、BOOTX64.efi からBOOTX.EFI にリネームできない。(FAT の仕様では、ファイル名の大文字/小文字の区別はない)

次はメモリマップの作成にかかる。カーネルを読み込むまでにはまだ道のりが遠い。

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 って大変だ。

スマホをキーボード・マウスの代わりにするアプリ Remote Touchpad

数週間もするときっと忘れてしまうのでメモ。

LM22 で SIP ソフトフォンを探していると 『Remote TouchPad』 というアプリを見つけた。どうやらスマフォからの入力をマウスやキーボードに見立てて PC に渡してくれるものらしい。

対応 OSは、 Win、Linux に対応している。

インストールして起動すると、QR コードが表示され、スマホでスキャンするとブラウザ経由で PC に接続する仕組みらしい。
つまり、PC 側にしかインストールが必要ないということだ。

PC にキーボードってそもそもいらないんじゃない?って以前考えたことがあったが、それを実現するソフトだ。

動作は若干もっさりしているが、緊急で一時しのぎで使うには問題なさそうだ。

Remoto Touchpad を動かす PC で nomachine を使って更に先のどこかにつないでいる場合、nomachine で転送しているリモートの画面では日本語の入力が上手く動かない気がする。
その場合、nomachine の転送元で Remote Toouchpad を動かせばいいのかもしれないが未検証なのでわからない。

参考

Github: Unrud/remote-touchpad: Control mouse and keyboard from a smartphone

雑談: メルカリの認証ってクソすぎん?

メルカリのアプリ版をパスキー認証にしてししばらく経ちますが、昨日くらいから PC でアクセスする Web 版がログイン不可になりました。

パスワード変更すると状態がリセットするらしいが、自分の状況ではメルカリのビットコインを買っているので問い合わせ対応になるらしい。あーめんど。

でしょうがないのでいやだけど Google アカウントと連携することで PC からもログイン出来るようになった。のはいいが副作用として SMS での2段階認証が必須になった。

そもそも、SMS での 2FA を避ける為にパスキー認証にしたのに Web 版を利用すると結局 2FA にフォールバックするっていう仕様はゴミですか?

大量の候補をだらだら見るときは PC のがやっぱり見やすいんよ。なんとかならんか。

edk2 Hello その2です

前回記事で、edk2 のハローワールドを作りました。今回は、それを qemu で実行していきます。

自分が理解する限り、UEFI の仕様は FAT パーティションに /EFI/BOOT/ というフォルダを作成してそこに起動ファイルを置く必要があるようです。

準備

edk2 のOvmfPkg をビルドしておく必要があります。 build -p OvmfPkg/OVmfPkg.dsc とかでビルドできます。

ディスクイメージの準備

ディスクイメージを作っていきます。ビルドした .efi ファイルは、
edk2/Build/HelloPkg/DEBUG_CLANGDWARF/X64/HelloApp.efi
にあるものとします。

$ qemu-img create -f raw disk.img 128M
$ mkfs.fat -n 'xxos' -s 2 -f 2 -R 32 -F 32 disk.img
; -s 2: 2 セクタ /クラスタ
; -f 2: FAT を2個用意する
; -R 32: 32 セクタを FAT で使用する
; -F 32: FAT32 を使用する

; マウントポイントを作成
$ mkdir ./mnt

$ sudo mount -o loop,uid$(id -u),gid=$(id -u) disk.img ./mnt
$ mkdir ./mnt/EFI/BOOT
$ cp ./ edk2/Build/HelloPkg/DEBUG_CLANGDWARF/X64/HelloApp.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

実行は本と同じですね。

$	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
		# -net none -nographic

OVMF_CODE.fd と OVMF_VARS.fd は長いので環境変数に逃したほうがいいかも。

上は
fs0:
cd EFI/BOOT
HelloApp.efi

で実行した様子

QEMU にマウスとキーボードを掴まれたときは Ctrl + Alt + G で抜けることが出来る。

確かめていないが HelloApp.efi を適切な名前にすれば自動起動するはずだと思う。

次は、カーネルを作成したいので、まずは edk2 でのファイルの読み込みをやってみよう。 Mikan 本のブートローダーはメモリマップを作成して、ちゃんと空きメモリにカーネルロードしている。

Mikan本 派生でないっぽい Uefi や edk2 のカーネルローダーをネットでみるとやっぱり何かしら空きメモリを探す処理をしているので必要だよな。じゃないと uefi の機能が全部死ぬかもしれぬので画面出力もできなくなるよね、多分。