「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は「手探りでCUI OS作成に挑む」連載目次を御覧下さい。
目的
前回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
検証プログラム
boot.asm
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
readdisk.asm
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
kernel.asm
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
動作確認
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 # 起動
8086実機
以下の手順でコンパクトフラッシュへ書き込みます
lsblk # CFカードの名前確認
sudo dd if=os.img of=/dev/sdb bs=1M count=1 conv=notrunc
当日追記
CHS形式でも試して見ましたがそれでも実機では動作しませんでした。
boot.asm
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