mikanOS 3章を整理 [後編]

# はじめに

前回の記事では何もしない kernel を
ブートしていました。
今回はカーネルから画面の色を
変更する所まで整理したいと思います。

[**⇦前|mikanOS 3章を整理[前編]**](https://qiita.com/AKpirion/items/d20a27bd57645a52df87) || [**mikanOS 4章を整理[前編]|後⇨**](https://qiita.com/AKpirion/items/f57364708b2679e76c8c)

# ブートローダーから画像を変えるフロー
まずは画素を変える事をゴールに
フローを整理してみます。

![flow_chart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/4505443a-4a49-edf9-4f8e-8b234983ef7f.png)

# Graphics Output Protocol を検索
LocateHandleBuffer() を使います。
検索したプロトコルを workmemory に用意した領域に一度貯めます。
ただココで注意なのは貯めたプロトコルは **EFI_HANDLE** であることです。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/482331ad-635c-e24e-e909-577af70fe8dd.png)

# Graphics Output Protocol を Open
OpenProtocol を使っていますが、
実際は HandleProtocol に変形させて使っています。

![openprotocol.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/635483d7-2492-233b-41a1-edb8d7f82c1f.png)
<font color="Red">**[注意]**</font>
以下にあるように HandleProtocol ではなく、
OpenProtocol の使用を推奨しています。
![chuui.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/beda77b7-0278-4fb8-3b97-704c4ace52d8.png)


# メモリ領域の割り当て
Description の最後の方に記載があるが、Buffer を使うのであれば
AllocatePool() を call する必要があると書いてある。
おそらく章を追うと何処かで追加されると思う。

![free_pool.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/9da37f50-8381-2958-0df9-e1aba5845f94.png)

# Frame Buffer Base で色指定
まずは画素の定義を確認。Blt() で行けそうな気がした。
![buffer_pixel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/f9c608e6-3f20-ca39-fdf9-1abfd43fc6ee.png)
しかし、3章では **Blt() を使わない**、
**Frame Buffer Base を使う**。
sample コードを参考に UEFI の規格を追ってみた。

![buffer_pixel_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737344/9fffdf9d-504a-84c0-fe16-cf3b9bb17ebf.png)
なるほど。
FrameBufferSize も取り出して for 分を回せば、
全ピクセルの色を指定したプログラムができる。

# プログラムで総括

```Graphic_Open.c

EFI_STATUS OpenGOP(EFI_HANDLE image_handle,
                   EFI_GRAPHICS_OUTPUT_PROTOCOL** gop) {
  UINTN num_gop_handles = 0;
  EFI_HANDLE* gop_handles = NULL;
  /*search the Graphic open protocol*/
  gBS->LocateHandleBuffer(
      ByProtocol,
      &gEfiGraphicsOutputProtocolGuid,
      NULL,
      &num_gop_handles,
      &gop_handles);
  /*open the Graphic open protocol*/
  gBS->OpenProtocol(
      gop_handles[0],
      &gEfiGraphicsOutputProtocolGuid,
      (VOID**)gop,
      image_handle,
      NULL,
      EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
  /*alocate memory area for system*/
  FreePool(gop_handles);

  return EFI_SUCCESS;
}

EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    //open graphic protocol
    EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
    status = OpenGOP(image_handle, &gop);
    if (EFI_ERROR(status)) {Print(L"Fail to open graphic\n");}

    //write graphic
    UINT8* frame_buffer = (UINT8*)gop->Mode->FrameBufferBase;
    for (UINTN i = 0; i < gop->Mode->FrameBufferSize; ++i) {
        frame_buffer[i] = 255;
    }

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

# その他メモ
グラフィックの操作は Console I/O protocol に分類されます。
UEFI specification によると、Boot service 中に有効となる protocol になります。

# カーネルから Frame Buffer base を制御するために
kernel ファイルの引数として
FrameBufferBase, FrameBufferSize
の 2 つが必要です。

追加してみましょう。

```compare.c
  /*[before]*/
    typedef void EntryPointType(void);
    EntryPointType* entry_point = (EntryPointType*)entry_addr;
    entry_point();

  /*[after]*/
    typedef void EntryPointType(UINT64, UINT64);
    EntryPointType* entry_point = (EntryPointType*)entry_addr;
    entry_point(gop->Mode->FrameBufferBase, gop->Mode->FrameBufferSize);
```

念のため kernel 側のコードも載せておきます。

```main.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");
}
```