Boot to CP/M - UEFI起動するCP/MをPCとMacで動かそう

# ドマイナーなネタですが。
色々とタグを考えてみたけど、驚くほど一匹狼なネタですね……。

# EFI (UEFI)
@syuu1228 先生のスライドとか見てください、[これ](http://www.slideshare.net/syuu1228/uefi-boot-loaders)とか。他にも関連で出てくるスライドに面白いネタがたくさんあります。そんな[カーネル／VM](http://www.kernelvm.org/)の人たちが楽しそうに遊んでいたEFIを、周回遅れで追いかけてみました（情報たくさん残して頂き感謝！）

こんな面白いネタなのに、なんでタグが……

# Boot to CP/M
昔AVR上で動かしていた[CP/M](https://github.com/toyoshim/cp-mega88)を持ってきて移植しました。gnu-efiを使っているのでUbuntuとかだったら

```shell-session
% sudo apt-get install gnu-efi
% cd <somewhere>
% git clone https://github.com/toyoshim/cp-mega88.git
% cd cp-mega88
% make -f Makefile.uefi
```

これだけでbuildできます。あとは出来上がったcpmega88.efiとCP/Mのイメージファイルを

- /EFI/Boot/bootx64.efi
- /EFI/cpmega88/sdcard.img

という名前でUSBメモリのFAT32にぶち込めば、あら簡単CP/Mが直接起動するUSBメモリの出来上がり（secure bootは殺しちゃって下さい）。Macでもoptionキーを押しながら電源を入れれば起動できます。

QEMUとOVMF.fdが用意してあれば、特にメディアとか用意しなくても

```shell-session
% make -f Makefile.uefi install && make -f Makefile.uefi run
```

でQEMUからも起動可能。

# Mac対応でハマった点
これが無ければエントリを書くほどのネタじゃないんですが、2点ほどハマりどころがあったので後続の変な人のために記録を残しておきます。

## コンソール出力が一切画面に出てこない
Macの場合Graphicsモードで起動するため、コンソールを使いたい場合にはTextモードに切り替える必要があるようです。Graphicsモード、Textモードを切り替えるためにMacではConsole Control Protocolが用意されています。試した限りでは手元のPCやOVMF.fdにはこのProtocolはinstallされていませんでしたのでLocateProtocolで探してみて、見つかった時だけテキストに切り替える、というのが良さそうです。ざっと見た限り仕様的にはGraphicsをサポートする環境ではConsole Control Protocolを用意して、Textモードは必ず用意すべし、ってなっていました。

ちなみにgnu-efiでは必要な定義が用意されていないため、自前でGUIDとかプロトタイプ宣言を書いて呼んでます。

```cpp
#define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \
  { 0xf42f7782, 0x12e, 0x4c12, {0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21} }
EFI_GUID ConsoleControlProtocol = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;

typedef struct _EFI_CONSOLE_CONTROL_INTERFACE EFI_CONSOLE_CONTROL_INTERFACE;

// 必要なTextモード（0）だけ定義
typedef enum { EfiConsoleControlScreenText } EFI_CONSOLE_CONTROL_SCREEN_MODE;

typedef EFI_STATUS
(EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE) (
  IN  EFI_CONSOLE_CONTROL_INTERFACE   *This,
  IN  EFI_CONSOLE_CONTROL_SCREEN_MODE Mode
  );

struct _EFI_CONSOLE_CONTROL_INTERFACE {
  VOID *GetMode;  // 使わないので適当に......
  EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode;
  VOID *LockStdIn;  // 同じく
};
#endif

...

EFI_STATUS efi_main(EFI_HANDLE *image, EFI_SYSTEM_TABLE *systab) {
  ...
  EFI_CONSOLE_CONTROL_INTERFACE *efi_cc;
  EFI_STATUS status = uefi_call_wrapper(
      efi_systab->BootServices->LocateProtocol, 3,
      &ConsoleControlProtocol, NULL, (void**)&efi_cc);
  // Graphicsモードをサポートしていない環境ではエラーが返ってくるので、その際はスキップ
  if (!EFI_ERROR(status))
    uefi_call_wrapper(efi_cc->SetMode, 2, efi_cc, EfiConsoleControlScreenText);
  ...
}
```

## イメージファイルが読めない
最初に試していたPC環境ではFileSystemProtocolをLocateProtocolで掴み、何も考えずにOpenVolumeを呼び出すだけで起動元のVolumeが開けていました。なので単純にL"EFI\\\\cpmega88\\\\sdcard.img"みたいな名前でデータを読んでいたのですが、この方法はMacでは通用しませんでした。

LoadedImageProtocolをHandleProtocolしてきて、得られたEFI_LOADED_IMAGEをに格納されているDeviceHandleを使ってFileSystemProtocolをHandleProtocolしてあげると、OpenVolumeした際に起動したEFIの格納元Volumeを開くことができます。

もはや暗号ですね。何を言っているのかわからないのでコードで。

```cpp
EFI_STATUS efi_main(EFI_HANDLE *image, EFI_SYSTEM_TABLE *systab) {
  ...
  EFI_FILE_IO_INTERFACE *efi_fio;
  EFI_FILE_HANDLE efi_fs;
  EFI_GUID LoadedImageProtocol = LOADED_IMAGE_PROTOCOL;
  EFI_LOADED_IMAGE *efi_li;

  EFI_STATUS status = uefi_call_wrapper(
      efi_systab->BootServices->HandleProtocol, 3,
      image, &LoadedImageProtocol, (void**)&efi_li);
  if (EFI_ERROR(status)) return;  // abort

  // この時点でefi_liの中に実行イメージに関する情報が格納される
  status = uefi_call_wrapper(
      // LocateProtocolではなくHandleProtocolなので注意
      efi_systab->BootServices->HandleProtocol, 3,
      // ここのDeviceHandle指定がミソ
      efi_li->DeviceHandle, &FileSystemProtocol, (void**)&efi_fio);
  if (EFI_ERROR(status)) return;  // abort

  status = uefi_call_wrapper(efi_fio->OpenVolume, 2, efi_fio, &efi_fs);
  if (EFI_ERROR(status)) return;  // abort
  ...
}
```

ここまで準備ができたら、あとは

```cpp
  UINT64 mode = EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE;
  EFI_STATUS status = uefi_call_wrapper(
      efi_fs->Open, 5, efi_fs, &fp, L"EFI\\cpmega88\\sdcard.img", mode, 0);
}
```

こんなコードでファイルが触れます。

# おわりに
という事で、UEFIから起動するエミュレータが増えることを期待。PDP11とかMSXをお願いします。音周りがProtocol化されてないのが残念なんですけどね。まぁ、そりゃそーだ。Boot loaderには必要ないもん。USB Audio必携ですね。

**First come first served :)**