BIOSを使わないブートローダ

## 「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は[「手探りでCUI OS作成に挑む」連載目次](https://qiita.com/earthen94/items/b5d77f98f0cf85a13346)を御覧下さい。

## 目的
前回IDEのHDDを読み込むプログラムを書きました。
https://qiita.com/earthen94/items/1009feeecb852ddef7ec

そしてこの関数を元にINT13Hを呼び出さないブートローダを作ってみました。
同時にLBAのアドレス指定に第1セクタ目以外を指定できない不具合がありましたので併せて修正しました。

呼び出す時に3つのレジスタに分けて指定するようにしました。
前回作ったプログラムは汎用的に指定ができないようになっていました。
```
    mov bl, 0x01           ; LBA 0-7
    mov bh, 0x00           ; LBA 8-15
    mov cl, 0x00           ; LBA 16-23
    call read_sector
```

## 検証プログラム

<details>
<summary>boot.asm</summary>

```boot.asm
bits 16
org 0x7C00

start:

    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    
    mov si, loading_msg
    call print_string
    
    ; LBA 2(第2セクタ)を0x0000:0x7E00へ読み込む
    mov ax, 0x0000
    mov es, ax
    mov di, 0x7E00          ; ES:DI = 0x0000:0x7E00
    
    ; LBA 1 (第2セクタ) の設定
    mov bl, 0x01           ; LBA 0-7
    mov bh, 0x00           ; LBA 8-15
    mov cl, 0x00           ; LBA 16-23
    call read_sector
    
    jc disk_error       ; エラー
    
    ; 成功の文言
    mov si, success_msg
    call print_string
    
    ; 読み込んだコードへ跳ぶ（第2セクタ）
    jmp 0x0000:0x7E00
    
    jmp $
    
disk_error:
    mov si, error_msg
    call print_string
    
    jmp $
    
; 文字列表示
print_string:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp print_string
.done:
    ret
    
loading_msg db "Loading...", 0x0D, 0x0A, 0
success_msg db "Sector read successfully!", 0x0D, 0x0A, 0
error_msg db "Disk read error!", 0x0D, 0x0A, 0
test_msg db "TEST", 0x0D, 0x0A, 0

%include "readdisk.asm"

times 510-($-$$) db 0
dw 0xAA55
```
</details>

<details>
<summary>readdisk.asm</summary>

```readdisk.asm
; IDEHDD読み込み

read_sector:
    push ax
    push bx
    push cx
    push dx
    push ds
    
    ; 引数を保存
    push bx               ; BXレジスタをスタックに保存（bl、bhを含む）
    push cx               ; CXレジスタをスタックに保存（clを含む）
    
    ; ===== 1. HDDが使用可能か =====
    mov dx, 0x1F7       ; 状態レジスタポート
    mov cx, 0xFFFF      ; 最大試行回数
.wait_not_busy:
    in al, dx
    test al, 0x80       ; 最高位ビット=1かどうか(0x80 = 1000 0000)
    jz .ready           ; 1でなければ使用可能 →readyへ遷移
    dec cx              ; 試行回数を減算
    jnz .wait_not_busy  ; 使用不可の場合は待ち続ける
    jmp .error          ; 時間切れ

.ready:

    ; ===== 2. どのドライブを選択するか​​と​​LBAアドレスの上位4ビット​​を設定 =====
    mov dx, 0x1F6       ; 「ドライブ/ヘッドレジスタ」
    mov al, 0xE0        ; 本文を参照
    or al, bh           ; 
    and al, 0xEF        ; bit4=0(固定)
    out dx, al
    
    ; ===== 3. 読み込みセクタ数の設定 =====
    mov dx, 0x1F2       ; セクタ数レジスタ​​：転送する​​セクタ数​​を指定するために使用
    mov al, 1           ; 1セクタ読み込み
    out dx, al

    ; ===== 4. LBAアドレスの指定 =====
    
    ; 引数を取り出す
    pop cx
    pop bx
    
    mov dx, 0x1F3       ; LBA 0-7
    mov al, bl          ; bl = LBA 0-7
    out dx, al

    mov dx, 0x1F4       ; LBA 8-15
    mov al, bh          ; bh = LBA 8-15
    out dx, al

    mov dx, 0x1F5       ; LBA 16-23
    mov al, cl   

    ; ===== 5. 読み込み命令送信 =====
    mov dx, 0x1F7
    mov al, 0x20        ; 0x20​​: セクタ読み込み　(参考　0x30​​: セクタ書き込み)
    out dx, al          ; 一般的な書き方　outb(0x1F7, 0x20) → 読み込み開始

    ; ===== 6. データ転送が可能となるのを待つ =====
    mov cx, 0xFFFF      ; 最大試行回数
.wait_data_ready:
    in al, dx
    test al, 0x80       ; 読み込み可能か
    jnz .wait_data_ready
    test al, 0x08
    jnz .data_ready
    dec cx
    jnz .wait_data_ready
    jmp .error

.data_ready:
    ; ===== 7. エラー確認 =====
    test al, 0x01       ; 状態レジスタ 0bit目=1はエラー
    jnz .error

    ; ===== 8. データ読み込み =====
    mov cx, 256         ; 512バイト(WORD単位で÷2)　IDEデータレジスタ（0x1F0）は​​16ビット（2バイト）単位​​でデータを扱う
    mov dx, 0x1F0       ; データレジスタ(読み書きに使用される)
    cld                 ; 念のため明示的にDF=0に設定し、insw実行時に​​メモリアドレス（DI）を自動増加​​（+2）させる。std（DF=1）だと、アドレスが減少（-2）
    rep insw            ; ES:DIへ読み込む　cxの回数だけinswを繰り返し、


    pop ds
    pop dx
    pop cx
    pop bx
    pop ax
    clc                 ; 成功
    ret

.error:

    pop ds
    pop dx
    pop cx
    pop bx
    pop ax
    stc                 ; 失败
    ret

```
</details>

<details>
<summary>kernel.asm</summary>

```kernel.asm
org 0x7E00

    ; セグメント初期化
    xor ax, ax
    mov ds, ax

    ; 文字列表示
    mov si, msg
.print:
    lodsb
    test al, al
    jz .halt
    mov ah, 0x0E
    int 0x10
    jmp .print

.halt:
    jmp $

msg db 'Hello from kernel (2nd sector)!', 0x0D, 0x0A, 0

times 512-($-$$) db 0
```
</details>

## 動作確認

### QEMU
以下の手順で確認しました。
```
nasm boot.asm -o boot.bin          # コンパイル
nasm kernel.asm -o kernel.bin
# 20MBの仮想HDDを作成
dd if=/dev/zero of=os.img bs=1M count=20

# boot.bin及びkernel.binを先頭(0バイト目)から純に書き込む。
dd if=boot.bin of=os.img conv=notrunc
dd if=kernel.bin of=os.img conv=notrunc seek=1
qemu-system-i386 -hda os.img       # 起動
```
![图片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/26e32aae-8989-4de4-a72f-2bb91d5e5831.png)


### 8086実機
以下の手順でコンパクトフラッシュへ書き込みます
```
lsblk                                   # CFカードの名前確認
sudo dd if=os.img of=/dev/sdb bs=1M count=1 conv=notrunc
```
実機では失敗しました。IDEに対応していないのでしょうか。
![webwxgetmsgimg.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/6c152519-3858-4809-90cf-fd910317fbd0.jpeg)

## 当日追記
CHS形式でも試して見ましたがそれでも実機では動作しませんでした。
<details>
<summary>boot.asm</summary>

```boot.asm
[bits 16]
[org 0x7C00]

    ; ES:DI = 0000:7E00へ読み込む
    xor ax, ax
    mov es, ax          
    mov di, 0x7E00
                        

    ; 使用可能になるまで待つ
.wait_drive_ready:
    mov dx, 0x1F7       ; 状態レジスタ
    in al, dx
    test al, 0x80       ; BSYビットを確認
    jnz .wait_drive_ready
    test al, 0x40       ; RDYビットを確認
    jz .wait_drive_ready

    ; CHS設定 (シリンダ0, ヘッド0, セクタ1)
    mov dx, 0x1F6       ; ドライブ/ヘッドポート
    mov al, 0xA0        ; 1台目ドライブ, ヘッド0
    out dx, al

    mov dx, 0x1F2       ; セクタカウント
    mov al, 1           ; 1セクタを読み込む
    out dx, al

    mov dx, 0x1F3       ; セクタ番号
    mov al, 2           ; 第2セクタ (LBAの下位8ビット)
    out dx, al

    mov dx, 0x1F4       ; シリンダ下位バイト
    mov al, 0           ; シリンダ0 (LBAの8-15ビット)
    out dx, al

    mov dx, 0x1F5       ; シリンダ上位バイト
    mov al, 0           ; シリンダ0 (LBAの16-23ビット)
    out dx, al

    ; データ転送命令
    mov dx, 0x1F7
    mov al, 0x20
    out dx, al

    ; 読み込み可能になるまで待つ
.wait_data_ready:
    in al, dx
    test al, 0x80       ; BSYビットを確認
    jnz .wait_data_ready
    test al, 0x08       ; DRQビットを確認
    jz .wait_data_ready
    
    mov si, test_msg
    call print_string

    ; 0x1F0から512バイト分読み込む
    mov cx, 256         ; 256次字读取(512字节)
    mov dx, 0x1F0
    rep insw            ; ES:DIへ読み込む
    
    ; 読み込んだコードへ跳ぶ（第2セクタ）
    jmp 0x0000:0x7E00

    jmp $               ; CPU停止

.error:
    jmp $

; 文字列表示
print_string:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp print_string
.done:
    ret

test_msg db "TEST", 0x0D, 0x0A, 0

times 510-($-$$) db 0
dw 0xAA55
```
</details>