WPP
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 の機能が全部死ぬかもしれぬので画面出力もできなくなるよね、多分。

edk2 Hello

しばらく中断していた Mikan 本関連です。5日目くらいまで本に沿ってやっていたのですがどうも馴染めないのでやり方を変えることにした。

本をみちしるべにしつつも環境構築から勝手に行い、好きに書いていくほうがわかりやすいのでは?と思いやってみることにした。

まずは edk2 Hello world から

事前準備

edk2 をインストールして予め BaseTools をビルドしておくこと。

BaseTools に build スクリプトが含まれるのでこれをしとかないとビルドができない。

ツリー構造

今回つくる HelloPkg は最終的にこういう感じで配置しています。edk2 フォルダは git clone した時にできたフォルダになります。

  • プロジェクトルート
    • edk2 フォルダ
      • Build
      • HelloPkg << 今回作るパッケージ
        • Include
        • Library
        • Main.c
        • Hello.inf
        • HelloPkg.dec
        • HelloPkg.dsc

コード

Claude に聞くと上記の各アイテムを作れと言わるので作っていきます。

$ cd edk2
$ mkdir Include Library
$ touch Main.c Hello.inf HelloPkg.dec Hello.Pkg.dsc

各ファイルはこんな感じになります。

最初は、C ソースです。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE       ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    Print(L"Hello edk2\n");
    return EFI_SUCCESS;
}

UefiMain はなんにもしていないので極めてシンプルです。

次は、 HelloPkg.dec です。
このファイルの役割はよくわかっていませんが今の所、あまり書くことはありません。

[Defines]
DEC_SPECIFICATION = 0x00010005
PACKAGE_NAME      = HelloPkg
PACKAGE_GUID      = 25d16f8a-7fb0-467d-a13d-97c83f2a84a4
PACKAGE_VERSION   = 0.1


[Includes]
Include

[Guids]

[Protocols]

[PcdsFixedAtBuild]

DEC_SPECIFICATION は edk2 の公式ドキュメントで定義されている DEC のバージョンです。
PACKAGE_GUID は uuidgen などで生成した値を指定します。

edk2 ではちょくちょく GUID を指定する場面がありますが、その都度 uuidgen などで生成してコピペしてやります。

HelloPkg.dsc です。

[Defines]
PLATFORM_NAME               = HelloPkg
PLATFORM_GUID               = 46fcca5f-7fe2-4cd7-83e4-f0b535d60410
PLATFORM_VERSION            = 0.1
DSC_SPECIFICATION           = 0x00010005
OUTPUT_DIRECTORY            = Build/HelloPkg
SUPPORTED_ARCHITECTURES     = X64
BUILD_TARGETS               = DEBUG|RELEASE|NOOPT
SKUID_IDENTIFIER            = DEFAULT

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

# dependencies
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]
HelloPkg/Hello.inf

先程と異なり結構書きます。

どうやらこのファイルに記述したモジュール名を頼りにビルドの依存関係を解決していくようです。

その仕組みと source edksetup.sh を使って、Main.c で突然出てきた Uefi.h とかを参照し、リンクするってことをしているようです。

当初 [LibraryClasses] には UefiApplicationEntryPoint と UefiLibMdePkg の2行しか記述していなかったのですが、それだと edk2 の build コマンドでビルドエラーが出てきます。
例えば、 BaseLibMdePkg が見つからないよとか言ってきます。直してはビルドを繰り返していくうちにこういうふうになりました。結局 Mikan 本の[LibraryClasses] とほぼ同じ感じになったはず。

Hello.inf です

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = HelloApp
FILE_GUID       = 2bbb1fb0-95b5-4b59-a98c-15093d771b11
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.1
ENTRY_POINT     = UefiMain

[Sources]
Main.c

[Packages]
MdePkg/MdePkg.dec
HelloPkg/HelloPkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint

ビルドすると BASE_NAME が引き継がれて HelloApp.efi が作成されるようです。

ビルド結果は edk2/Build/HelloPkg/DEBUG_CLANGDWARF/X64 に保存されます。

保存先のフォルダは build コマンドに引数を渡さなければ以下のようになります。

DEBUG_CLANGDWARF は、edk2/Conf のtarget.txt の TARGET= と TOOL_CHAIN_TAG= を組み合わせたものになります。GCC を使っていれば DEBUG_GCC とかになります。

X64 の部分も同じように target.txt の TARGET_ARCHI= の値が来ます。

build -t CLANGDWARF とかすると target.txt の内容を無視して指定の内容で実行されます。

長くなったので QEMU での実行は次の記事にします。

NoMachine をアップデートすると Windows に接続できない場合の対策

以前から、NoMachine をアップデートするたびに Windows PC に接続できなくなる気がしていたがなんとなくアンインストール / 再インストールなどしてお茶を濁していたがいい加減面倒になってきたので少し調べて見た。

どうやら Active Directory に参加している PC だと、ドメインのポリシーによっては必要な権限が外れるっぽい。NoMachine は Windows のユーザー認証の真面目に対応しているのでかえって面倒なことが起きてしまうようだ。

VNC みたいに独自認証を突き進む場合、Windows 側の設定には左右されないのでこういう問題はおきない。

現象

他の PC から NoMachine で接続するとユーザー認証画面が出てくるがここは認証が通る。

新しいウィンドウが表示されるが、真っ白なままで接続先の画面が転送されてこない。
(このとき、接続された Windows 側では接続してきたよと通知が出るので認証は通っている。)

数秒立つと接続が切断され、リトライを続ける。

環境

Windows 11 + Active Domain に参加しているPC

対策

ローカルグループポリシーエディタで User Rights Assignment で Act as part of operating system に nx ユーザーを追加し直す。

実は他にもあるらしい、全部まとめると

  • Act as part of operating system
  • Logon as service
  • Adjust memory quatas for a process
  • Replace a process level token

の 4つのユーザー権限が必要らしい。

手順としては、

スタートから Local Security Policy を起動する

左ツリーから Local Policies > User Rights Assignment にすすむ

右ペインの Act as part of operating system をダブルクリック

恐らく nx ユーザーが追加されているはずですが、こいつを一旦削除する。
(PC に nx ユーザが追加されていないようだと NoMachine のインストールそのものが上手くいっていない。)

Add User or Group ボタンを押して nx ユーザーを追加し直す。

これで通った。

他の設定もなにか影響している可能性も考えられるが事象を記録しておく。

追記、一晩明けた今朝、接続を試みるとまた接続できなくなってしまった。うーん原因がわからない。

参考

NoMachine Forum – First time connecting session negotiation failed error

NoMachine Forum – Active directory authentication

トラックボールの音鳴り対策 – その1:ハンドクリーム

普段自宅では、Kensington Slime Blase Pro を使っているのですが、最近ボールを転がすとキーって感じの音がして気になっていました。

このトラックボールは、ボールの側面を上下に動かすとスクロールしてくれるのですがそれをすると特に顕著に音がします。

インターネットを検索するとやはりそういう問題があるようで、対策してはシリコンスプレーや食用の油をボールに塗って動作を潤滑にするっていうのがあるようです。

で、手元にシリコンスプレーがないので買いに行くのはちょっと面倒と思っていましたが、ふと思い立ちハンドクリームをボールに塗って代替にできないか試してみました。

ハンドクリームならもともと手に塗るものだし、良さそうだと。
ちょうど自宅にある食用油は、バターかオリーブオイル以外はきらせていて、その2つだたぬるぬるがいつまでも取れないとかありそうなので、まずはハンドクリームで試してみることにした。

結果、音は鳴らなくなり、割といい感じになりました。手元にあったハンドクリームはさらっとしたタイプのテクスチャだったのでそれが良かったのかもしれない。しかし、数分前に塗ったばっかりなので効果の持続性はまだわからない。

副作用としてボールの動きが恐ろしく軽くなった。考えてみると購入時はハンドクリームを塗る前ほどボールの動きが重くなかった気がするので、最初はなにか塗布されているのだと思う。

すぐに効果が切れるようなら他の方法も試してみます。

追記:2日くらいしか持ちませんでした。

LM22.1 にアップグレード

Linux Mint 22 から 22.1 にアップグレードした。

今回は、21 から 22 のときのような TimeShift のバックアップを要求されずにすんなりとアップグレードできた。

画面は全くキャプチャ取っていないが、何も難しいことはないので問題ないだろう。

os-realease と upstream-relase/lsb-release はこうなった。

$  cat /etc/os-release 
───────┬──────────────────────────────────────────────────────────────────────────────────────────────
       │ File: /etc/os-release
───────┼──────────────────────────────────────────────────────────────────────────────────────────────
   1   │ NAME="Linux Mint"
   2   │ VERSION="22.1 (Xia)"
   3   │ ID=linuxmint
   4   │ ID_LIKE="ubuntu debian"
   5   │ PRETTY_NAME="Linux Mint 22.1"
   6   │ VERSION_ID="22.1"
   7   │ HOME_URL="https://www.linuxmint.com/"
   8   │ SUPPORT_URL="https://forums.linuxmint.com/"
   9   │ BUG_REPORT_URL="http://linuxmint-troubleshooting-guide.readthedocs.io/en/latest/"
  10   │ PRIVACY_POLICY_URL="https://www.linuxmint.com/"
  11   │ VERSION_CODENAME=xia
  12   │ UBUNTU_CODENAME=noble
───────┴──────────────────────────────────────────────────────────────────────────────────────────────

$ cat /etc/upstream-release/lsb-release 
───────┬──────────────────────────────────────────────────────────────────────────────────────────────
       │ File: /etc/upstream-release/lsb-release
───────┼──────────────────────────────────────────────────────────────────────────────────────────────
   1   │ DISTRIB_ID=Ubuntu
   2   │ DISTRIB_RELEASE=24.04
   3   │ DISTRIB_CODENAME=noble
   4   │ DISTRIB_DESCRIPTION="Ubuntu Noble Numbat"
───────┴──────────────────────────────────────────────────────────────────────────────────────────────