mikanOS 3章を整理 [前編]

# はじめに
今回はカーネルを call してみます
基本的には、mikanOS の sample コードを読み解き、
コードから具体的なフローを学んでいくスタンスでまとめていきます。

[**⇦前|mikanOS 2章を整理**](https://qiita.com/AKpirion/items/c73ab782396fca6f8d85) || [**mikanOS 3章を整理[後編]|⇨**](https://qiita.com/AKpirion/items/54d00f2afd9999337022)

# Kernel を呼び出すフロー
まずは Kernel.elf を取り込んで、
ソフト的に色々できるようにします。

色々と言っても複雑なことではなく、
ファイルサイズを確認したり、kernel を WorkMemory に配置して
アプリケーションとして Call 出来る準備をしたりします。

![flow_chart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/96fd0a16-3568-8f5d-7c6e-31e126c5777b.png)

# Kernel の取り込み
冒頭にあるように Kernel.elf を一旦取り込む必要があります。
取り込むために必要な Open() について確認してみましょう。

<font color="red">**Open() の概要**:
以下 "*This" を基準に "FileName" の "file" もしくは "directory" を検索して開く </font>
![open_specification.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/a911c1d5-924b-5458-9814-655f9b2e0edf.png)

```Main.c
/*take in kernel.elf*/
  EFI_FILE_PROTOCOL* kernel_file;
  status = root_dir->Open(
      root_dir,           //*This 
      &kernel_file,       //**NewHandle
      L"\\kernel.elf",    //*FileName
      EFI_FILE_MODE_READ, //OpenMode
      0);                 //Attributes

/*
"*This" を基準に "FileName" の "file" もしくは "directory" を開く
                     || <=イコール 
　　"root_dir" から "kernel.elf" を read mode で開く
*/
```

# Kernel のサイズ確認 
application として kernel を call するのであれば、
横やりが入らないように workmemory の一部を kernel 用に確保してあげる必要があります。

そのためにもファイルサイズ情報が必須です。
アプローチとしては GetInfo() を使うのですが、
まずは仕様確認。
![Getinfo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/1324d788-9f4c-9635-3f93-3e6268213b47.png)

欲しいのは Kernel ファイルの情報なので、
**EFI_FILE_INFO** かな。。って事で仕様をチラ見する。
![efi_file_info.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/6259ec6b-7761-b155-4cbe-da2bab3789bf.png)

GetInfo で output するデータは Void * なので
データの中身自体は Void 型となります。
しかし、想定しているデータは EFI_FILE_INFO :構造体です。

よってデータ取得後、 
Void* を EFI_FILE_INFO* とするようなキャストが必要になります。

```check_kernel_size.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    UINTN BufferSize = sizeof(EFI_FILE_INFO) + sizeof(CHAR16)*12;
    UINT8 void_kernel_info[BufferSize];
    status = kernel_file->GetInfo(
             kernel_file, &gEfiFileInfoGuid,
             &BufferSize,void_kernel_info);
    if (EFI_ERROR(status)) {Print(L"Fail to get kernel info\n");}
    EFI_FILE_INFO* kernel_info = (EFI_FILE_INFO*)void_kernel_info;

    Print(L"All done\n");
    while (1);
    return EFI_SUCCESS;
}
```


# メモリ領域の確保
カーネルサイズを確認したら、
work memory に配置する領域を確保します。
![pagealocate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/5b9563f7-be89-4903-1de5-febb7314af0e.png)
EFI_ALLOCATE_TYPE の説明についてはOS自作入門を買って確認しましょう。
*UEFI specification に説明ありますけど。。。

```keep_workmemory.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {

    //alocate kernel on work memory
    EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
    gBS->AllocatePages(
        AllocateAddress, EfiLoaderData,
        (kernel_info.FileSize + 0xfff) / 0x1000, &kernel_base_addr);

    //↓ 以下の記述が無くても動作はする。
    //kernel_file->Read(kernel_file, &(kernel_info.FileSize), (VOID*)kernel_base_addr);

    Print(L"All done\n");
    while (1);
    return EFI_SUCCESS;
}
```

# UEFI Boot service 終了
kernel を呼び出す前に Boot Service を終了させる必要があります。
[**ココ**](https://uefi.org/specs/ACPI/6.4/15_System_Address_Map_Interfaces/uefi-getmemorymap-boot-services-function.html) に参考情報があります。boot service から runtime srvice の
移行するためのお約束として Exit boot service するようです。
![editboot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/2dd2911f-52d0-015c-a72e-a242c0c7e30a.png)
当初に get memory map を call したときに map key は取得しているのですが、
その後の操作で map key は変わっている場合がありますので、exit する際には
再度 get memory map を call して map key の更新が必要です。

```exit_boot_service.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {    
    //Exit boot services
    MemMap_Make(&MemMap,4096*4);
    status = gBS->ExitBootServices(image_handle, MemMap.map_key);
    if (EFI_ERROR(status)) {
      Print(L"Fail to ExitBootServices\n");}
    }

```
# Kernel の起動
◆**Point 1 : entry point 明確化**
base address から 24バイトのオフセットに
Entry point のアドレスが格納されています(らしい)。
そのため (base address + 24)をポインタキャストして * とすれば
Entry point のアドレスになると思います。

◆**Point 2: kernel を call するために関数のプロトタイプ宣言が必要**
呼び出す kernel は結局、関数なので
引数と戻り値を定義するプロトタイプ宣言が
必要という意義は納得です。

◆**その他: 作法としてストックしておけば、とりあえず今は良いかも**
```boot_kernel.c
    //set entry point and call kernel
    UINT64 entry_addr = *(UINT64*)(kernel_base_addr + 24);    /* <= Point 1*/
    typedef void EntryPointType(void);                        /* <= Point 2*/
    EntryPointType* entry_point = (EntryPointType*)entry_addr;
    entry_point();
```