お疲れ様です。
先日、突貫で取り急ぎ動かしてみました。
具体的なフローは以下にまとめています。
本当に全くのド素人なので
勉強した内容をまとめていこうと思います。
#0章 UEFI の前に EFI?
UEFI はハードを制御するファームと OS 間の通信の規格です。
有識者の指導はコチラとコチラ
ただ、UEFI の前身は EFI らしいので、
EFI の規格書を読んだ方が良いと思われる。
興味がある方はコチラをクリック
EFI driver
要はドライバーだが、制御用の口はハンドル(protocol) と繋がっています。
. An EFI driver is an executable EFI image that installs a variety of protocols of various handles to accomplish its job.
EFI protocol
protocol とは EFI driver への pointer と data 構造 or API のブロックです。
protocol に設定すべき情報(仕様)は各 protocol の仕様で要確認です。
ただ、最低でも GUID を設定してあげる必要があります。
この GUID は "やりたいこと" に紐づけて命名されており、
handl data base から GUID で検索し、各種 Driver を動かしていきます。
つまづきポイント :ハンドルって?基礎知識は コチラ
ハンドルは変数(ポインタ)というイメージですが、
EFI の規格書の中では 1 つ以上の protocol の集合を指します。
前述の handle data base は、ファーム側で保持しており、
その中身はハンドルと protocol で構成されています。
Handles are a collection of one or more protocols,
and protocols are data structures that are named by a GUID.
ファーム側に使いたいドライバーのポインタと、ドライバに放り込みたいデータをまとめてinput し、
それを受け取ったファームは自身の database から該当のドライバを検索し、制御するイメージだと思います。
以上を前提に以下の図を見るとイメージが付きやすいのではないでしょうか?
ただし、実際は HandleProtocol() もしくは OpenProtocol() を経由して
各種 Device driver にリンクしていくそうです.
The protocols that a device handle supports are discovered
through the EFI_BOOT_SERVICES.HandleProtocol() Boot Service or
the EFI_BOOT_SERVICES.OpenProtocol() Boot Service.
具体的に呼び出せる Device driver の一覧です。
EFI_LOADED_IMAGE_PROTOCOL | EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL | EFI_DEVICE_PATH_PROTOCOL |
EFI_DRIVER_BINDING_PROTOCOL | EFI_DRIVER_FAMILY_OVERRIDE_PROTOCOL | EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL |
EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL | EFI_DRIVER_DIAGNOSTICS2_PROTOCOL | EFI_COMPONENT_NAME2_PROTOCOL |
EFI_SIMPLE_TEXT_INPUT_PROTOCOL | EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL | EFI_SIMPLE_POINTER_PROTOCOL |
EFI_SERIAL_IO_PROTOCOL | EFI_LOAD_FILE_PROTOCOL | EFI_LOAD_FILE2_PROTOCOL |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL | EFI_FILE_PROTOCOL | EFI_DISK_IO_PROTOCOL |
EFI_BLOCK_IO_PROTOCOL | EFI_BLOCK_IO2_PROTOCOL | EFI_UNICODE_COLLATION_PROTOCOL |
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL | EFI_PCI_IO_PROTOCOL | EFI_USB_IO_PROTOCOL |
EFI_SIMPLE_NETWORK_PROTOCOL | EFI_PXE_BASE_CODE_PROTOCOL | EFI_BIS_PROTOCOL |
EFI_DEBUG_SUPPORT_PROTOCOL | EFI_DEBUGPORT_PROTOCOL | EFI_DECOMPRESS_PROTOCOL |
EFI_EBC_PROTOCOL | EFI_GRAPHICS_OUTPUT_PROTOCOL | EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL |
EFI_EXT_SCSI_PASS_THRU_PROTOCOL | EFI_USB2_HC_PROTOCOL | EFI_AUTHENTICATION_INFO_PROTOCOL |
EFI_DEVICE_PATH_UTILITIES_PROTOCOL | EFI_DEVICE_PATH_TO_TEXT_PROTOCOL | EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL |
EFI_EDID_DISCOVERED_PROTOCOL | EFI_EDID_ACTIVE_PROTOCOL | EFI_EDID_OVERRIDE_PROTOCOL |
EFI_ISCSI_INITIATOR_NAME_PROTOCOL | EFI_TAPE_IO_PROTOCOL | EFI_MANAGED_NETWORK_PROTOCOL |
EFI_ARP_SERVICE_BINDING_PROTOCOL | EFI_ARP_PROTOCOL | EFI_DHCP4_SERVICE_BINDING_PROTOCOL |
EFI_DHCP4_PROTOCOL | EFI_TCP4_SERVICE_BINDING_PROTOCOL | EFI_TCP4_PROTOCOL |
EFI_IP4_SERVICE_BINDING_PROTOCOL | EFI_IP4_PROTOCOL | EFI_IP4_CONFIG_PROTOCOL |
EFI_IP4_CONFIG2_PROTOCOL | EFI_UDP4_SERVICE_BINDING_PROTOCOL | EFI_UDP4_PROTOCOL |
EFI_MTFTP4_SERVICE_BINDING_PROTOCOL | EFI_MTFTP4_PROTOCOL | EFI_HASH_PROTOCOL |
EFI_HASH_SERVICE_BINDING_PROTOCOL | EFI_SD_MMC_PASS_THRU_PROTOCOL |
HandleProtocol() or OpenProtocol() ってどっちが良いの?
The HandleProtocol() function is still available for use by old EFI applications and drivers.
However, all new applications and drivers should use EFI_BOOT_SERVICES.OpenProtocol() in place of HandleProtocol().
(・_・D フムフム。 OpenProtocol() を使えって事ですね。了解。
OpenProtocol を少し見てみた。
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface OPTIONAL,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle,
IN UINT32 Attributes
);
item | description | in japanease |
---|---|---|
Handle | The handle for the protocol interface that is being opened. | どこに繋がりたいのか |
Protocol | The published unique identifier of the protocol. It is the caller’s responsibility to pass in a valid GUID.See “Wired For Management Baseline” for a description of valid GUID values. | GUID を入力。入力すべき GUID はコチラを参照 |
Interface | Supplies the address where a pointer to the corresponding Protocol Interface is returned. NULL will be returned in *Interface if a structure is not associated with Protocol. This parameter is optional, and will be ignored if Attributes is EFI_OPEN_PROTOCOL_TEST_PROTOCOL. | 対応する protocol IF のポインタのポインタを返します。非対応であれば Null を返し、EFI_OPEN_PROTOCOL_TEST_PROTOCOL を入れると、この項目は無視されます。 |
AgentHandle | The handle of the agent that is opening the protocol interface specified by Protocol and Interface. For agents that follow the UEFI Driver Model, this parameter is the handle that contains the EFI_DRIVER_BINDING_PROTOCOL instance that is produced by the UEFI driver that is opening the protocol interface. For UEFI applications, this is the image handle of the UEFI application that is opening the protocol interface. For applications that use HandleProtocol() to open a protocol interface, this parameter is the image handle of the EFI firmware. | プロトコルとインターフェースで指定されたプロトコルインターフェースを開いているエージェントのハンドル。UEFIドライバーモデルに従うエージェントの場合、このパラメーターは、プロトコルインターフェイスを開いているUEFIドライバーによって生成されるEFI_DRIVER_BINDING_PROTOCOLインスタンスを含むハンドルです。UEFIアプリケーションの場合、これはプロトコルインターフェイスを開いているUEFIアプリケーションのイメージハンドルです。HandleProtocol()を使用してプロトコルインターフェイスを開くアプリケーションの場合、このパラメーターはEFIファームウェアのイメージハンドルです。 |
ControllerHandle | If the agent that is opening a protocol is a driver that follows the UEFI Driver Model, then this parameter is the controller handle that requires the protocol interface. If the agent does not follow the UEFI Driver Model, then this parameter is optional and may be NULL. | プロトコルを開いているエージェントがUEFIドライバーモデルに従うドライバーである場合、このパラメーターはプロトコルインターフェイスを必要とするコントローラーハンドルです。 エージェントがUEFIドライバーモデルに準拠していない場合、このパラメーターはオプションであり、NULLになる可能性があります。 |
Attributes | The open mode of the protocol interface specified by Handle and Protocol. See "Related Definitions" for the list of legal attributes. | ハンドル/プロトコルによって指定されたプロトコル IF の Open mode 設定 |
ではでは、AKpirion が os をいじるのだから、
AKpirios と命名して色々試してみる。
##その他 雑多な MEMO
###EFI_SYSTEM_TABLE って何?
EFI BOOT SERVICE table, EFI RUNTIME SERVICE table , Protocol services を含んだデータ構造。
~ table は良いとして、Protocol servies について補足します。
例えば openprotocol を走らせる場合、何を open しますか?
Grobal uniqe id として何をするかを ID 化してまとめたものが、
Protocol services です。コレ
因みに Protocol は GUID とか IN / OUT をまとめたデータ構造のことです。
紛らわしい(笑)
###image handle って何?
Supports the Loaded Image Protocol。つまり、EFI_LOADED_IMAGE_PROTOCOL で load した handle。
###Handle database って何?
Protocol には GUID / IN / OUT で色々放り込みます、空データとか。
電源投入後 EFI が起動する際に初期値が色々生成されるので、
全部丸々 protocol に突っ込む必要はないようにしてくれるのが Handle database です。
じゃあ、何を何処までやってくれるの??分からん(笑)
###OpenVolume()
The OpenVolume() function opens a volume, and returns a file handle to the volume’s root directory.
This handle is used to perform all other file I/O operations.
The volume remains open until all the file handles to it are closed.
OpenVolume() は(Root の)ボリュームの file を Open します。そして、file のポインタを返します。
そのポインタは、その他全ての file IO 操作で使われます。また closed されない限り
Open 状態は維持されます。
###Open()
The Open() function opens the file or directory referred to by "FileName" relative
to the location of "This" and returns a "NewHandle".
ファイル or ディレクトリを開きます。
"FileName" を検索して、そのポインタを返します。
###gBS->LoadImage()
device driver は gBS->LoadImage() により work memory に展開され、
gBS->StartImage() により呼び出すことが出来ます。
A device driver is loaded into memory with the gBS->LoadImage() Boot Service
and invoked with the gBS->StartImage() Boot Service.
また gBS->LoadImage() は自動で EFI_LOADED_IMAGE_PROTOCOL を埋め込んだ image handle を生成します。
The gBS->LoadImage() seervce automoatically creates an image handle and installs the
EFI_LOADED_IMAGE_PROTOCOL onto the image handle.
EFI_LOADED_IMAGE_PROTOCOL には device driver のメモリの何処に格納されてて、どこで実行されたか記載されています。
The EFI_LOADED_IMAGE_PROTOCOL describes the location where the device driver
was loaded and the location in system memory where the device driver was placed.
###そもそも memory map の作成は必須なの?
コチラ を読んでみると面白いです。
そもそも OS loader は OSPM への memory map の報告が must だそうです。
また書籍では"色々邪魔だから ExitBootServices() を call する" っとありますが、
作者の気分なのか、OS とはそういうものなのか、良く分かりませんでした。
しかし、この記事にはboot service から run-time へ移行する際に
ExitBootService() を実行するとあります。おもろいです。
memory map はどうやって作る?
typedef
EFI_STATUS
(EFIAPI *EFI_GET_MEMORY_MAP) (
IN OUT UINTN *MemoryMapSize,
OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
OUT UINTN *MapKey,
OUT UINTN *DescriptorSize,
OUT UINT32 *DescriptorVersion
);
メモリマップをコピーするための容量を input すると、
そのサイズ分のメモリマップ情報のポインタと key を返す。
###main.c では何やってるの?その1
EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root) {
EFI_STATUS status;
EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
/*Can be used on any image handle to obtain information about the loaded image*/
/*work memory に展開されたアプリケーションへのプロトコル*/
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;
/*EFI_SIMPLE_FILE_SYSTEM_PROTOCOL is used to open a device volume and return
an EFI_FILE_PROTOCOL that provides interfaces to access files on a device volume.
The only function of this interface is to open a handle to the root directory
of the file system on the volume.
つまり、root を開くための interface として fs を使いますって事ね。*/
status = gBS->OpenProtocol(
image_handle, /*efi_main() のハンドル(ポインタ)*/
&gEfiLoadedImageProtocolGuid,
(VOID**)&loaded_image, /*efi_main() へアクセスしたいから loaded_image を入力*/
image_handle, /*openprotocol の call は誰がしてる? => efi_main().*/
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
if (EFI_ERROR(status)) {
return status;
}
status = gBS->OpenProtocol(
loaded_image->DeviceHandle, /*EFI image のロード元のデバイスハンドル => root??*/
&gEfiSimpleFileSystemProtocolGuid,
(VOID**)&fs, /*root への IF を open.root そのものを open したわけではない*/
image_handle, /*openprotocol の call は誰がしてる? => efi_main().*/
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
if (EFI_ERROR(status)) {
return status;
}
return fs->OpenVolume(fs, root);
/*root への IF である fs を放り込むと root を open.
これは root のボリュームを確保したとも言い換えられる。
第二引数の root は output. ココに root へのパスのポインタが入っている*/
}
#1章 Hello world
Hello.c をコンパイルするところから入った。
$HOME/osbook/day01/c に格納されている hello.c を覗いてみる。
取り急ぎ、コード内の Hello world を適当に変えて、
ファイル名も適当に変えて動くか確認。
##before
$ cd $HOME/osbook/day01/c
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c hello.c
$ lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o
$ $HOME/osbook/devenv/run_qemu.sh hello.efi
##after
$ cd $HOME/osbook/day01/c
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c try_hello.c
$ lld-link /subsystem:efi_application /entry:EfiMain /out:try_hello.efi try_hello.o
$ $HOME/osbook/devenv/run_qemu.sh try_hello.efi
基本的に hello.c を try_hello.c に変えただけ。
もちろん動いた。
$ cd $HOME/osbook/day01/c
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c try_hello.c
$ lld-link /subsystem:efi_application /entry:EfiMain /out:try_hello.efi try_hello.o
$ $HOME/osbook/devenv/run_qemu.sh try_hello.efi
とりあえず、やってることを言葉にしてみた。
- $HOME/osbook/day01/c に移動
- try_hello.c をオブジェクトファイル(機械語)に変換。祝 try_hello.o 誕生。
- オブジェクトファイルを何かと紐づけて efi にしてるらしい。また今度調べる(予定)
- ディスクイメージに efi を埋め込んで起動するコマンド
取り急ぎコメントしてみた。
typedef unsigned short CHAR16;
typedef unsigned long long EFI_STATUS;
typedef void *EFI_HANDLE;
//◆ Clear the path for text output ◆
// Main
// ↓
// EFI_SYSTEM_TABLE
// ↓
// EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
// ↓
// EFI_TEXT_STRING
/*Details of EFI_TEXT_STRING-----*/
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
typedef void (*EFI_TEXT_STRING) (
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
CHAR16 *String);
/*EFI simple text output protocol*/
typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
void *dummy;
EFI_TEXT_STRING OutputString;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
/*EFI system table*/
typedef struct {
char dummy[60];
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
} EFI_SYSTEM_TABLE;
/*Main*/
EFI_STATUS EfiMain(EFI_HANDLE ImageHandle,EFI_SYSTEM_TABLE SystemTable){
SystemTable.ConOut->OutputString(SystemTable.ConOut, L"Hello! AKPdddeee\n");
while (1);
return 0;
}
// KEY POINT
// --"Details of EFI_TEXT_STRING" is using Functional pointer
// to determine EFI_TEXT_STRING as function.
// --"EFI system table" use dummy[60].
// because there are 60byte in front of *ConOut.
// --"Main" needs argument "SystemTable" to initialize it.
// initializtion is needed to work well.
構造体/ポインタについて勉強しなおしました。
・C言語の絵本
・新・明解C言語 ポインタ完全攻略
#2章 EDK2 で Hello world
linux のコンパイラを卒業して EKD2 へ進学。
まずは使用したコマンドを以下に整理
$ cd $HOME/workspace/mikanos/ #$HOME/workspace/mikanos/に移動
$ git checkout osbook_day02a #osbook_day02a にワークツリーを変更
$ cd ~/edk2 #$HOME/edk2 に移動
$ ln -s $HOME/workspace/mikanos/MikanLoaderPkg ./ #$HOME/edk2 の下に MikanLoaderPkg へ直行できるフォルダを生成
$ source edksetup.sh #edk2 でコンパイルするための設定ファイルを call
こちらに大変お世話になった。
https://osdev-jp.readthedocs.io/ja/latest/2017/create-uefi-app-with-edk2.html
まぁ、もともと動くソースをいじるのだから、
.dsc , dec , inf とかはファイル名こそ変えど、
中身はコピーで宜しくないか?っということで丸コピー。
具体的には workspace も MikanLoaderPkg に入っている Main.c , MikanLoaderPkg.des , MicanLoarderPkg.dsc, Loarder.inf を、以下にあるように AKpiriosLoaderPkg として新調した作業フォルダに名前を変えてコピー。
$HOME
↓
edk2 → MikanLoarderPkg
→ AKpiriosLoaderPkg
#File names
#before
Main.c , MikanLoaderPkg.des , MikanLoarderPkg.dsc, Loarder.inf
#after
Main.c , AKpiriosLoaderPkg.des , AKpiriosLoarderPkg.dsc, Loarder.inf
一応、des/dsc/inf ファイルの中身も Mikan ** ~ 的な記述を全て、
AKpirios ** ~ 的な感じに書き換えて対応した。
後は以下のソースを build した。
#include <Uefi.h>
#include <Library/UefiLib.h>
EFI_STATUS EFIAPI UefiMain(
EFI_HANDLE image_handle,
EFI_SYSTEM_TABLE *system_table)
{
Print(L"Hello, AKpirios World!\n");
while (1);
return EFI_SUCCESS;
}
###驚くほど記述が減った。なんで?
//edk2/MdePkg/Library/UefiLib/UefiLibPrint.c
/*
Source code key point
POINT1:EDK environment is configured to using EFI_SYSTEM_TABLE.
So user must declare EFI_SYSTEM_TABLE on user program
to call all definition of header files.
POINT2:Print function contains InternalPrint function.
POINT3:InternalPrint's OutputString is the main body of the text display
*/
/** @file
Mde UEFI library API implementation.
Print to StdErr or ConOut defined in EFI_SYSTEM_TABLE <= POINT1
Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
//InternalPrint//
UINTN
InternalPrint (
IN CONST CHAR16 *Format,
IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *Console,
IN VA_LIST Marker
)
{
EFI_STATUS Status;
UINTN Return;
CHAR16 *Buffer;
UINTN BufferSize;
ASSERT (Format != NULL);
ASSERT (((UINTN)Format & BIT0) == 0);
ASSERT (Console != NULL);
BufferSize = (PcdGet32 (PcdUefiLibMaxPrintBufferSize) + 1) * sizeof (CHAR16);
Buffer = (CHAR16 *)AllocatePool (BufferSize);
ASSERT (Buffer != NULL);
Return = UnicodeVSPrint (Buffer, BufferSize, Format, Marker);
if ((Console != NULL) && (Return > 0)) {
//
// To be extra safe make sure Console has been initialized
//
Status = Console->OutputString (Console, Buffer); //<= POINT3
if (EFI_ERROR (Status)) {
Return = 0;
}
}
FreePool (Buffer);
return Return;
}
//Print//
UINTN
EFIAPI
Print (
IN CONST CHAR16 *Format,
...
)
{
VA_LIST Marker;
UINTN Return;
VA_START (Marker, Format);
Return = InternalPrint (Format, gST->ConOut, Marker); //<= POINT2
VA_END (Marker);
return Return;
}
蛇足ですけど、 while(1) の役割について。
これがないと UefiMain は EFI_SUCCESS を return して終了となる。
そのため、黒い画面から変な設定画面に飛ぶ(笑)。
無限ループを置くことで、return EFI_SUCCESS に行かずに、
その場にとどまっているから、hello world の画面のまま静止することが出来ます。
はい、横道にそれました。
QEMU を起動するときは以下のコマンドで対応しました。
$HOME/osbook/devenv/run_qemu.sh $HOME/edk2/Build/AKpiriosLoaderX64/DEBUG_CLANG38/X64/Loader.efi
結果はこんな感じ。
##osbook_day02b を自分の環境で動かす
まずは、AKpiriosLoaderPkg でビルドして
動作するまで持っていかないと話が始まらない。
そんな中、出会った有識者のノウハウ。
https://zenn.dev/nnabeyang/scraps/fd9caa55515852
記事にあるように Loader.inf をいじらないと
osbook_day02b は上手く動かないので注意
Main.c の中身を議論する前に有識者の見解を探す。。あった。
https://mir3636.hatenablog.com/
##わくわく Memory Map 取得のソースコード
その前に基礎知識。ボリュームって何だっけ?
=>コチラ or コチラ
本体の UefiMain を覗いてイメージを作ってみる。
EFI_STATUS EFIAPI UefiMain(
EFI_HANDLE image_handle, //UefiMain のポインタ
EFI_SYSTEM_TABLE* system_table) //EFI_SYSTEM_TABLE のポインタ
{
CHAR8 memmap_buf[4096 * 4];//メモリマップ用に workmemory に用意する領域
struct MemoryMap memmap = {sizeof(memmap_buf), memmap_buf, 0, 0, 0, 0};//メモリマップ生成用のパラメータ一括生成
GetMemoryMap(&memmap);//指定の先頭アドレスを start として memmap_buf 分だけ使用してメモリマップを workmemory にメモ
EFI_FILE_PROTOCOL* root_dir;
OpenRootDir(image_handle, &root_dir);//root 領域を Open!! file IO access を可能にする
EFI_FILE_PROTOCOL* memmap_file; /*EFI_FILE_PROTOCOLをインスタンス。ココにメモリマップを書き込む*/
/*PROTOCOL は色々な関数を call も出来るが自由帳としてもイメージ*/
root_dir->Open(root_dir, /*open した ROOT を基準に...*/
&memmap_file, /*↓の結果のポインタを返す*/
L"\\memmap", /*root_dir で memmap を検索して open*/
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);/*memmap は無いから新規で作成*/
SaveMemoryMap(&memmap, memmap_file); /*memmap_file にmemory mapをメモして save*/
memmap_file->Close(memmap_file); /*file closed*/
Print(L"All done\n");
while (1);
return EFI_SUCCESS;
}
各プロトコルを使い分けて何をしたいかを明確にしてから
逆転してプログラムを読むと上記のような推察になった(今後、修正 update の可能性あり)。
参照 URL => コチラ
ディレクトリって何だ?? => コチラ
UEFI ファームウェアにアクセスしメモリマップを取得について => コチラ
ブートローダーって何? => コチラ
#3章カーネル
実行コマンドを念のためメモ。
$HOME/osbook/devenv/run_qemu.sh $HOME/edk2/Build/AKpiriosLoaderX64/DEBUG_CLANG38/X64/Loader.efi $HOME/workspace/mikanos/kernel/kernel.elf
前章でメモリマップを作った続きとして、
以下の記述が続き、カーネルをブートさせているらしいです。
EFI_FILE_PROTOCOL* kernel_file;
root_dir->Open(
root_dir, /*open した ROOT を基準に...*/
&kernel_file, /*↓の結果のポインタを返す*/
L"\\kernel.elf", /*root_dir で kernel.elf を検索し、Open*/
EFI_FILE_MODE_READ,/*read で kernel.elf を開く*/
0);
UINTN file_info_size = sizeof(EFI_FILE_INFO) + sizeof(CHAR16) * 12;
UINT8 file_info_buffer[file_info_size];
//ファイル情報を読みたいときに getinfo
kernel_file->GetInfo(
kernel_file, /*読みたいファイルのポインタ*/
&gEfiFileInfoGuid, /*file type ID => EDK2 のライブラリで extern 宣言してくれている see 参考情報1*/
&file_info_size, /*data 入れる前のバケツ*/
file_info_buffer); /*data 入れた後の buffer ポインタ*/
EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
UINTN kernel_file_size = file_info->FileSize;
EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
gBS->AllocatePages( /*system からメモリページを割り当てる*/
AllocateAddress, /*AllocateAddress 直入力で OK */
EfiLoaderData, /*memory type は EfiLoaderData 直入力で OK*/
(kernel_file_size + 0xfff) / 0x1000, /*page 数 : 1page 当たり 4 KiB*/
&kernel_base_addr); /*メモリ確保 開始アドレス*/
kernel_file->Read(kernel_file, &kernel_file_size, (VOID*)kernel_base_addr);
Print(L"Kernel: 0x%0lx (%lu bytes)\n", kernel_base_addr, kernel_file_size);
EFI_STATUS status;
status = gBS->ExitBootServices( /*terminates all boot service*/
image_handle, /*UefiMain に紐づく boot service を止める*/
memmap.map_key); /*lastest memory map の key*/
if (EFI_ERROR(status)) {
status = GetMemoryMap(&memmap);
if (EFI_ERROR(status)) {
Print(L"failed to get memory map: %r\n", status);
while (1);
}
status = gBS->ExitBootServices(image_handle, memmap.map_key);
if (EFI_ERROR(status)) {
Print(L"Could not exit boot service: %r\n", status);
while (1);
}
}
UINT64 entry_addr = *(UINT64*)(kernel_base_addr + 24);
typedef void EntryPointType(void);
EntryPointType* entry_point = (EntryPointType*)entry_addr;
entry_point();
参考情報1 :&gEfiFileInfoGuid
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Guid/FileInfo.h
https://monozukuri-c.com/langc-funclist-extern/
カーネルの呼び出しっと考えると重たいですが、
workmemory に割り当てたソフト(関数)を呼び出して
実行するイメージであれば考えやすいです。
プログラムの解説に関しては書籍を購入してご確認を。
##ブートローダーからピクセル編集(osbook_day03b)
UEFI の規格書を覗くと見かける pool。pool??
https://wa3.i-3-i.info/word18163.html
/*以下の記述は理解すべきか不明。いつか調べるかも(笑)*/
EFI_STATUS OpenGOP(EFI_HANDLE image_handle,
EFI_GRAPHICS_OUTPUT_PROTOCOL** gop) {
UINTN num_gop_handles = 0;
EFI_HANDLE* gop_handles = NULL;
gBS->LocateHandleBuffer(
ByProtocol, /*SearchKey を除く全ての handle で検索*/
&gEfiGraphicsOutputProtocolGuid, /*参考1*/
NULL,
&num_gop_handles,
&gop_handles);
gBS->OpenProtocol(
gop_handles[0],
&gEfiGraphicsOutputProtocolGuid,
(VOID**)gop,
image_handle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
FreePool(gop_handles);
return EFI_SUCCESS;
}
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
OpenGOP(image_handle, &gop);
/*以下の Print を call しているが、その後に塗りつぶしているので結局表示されない(笑)*/
/*Print(L"Resolution: %ux%u, Pixel Format: %s, %u pixels/line\n",
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
GetPixelFormatUnicode(gop->Mode->Info->PixelFormat),
gop->Mode->Info->PixelsPerScanLine);
Print(L"Frame Buffer: 0x%0lx - 0x%0lx, Size: %lu bytes\n",
gop->Mode->FrameBufferBase,
gop->Mode->FrameBufferBase + gop->Mode->FrameBufferSize,
gop->Mode->FrameBufferSize);*/
/*画面を塗りつぶす記述*/
UINT8* frame_buffer = (UINT8*)gop->Mode->FrameBufferBase;
for (UINTN i = 0; i < gop->Mode->FrameBufferSize; ++i) {
frame_buffer[i] = 200;
}
参考1
https://dox.ipxe.org/GraphicsOutput_8h_source.html
カーネルから行く場合は、どうする??っとワクワク。
##カーネルからピクセル編集(osbook_day03c)
entry point からカーネル(プログラム)を call してるようです。
ただし、call に伴い 変数を 2 つ渡しています。
typedef void EntryPointType(UINT64, UINT64);
EntryPointType* entry_point = (EntryPointType*)entry_addr;
entry_point(gop->Mode->FrameBufferBase, gop->Mode->FrameBufferSize);
osbook_day03b でやってる塗りつぶし作業をカーネル(man.cpp) に任せているイメージ。
#include <cstdint>
extern "C" void KernelMain(uint64_t frame_buffer_base,
uint64_t frame_buffer_size) {
uint8_t* frame_buffer = reinterpret_cast<uint8_t*>(frame_buffer_base);
for (uint64_t i = 0; i < frame_buffer_size; ++i) {
frame_buffer[i] = i % 256;
}
while (1) __asm__("hlt");
ほぇーー。
#4章 make/ピクセル描写
##hello make
GNU make 第3版を読むのは、ひとまず置いておきます(笑)
とりあえず、ビルドとリンクをまとめてくれる Makefile を呼び出してくれる良い奴です。
[before]
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c hello.c
$ lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o
[after]
$ make
##ピクセル描写
基本は同じ。投げるパラメータを増やしてる。
/*投げるパラメータを構造体で定義*/
struct FrameBufferConfig config = {
(UINT8*)gop->Mode->FrameBufferBase,
gop->Mode->Info->PixelsPerScanLine,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
0
};
//:
/*中略*/
//:
typedef void EntryPointType(const struct FrameBufferConfig*);
EntryPointType* entry_point = (EntryPointType*)entry_addr;
entry_point(&config);/*先ほど定義した構造体 config 込みで entry_point を call*/
次に投げられたカーネルは何をするのか。
extern "C" void KernelMain(const FrameBufferConfig& frame_buffer_config) {
/*画面一杯を白に塗る*/
for (int x = 0; x < frame_buffer_config.horizontal_resolution; ++x) {
for (int y = 0; y < frame_buffer_config.vertical_resolution; ++y) {
WritePixel(frame_buffer_config, x, y, {255, 255, 255});
}
}
/*0<x<200, 0<y<100 の領域を緑で上書き*/
for (int x = 0; x < 200; ++x) {
for (int y = 0; y < 100; ++y) {
WritePixel(frame_buffer_config, 100 + x, 100 + y, {0, 255, 0});
}
}
while (1) __asm__("hlt");
}
この WritePixel() って何ぞ?
int WritePixel(const FrameBufferConfig& config,
int x, int y, const PixelColor& c) {
const int pixel_position = config.pixels_per_scan_line * y + x;
/*data format check: is it RGB?*/
if (config.pixel_format == kPixelRGBResv8BitPerColor) {
uint8_t* p = &config.frame_buffer[4 * pixel_position];/*fram_buffer を P とする*/
p[0] = c.r; /*red 値を直書き*/
p[1] = c.g; /*green 値を直書き*/
p[2] = c.b; /*blue 値を直書き*/
/*data format check: is it BGR?*/
} else if (config.pixel_format == kPixelBGRResv8BitPerColor) {
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.b;
p[1] = c.g;
p[2] = c.r;
} else {
return -1;
}
return 0;
}
loader のカスタムについては、また今度。
#5章文字を書く
A は以下のように表現しているらしい、すげー(笑)
const uint8_t kFontA[16] = {
0b00000000, //
0b00011000, // **
0b00011000, // **
0b00011000, // **
0b00011000, // **
0b00100100, // * *
0b00100100, // * *
0b00100100, // * *
0b00100100, // * *
0b01111110, // ******
0b01000010, // * *
0b01000010, // * *
0b01000010, // * *
0b11100111, // *** ***
0b00000000, //
0b00000000, //
};
これを使ってどうやって書いてるか。
/*文字の X,Y 座標, 表示文字,文字の色*/
void WriteAscii(PixelWriter& writer, int x, int y, char c, const PixelColor& color) {
/*入力が A じゃなきゃ終了*/
if (c != 'A') {
return;
}
for (int dy = 0; dy < 16; ++dy) { /*kFontA[16] を [0],[1]...[16]で取り出す*/
for (int dx = 0; dx < 8; ++dx) { /*kFontA[0][7],[0][6].. を順番に 1 を掛けて 1 なら塗る*/
if ((kFontA[dy] << dx) & 0x80u) { /*[0][7]的な考え方をするのは私が python 出身だからです、すいません*/
writer.Write(x + dx, y + dy, color);/*座標(x,y) を基準に塗る*/
}
}
}
}
WriteAscii(*pixel_writer, 50, 50, 'A', {0, 0, 0});
WriteAscii(*pixel_writer, 58, 50, 'A', {0, 0, 0});
##分割コンパイル
main.cpp に分割したファイルを #include で宣言しておく。
#include "frame_buffer_config.hpp"
#include "graphics.hpp"
#include "font.hpp"
##フォントを増やそう
txt に書かれている 1 文字当たりのデータを
16Byte のバイナリデータに変換してから扱うそうです。
久々の python にホッとする(笑)
import argparse
import collections
import functools
import re
import sys
BITMAP_PATTERN = re.compile(r'([.*@]+)')
def compile(src: str) -> bytes:
src = src.lstrip()# 先頭を削除
result = []
for line in src.splitlines():
m = BITMAP_PATTERN.match(line)
if not m:
continue
bits = [(0 if x == '.' else 1) for x in m.group(1)]
bits_int = functools.reduce(lambda a, b: 2*a + b, bits)
result.append(bits_int.to_bytes(1, byteorder='little'))
return b''.join(result)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('font', help='path to a font file') #hankaku.txt
parser.add_argument('-o', help='path to an output file', default='font.out')#hankaku.bin
ns = parser.parse_args()
with open(ns.o, 'wb') as out, open(ns.font) as font:
src = font.read()
out.write(compile(src))
if __name__ == '__main__':
main()
argparse??? なんだそりゃ(笑)
引数をコマンドラインで取れるのね、へー。
なんか option で "font" としてるの?って思ったけど、
只の naming のようだ。
https://docs.python.org/3/library/argparse.html#the-add-argument-method
だけど、この nameing を使って file open をやってる、ワクワク(笑)
https://note.nkmk.me/python-file-io-open-with/
取り急ぎ以下のコマンドで bin ファイルが出来た。
python test.py hankaku.txt -o hankaku.bin
事前に用意されている Makefile を覗くと、
hankaku.txt を bin への変換(上記の python の実行)と
リンクに必要なのりしろ付きオブジェクトファイルの生成までやっているようです。