BIOS interrupt callを使う

# はじめに
BIOS interrupt callの使い方を説明します。

from Wikipedia

>
BIOS interrupt calls are a facility that operating systems and application programs use to invoke the facilities of the Basic Input/Output System on IBM PC compatible computers.
>

BIOSは入出力機能を提供します。BIOS Serviceといいます。
BIOS interrupt callを使うことでBIOS Serviceを呼び出します。

# 使い方
BIOS interrupt callの利用手順を説明します。

Serviceに対応したInterrupt vectorがあります。
利用者はInterrupt vectorを指定してSoftware interruptを発行します。

| Interrupt vector | Service |
|:--|:--|
| 0x10 | Video Services |
| 0x13 | Low Level Disk Services |
| 0x14 | Serial port services |
| 0x16 | Keyboard services |

Serviceには複数のfunctionがあります。function番号で区別します。
function番号は%ah registerに設定します。

例. Serial port servicesには次のfunctionがあります。
Transmit Characterを利用するときには%ah = 0x01を設定してint 0x14を発行します。

| # | function |
|:--|:--|
| 0x00 | Serial Port Initialization |
| 0x01 | Transmit Character |
| 0x02 | Receive Character |
| 0x03 | Status |

具体例を用いてBIOS interrupt callの使い方を説明します。
Serial Portに'a'を出力する例です。

```gas:boot.s
    .code16
main:
    cli
    mov $0x01, %ah # Transmit Character
    mov $0x61, %al # Character 'a' = 0x61
    mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
    int $0x14      # Serial port services
    hlt
```

上記内容を説明します。

- CPU起動直後はReal-address modeであり16bit codeのため".code16"を指定します。
- %ahに0x01を設定します。functionがTransmit Characterであることを示します。
- %alに送信データを設定します。'a'(0x61)を設定します。
- %dxにPort numberを設定します。COM1(0)を設定します。
- int 0x14でSerial port servicesを呼び出します。
- BIOS ServiceがSerial Port COM1に'a'を出力します。

他のService / functionについても上記例と類似の方法で
BIOS interrupt callを呼び出します。

# PC boot sequence
BIOS interrupt callはCPU起動直後に利用します。
以降の説明のためここではPC boot sequenceを説明します。
次の順番で起動します。

- CPU reset
- Real-address modeで動作開始します。
- 0xFFFF0から処理を開始します。BIOSのプログラム(ROM)が配置されています。
- BIOSはPOST / デバイス初期化 を実行します。
- BIOSはboot deviceの先頭512byteをメモリ0x07C00に読み込みます。
- BIOSはIPを0x07C00に設定します。0x07C00から処理が開始します。

512byteのboot sectorにプログラムを記述してBIOS interrupt callを使います。

# QEMU
QEMUを使って動作確認します。QEMUをインストールします。

```bash:console
sudo apt-get install -y qemu
```

次のscript fileでboot.sをコンパイルしてboot sector imageを作成します。

```bash:mk.sh
#!/bin/sh

as -o boot.o boot.s
objcopy -O binary boot.o boot_head.img
BOOT_SEC_SIZE=512
PROG_SIZE=`stat --printf="%s" boot_head.img`
FILL_SIZE=$(expr $BOOT_SEC_SIZE - $PROG_SIZE - 2)

dd if=/dev/zero of=zero.img bs=$FILL_SIZE count=1
cat boot_head.img zero.img > boot.img
/bin/echo -ne "\x55\xaa" >> boot.img
```

上記内容を説明します。

- boot.sをコンパイルしてboot.oを作成します。
- boot.oの命令を(elf形式ではなく)バイナリでboot_head.imgに出力します。
- (512 - プログラムのサイズ - 2) byteの値0のファイルzero.imgを作成します。
- boot_head.imgとzero.imgを結合してboot.imgを作成します。
- boot.imgの末尾に0x55 0xaaの2byteを付加します。boot sectorのmarkです。

QEMUを実行します。

```bash:console
$ qemu-system-x86_64 -hda boot.img -nographic
a
```

BIOS画面に'a'が表示されます。Ctl+a -> xでQEMUを終了します。

# debug
gdbを使ったデバッグ方法を説明します。
次の.gdbinitを用意します。16bit codeを使用するためset architecture i8086を設定します。

```:.gdbinit
set architecture i8086
target remote localhost:1234
b *0x7c00
c
disas 0x7c00, +10
```

QEMUのオプションに-s -Sを追加します。

```bash:console
qemu-system-x86_64 -hda boot.img -nographic -s -S
```

gdbを実行します。

```bash:console
$ gdb
GNU gdb (Ubuntu 7.7-0ubuntu3) 7.7
(snip)
The target architecture is assumed to be i8086
0x0000fff0 in ?? ()
Breakpoint 1 at 0x7c00

Breakpoint 1, 0x00007c00 in ?? ()
Dump of assembler code from 0x7c00 to 0x7c0a:
=> 0x00007c00:	cli    
   0x00007c01:	mov    $0x1,%ah
   0x00007c03:	mov    $0x61,%al
   0x00007c05:	mov    $0x0,%dx
   0x00007c08:	int    $0x14
End of assembler dump.
(gdb) 
```

# Serial port services(0x14)
Serialで受信したデータを送信する(echo)の例です。

```gas:boot.s
    .code16
    cli
main:
    call status
    test $0x01, %ah # check 00000001b - Data ready.
    jz main
    call recv
    call send
    jmp main
    hlt
send: # %al : send character
    mov $0x01, %ah # 01h, indicating the Send Character Function.
    mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
    int $0x14      # Serial port services
    ret
recv: # %al : received character, %ah : line status
    mov $0x02, %ah # 02h, indicating the Receive Character Function.
    mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
    int $0x14      # Serial port services
    ret
status:
    mov $0x03, %ah # 03h, indicating the Read Serial Port Status Function.
    mov $0x00, %dx # Serial port number (0=COM1, 1=COM2, 2=COM3, 3=COM4).
    int $0x14      # Serial port services
    ret
```

上記内容を説明します。

- statusで受信状態を確認します。受信できると%ahのbit0が1(Data ready)になります。
- Data readyであればrecvで受信します。
- sendで受信したデータを送信します。
- 上記を繰り返します。

# Low Level Disk Services(0x13)
HDDから先頭の1 sector(512byte)を読み込む例です。

```gas:boot.s
    .code16
main:
    cli
    call read
    hlt
read:
    mov $0x02, %ah # 02h, indicating the Read Sectors Function.
    mov $0x01, %al # Number of sectors.
    mov $0x00, %ch # Bottom 8 bits of track number (0-based).
    mov $0x01, %cl # ttssssss, as follows:
                   #   tt = top two bits of 10-bit track number,
                   #   ssssss = 6-bit sector number (1-based).
    mov $0x00, %dh # Head number (0-based).
    mov $0x80, %dl # Drive number. 0x80 : 1st hard disk.
    xor %bx, %bx   # ES:BX - Address of user buffer.
    mov %bx, %es
    mov $0x8c00, %bx
    int $0x13
    ret
```

上記内容を説明します。

- %ahに02を設定します。Read Sectors Functionを示します。
- 読み込むsector数を%alに設定します。
- %cl/%chに先頭sectorのtrack number / sector numberを設定します。
- %dhに先頭sectorのheader numberを設定します。
- %dlにDrive numberを設定します。0x80は1st HDDを示します。
- ES:BXに読み込み先メモリアドレスを設定します。ここでは0x8c00に読み込みます。
- int 0x13を発行します。

# references
- https://en.wikipedia.org/wiki/BIOS_interrupt_call
- http://stackoverflow.com/questions/32955887/how-to-disassemble-16-bit-x86-boot-sector-code-in-gdb-with-x-i-pc-it-gets-tr