BitVisorからBIOSを呼び出す

BitVisorからBIOSを呼び出す方法を紹介します。

# callrealmode

`core/callrealmode.h`で定義されている関数群がBIOSを呼び出すためのものです。ここに使いたいものがすでにあれば、簡単です。

なお、仮想マシンを開始した後(ブートセクターを実行後)はBIOSを呼び出してはいけません。

ない場合は追加する必要があります。多少手間ではありますが、他の呼び出しを真似て書けばそれほど難しいものではありません。例として以下のふたつのビデオBIOSの呼び出しを追加します:

* ボーダーカラーの設定
* ピクセルの書き込み

## core/callrealmode.h

`core/callrealmode.h`に呼び出したいインターフェイスを追加します。今回グラフィックスモードを使いたいので、モードの定義も増やしておきます:

```diff

--- a/core/callrealmode.h
+++ b/core/callrealmode.h
@@ -47,6 +47,7 @@
 #define GETSHIFTFLAGS_INSERT_BIT       0x80
 
 #define VIDEOMODE_80x25TEXT_16COLORS   0x03
+#define VIDEOMODE_320x200GRAPHICS_16COLORS     0x0D
 
 struct sysmemmap {
        u64 base;
@@ -92,5 +93,7 @@ void callrealmode_setcursorpos (u8 page_
 void callrealmode_startkernel32 (u32 paramsaddr, u32 startaddr);
 void callrealmode_tcgbios (u32 al, struct tcgbios_args *args);
 void callrealmode_getfontinfo (u8 bh, u16 *es, u16 *bp, u16 *cx, u8 *dl);
+void callrealmode_setbordercolor (int color);
+void callrealmode_writepixel (int page, int color, int x, int y);
 
 #endif
```

## core/callrealmode.c

`core/callrealmode.c`では`core/callrealmode.h`で定義した関数を実装します。定数や構造体の具体的な定義は後回しにして、とりあえず必要そうなものを書いてみます:

```diff
--- a/core/callrealmode.c
+++ b/core/callrealmode.c
@@ -392,5 +392,21 @@ callrealmode_getfontinfo (u8 bh, u16 *es
                *dl = d.u.getfontinfo.dl_ret;
 }
 
+void callrealmode_setbordercolor (int color) {
+  struct callrealmode_data d;
+  d.func = CALLREALMODE_FUNC_SETBORDERCOLOR;
+  d.u.setbordercolor.color = color;
+  callrealmode_call (&d);
+}
+void callrealmode_writepixel (int page, int color, int x, int y) {
+  struct callrealmode_data d;
+  d.func = CALLREALMODE_FUNC_WRITEPIXEL;
+  d.u.writepixel.page = page;
+  d.u.writepixel.color = color;
+  d.u.writepixel.x = x;
+  d.u.writepixel.y = y;
+  callrealmode_call (&d);
+}
+
 INITFUNC ("global0", callrealmode_init_global);
 INITFUNC ("panic0", callrealmode_panic);
```

## core/callrealmode_asm.h

`core/callrealmode_asm.h`には`core/callrealmode.c`から使用する定数や構造体の定義を追加します:

```diff
--- a/core/callrealmode_asm.h
+++ b/core/callrealmode_asm.h
@@ -49,6 +49,8 @@ enum callrealmode_func {
        CALLREALMODE_FUNC_STARTKERNEL32 = 0x9,
        CALLREALMODE_FUNC_TCGBIOS = 0xA,
        CALLREALMODE_FUNC_GETFONTINFO = 0xB,
+       CALLREALMODE_FUNC_SETBORDERCOLOR = 0xC,
+       CALLREALMODE_FUNC_WRITEPIXEL = 0xD,
 };
 
 struct callrealmode_printmsg {
@@ -112,6 +114,15 @@ struct callrealmode_getfontinfo {
        u8 dl_ret, bh;
 } __attribute__ ((packed));
 
+struct callrealmode_setbordercolor {
+       u8 color;
+};
+
+struct callrealmode_writepixel {
+       u8 page, color;
+       u16 x, y;
+} __attribute__ ((packed));
+
 struct callrealmode_data {
        enum callrealmode_func func : 32;
        union {
@@ -126,6 +137,8 @@ struct callrealmode_data {
                struct callrealmode_startkernel32 startkernel32;
                struct callrealmode_tcgbios tcgbios;
                struct callrealmode_getfontinfo getfontinfo;
+               struct callrealmode_setbordercolor setbordercolor;
+               struct callrealmode_writepixel writepixel;
        } u;
 } __attribute__ ((packed));
 
```

enum callrealmode_funcに追加した値は次の`core/callrealmode_asm.s`に追加するものと一致するようにします。

## core/callrealmode_asm.s

`core/callrealmode_asm.s`はBIOSを呼び出すルーチンです。アセンブリ言語で記述します。Real-address modeへの移行などの処理はすでに書かれているので、新たな部分だけ追加すればOKです。`core/callrealmode_asm.h`に追加した値と`CALLREALMODE_FUNC_*`の値が一致していることと、`core/callrealmode_asm.h`に追加した構造体のメンバーのオフセット+0x30と`OFF_*`の値が一致していることと、構造体のメンバーにアクセスする際のサイズが一致していることを確認します。

```diff
--- a/core/callrealmode_asm.s
+++ b/core/callrealmode_asm.s
@@ -40,6 +40,8 @@
        CALLREALMODE_FUNC_STARTKERNEL32 = 0x9
        CALLREALMODE_FUNC_TCGBIOS = 0xA
        CALLREALMODE_FUNC_GETFONTINFO = 0xB
+       CALLREALMODE_FUNC_SETBORDERCOLOR = 0xC
+       CALLREALMODE_FUNC_WRITEPIXEL = 0xD
 
        SEG_SEL_CODE_REAL = 0x0000
        SEG_SEL_DATA_REAL = 0x0000
@@ -178,6 +180,11 @@ callrealmode_switch:
        OFF_GETFONTINFO_CX_RET = 0x34
        OFF_GETFONTINFO_DL_RET = 0x36
        OFF_GETFONTINFO_BH = 0x37
+       OFF_SETBORDERCOLOR_COLOR = 0x30
+       OFF_WRITEPIXEL_PAGE = 0x30
+       OFF_WRITEPIXEL_COLOR = 0x31
+       OFF_WRITEPIXEL_X = 0x32
+       OFF_WRITEPIXEL_Y = 0x34
 
        # Which function?
        mov     OFF_FUNC(%bp),%ax
@@ -205,6 +212,10 @@ callrealmode_switch:
        je      tcgbios
        cmp     $CALLREALMODE_FUNC_GETFONTINFO,%ax
        je      getfontinfo
+       cmp     $CALLREALMODE_FUNC_SETBORDERCOLOR,%ax
+       je      setbordercolor
+       cmp     $CALLREALMODE_FUNC_WRITEPIXEL,%ax
+       je      writepixel
        # Error!
        cld
        mov     $(errormsg_data-1-callrealmode_start+CALLREALMODE_OFFSET),%di
@@ -447,6 +458,25 @@ getfontinfo:
        mov     %dl,OFF_GETFONTINFO_DL_RET(%bp)
        ret
 
+# setbordercolor
+#
+setbordercolor:
+       mov     OFF_SETBORDERCOLOR_COLOR(%bp),%bh
+       mov     $0x1001,%ax
+       int     $0x10
+       ret
+
+# writepixel
+#
+writepixel:
+       mov     OFF_WRITEPIXEL_PAGE(%bp),%bh
+       mov     OFF_WRITEPIXEL_COLOR(%bp),%al
+       mov     OFF_WRITEPIXEL_X(%bp),%cx
+       mov     OFF_WRITEPIXEL_Y(%bp),%dx
+       mov     $0x0C,%ah
+       int     $0x10
+       ret
+
 # Subroutines
 #
 paging_and_protection_off:
```

## 機能テスト

上で追加した機能をテストしてみます。今回は`core/main.c`から呼び出してみることにします。

```diff
diff --git a/core/main.c b/core/main.c
--- a/core/main.c
+++ b/core/main.c
@@ -322,6 +322,29 @@ process_cpu_type_ext (void)
        }
 }
 
+static void drawtest (void) {
+  int x,y,x2,y2,xp2,x12,d;
+  callrealmode_setvideomode (VIDEOMODE_320x200GRAPHICS_16COLORS);
+  callrealmode_setbordercolor (3);
+  for(y=0;y<200;y+=2)for(x=y%4;x<320;x+=4)callrealmode_writepixel(0,7,x,y);
+  for(y=50;y<150;y++)for(x=85;x<235;x++)callrealmode_writepixel(0,15,x,y);
+  x=30,y=0,x2=900,y2=0;
+  while(x>=y){
+    for(int i=0;i<x;i++)callrealmode_writepixel(0,4,160+i,100+y),
+                         callrealmode_writepixel(0,4,160-i,100+y),
+                         callrealmode_writepixel(0,4,160+i,100-y),
+                         callrealmode_writepixel(0,4,160-i,100-y),
+                         callrealmode_writepixel(0,4,160+y,100+i),
+                         callrealmode_writepixel(0,4,160-y,100+i),
+                         callrealmode_writepixel(0,4,160+y,100-i),
+                         callrealmode_writepixel(0,4,160-y,100-i);
+    y2+=y+y+1,y++,xp2=900-y2,x12=x2-x-x+1;
+    if(xp2-x12<x2-xp2)x--,x2=x12;
+  }
+  if((d=msgopen("ttyin"))>=0)msgsendint(d,0),msgclose(d);
+  callrealmode_setvideomode (VIDEOMODE_80x25TEXT_16COLORS);
+}
+
 static void
 bsp_init_thread (void *args)
 {
@@ -356,6 +379,7 @@ bsp_init_thread (void *args)
        } else if (!uefi_booted) {
                load_drivers ();
                get_tmpbuf (&tmpbufaddr, &tmpbufsize);
+               drawtest ();
                load_bootsector (bios_boot_drive, tmpbufaddr, tmpbufsize);
                sync_cursor_pos ();
        }
```

ブートセクターの読み込みの前に、グラフィックスモードに切り替え、ボーダーカラーを設定し、背景に点を並べ、白い長方形と赤い円を描き、キー入力を待つだけの簡単なプログラムです。実行すると以下のようになります:

![a.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103733/a55f92c4-503a-1bee-ff30-83721e301493.jpeg)

完璧だ...!