「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は「手探りでCUI OS作成に挑む」連載目次を御覧下さい。
目的
将来BIOSを開発にあたりディスク読み込みの実装が必要となるため、IDEの使用を調べながら第一セクタを読み込むプログラムを書いてみます。
その後読み取ったデータをダンプし第一セクタ(ブートローダ)が読み込めているかを確認します。
検証コード
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 0(第1セクタ)を0x1000:0x0000へ読み込む
mov ax, 0x1000
mov es, ax
xor di, di ; ES:DI = 0x1000:0x0000
xor ax, ax ; LBA = 0
call read_sector
jc disk_error ; エラー
; 成功の文言
mov si, success_msg
call print_string
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
%include "readdisk.asm"
readdisk.asm
read_sector:
pusha
push ds
; ===== 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アドレスの指定 =====
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
popa
clc ; 成功
ret
.error:
pop ds
popa
stc ; 失败
ret
補足
「ドライブ/ヘッドレジスタ」設定値 0xE0 の意味
ビット位置 | 値 (0xE0) | 意味 |
---|---|---|
ビット7 | 1 | LBAモード有効化(固定で1にする必要あり) |
ビット6 | 1 | 固定値(IDE仕様で1に設定) |
ビット5 | 1 | 固定値(IDE仕様で1に設定) |
ビット4 | 0 | ドライブ選択(0=1台目、1=2台目) |
ビット3-0 | 0000 | LBAアドレスの24-27ビット目(後でor al, bh で設定される予定) |
LBAのアドレスを設定するポート
ポート番号 | 対応するLBAのビット範囲 | 使用レジスタ | 値の例 |
---|---|---|---|
0x1F3 | LBA 0-7(下位8ビット) | bl | bl = 0x12 |
0x1F4 | LBA 8-15(中位8ビット) | bh | bh = 0x34 |
0x1F5 | LBA 16-23(上位8ビット) | cl | cl = 0x56 |
0x1F6 | LBA 24-27(最上位4ビット) | bh(別の値) | bh = 0x07 |
検証
操作手順は以下の通りです。
nasm -f bin boot.asm -o boot.bin # コンパイル
qemu-system-i386 -hda boot.bin -monitor stdio # HDDとして起動
(qemu)pmemsave 0x10000 512 dump.bin # 0x10000から512バイト分をダンプ
hexdump -v -C dump.bin # 取り出したバイナリを確認
test@test-ThinkPad-X280:~/kaihatsu/test4$ hexdump -v -C dump.bin
00000000 31 c0 8e d8 8e c0 8e d0 bc 00 7c be 3b 7c e8 1e |1.........|.;|..|
00000010 00 b8 00 10 8e c0 31 ff 31 c0 e8 5a 00 72 08 be |......1.1..Z.r..|
00000020 48 7c e8 0a 00 eb fe be 64 7c e8 02 00 eb fe ac |H|......d|......|
00000030 08 c0 74 06 b4 0e cd 10 eb f5 c3 4c 6f 61 64 69 |..t........Loadi|
00000040 6e 67 2e 2e 2e 0d 0a 00 53 65 63 74 6f 72 20 72 |ng......Sector r|
00000050 65 61 64 20 73 75 63 63 65 73 73 66 75 6c 6c 79 |ead successfully|
00000060 21 0d 0a 00 44 69 73 6b 20 72 65 61 64 20 65 72 |!...Disk read er|
00000070 72 6f 72 21 0d 0a 00 60 1e ba f7 01 b9 ff ff ec |ror!...`........|
00000080 a8 80 74 05 49 75 f8 eb 49 ba f6 01 b0 e0 08 f8 |..t.Iu..I.......|
00000090 24 ef ee ba f2 01 b0 01 ee ba f3 01 88 d8 ee ba |$...............|
000000a0 f4 01 88 f8 ee ba f5 01 88 c8 ba f7 01 b0 20 ee |.............. .|
000000b0 b9 ff ff ec a8 80 75 fb a8 08 75 05 49 75 f4 eb |......u...u.Iu..|
000000c0 11 a8 01 75 0d b9 00 01 ba f0 01 fc f3 6d 1f 61 |...u.........m.a|
000000d0 f8 c3 1f 61 f9 c3 00 00 00 00 00 00 00 00 00 00 |...a............|
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200
最後に
正しく読み込めるようになりました。
次はこの関数を利用して、INT13Hを使わずに第2セクタを読み込むブートローダを書いてみる予定です。
当日追記
今回書いたプログラムを利用して、BIOSを呼び出さないブートローダを書いてみました。
LBAアドレスの指定方法に問題があり第0セクタ目以外が読み込めない問題があったので併せて修正しました。
https://qiita.com/earthen94/items/edb134dbbe5c632266fc