13 Read the disk

首先要知道 hard disk 長怎樣、如何指定讀取哪個位置的資料,參考Day 3 進入 32 bit 模式並導入 C 語言

使用 BIOS 讀取 hard disk

BIOS 提供我們讀取 hard disk 的 routine,讓我們不用煩惱 chip 跟 bus 等硬體對讀取 hard disk 資料的問題。(BIOS 提供了一層抽象!)

要 call 這個 routine 是 intterupt 0x13 並且將 ah 設為 0x02。另外必須設置幾個 register 的值,好讓 BIOS 知道我們要讀取哪些 sector、要把讀到的資料放到 memory 的哪個位置:

  • dl:第幾個 drive,由 0 開始
  • ch:cylinder 編號
  • dh:磁片的哪一面,由 0 開始
  • cl:第幾個 sector,由 1 開始
  • al:讀取幾個 sector
  • es:預設存放資料的 memory 位置的 segment register
  • bx:預設存放資料的 memory 位置的 offset

BIOS 可能因為各種原因沒有成功讀取資料,因此我們需要處理錯誤的狀況。BIOS 在讀取有錯誤時會將 carry flag set 起來,另外在 al register 會有實際讀取多少 sector 的值。我們可以用以下程式來處理讀取 disk 的相關錯誤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
int 0x13

jc disk_error ; jump if carry flag was set

cmp al, <# of sector expected> ; 與我們預期讀取的 sector 數量比較
jne disk_error ; 不相等時跳到處理 error 的區域

disk_error:
mov bx, DISK_ERROR_MSG
call print_string
jmp $

DISK_ERROR_MSG: db "Disk read error!", 0

read hard disk routine

isk_load.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
disk_load:
push dx

mov ah, 0x02 ; BIOS read sector function
mov al, dh ; Read DH sectors
mov ch, 0x00 ; Select cylinder 0
mov dh, 0x00 ; Select head 0
mov cl, 0x02 ; Start reading from second sector
int 0x13 ; BIOS interrupt

jc disk_error

pop dx ; restore dx from the stack
cmp dh, al ; if al (sectors read) != dh (sectors expected)
jne disk_error ; display error message
ret

disk_error:
mov bx, DISK_ERROR_MSG
call print_string
jmp $

DISK_ERROR_MSG db "Disk read error!", 0

print_hex.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
; prints the value of DX as hex.
print_hex:
pusha

mov ax, 5
loop: ; for (ax = 5; ax >= 2; ax--)
cmp ax, 2
jl end_loop
mov cx, 0
shr dx, 1 ; dx >> 1
jnc end_if_1
add cx, 1 ; if cf == 1, cx += 1
end_if_1:

shr dx, 1 ; dx >> 1
jnc end_if_2
add cx, 2 ; if cf == 1, cx += 2
end_if_2:

shr dx, 1 ; dx >> 1
jnc end_if_3
add cx, 4 ; if cf == 1, cx += 4
end_if_3:

shr dx, 1 ; dx >> 1
jnc end_if_4
add cx, 8 ; if cf == 1, cx += 8
end_if_4:

; only bx can be used as an index register
mov bx, HEX_OUT
add bx, ax ; bx = HEX_OUT + ax

cmp cx, 10
jl less_ten
mov byte [bx], 'a' ; [bx] = 'a'
sub cx, 10 ; cx -= 10
less_ten:
add [bx], cx ; [bx] += cx

sub ax, 1 ; ax--
jmp loop
end_loop:

mov bx, HEX_OUT
call print_string

popa
ret

print_string.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print_string:           ; function name
pusha ; push all register to stack to preserve them

mov ah, 0x0e ; tty mode

loop_start:
cmp byte [bx], 0 ; compare [bx] which is one byte to zero, for null terminating char
je loop_end ; if [bx] == 0, end loop
mov al, [bx] ; move char printed to al
int 0x10 ; call print interrupt
add bx, 1 ; bx + 1, move to next char
jmp loop_start ; run loop body again

loop_end:
popa ; restore all register
ret ; return to callee

boot_sector_load_disk.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[org 0x7c00]

mov [BOOT_DRIVE], dl ; BIOS stores our boot drive in dl, so it's best to remember this for later

mov bp, 0x8000 ; set stack
mov sp, bp

mov bx, 0x9000 ; Load 2 sectors to 0x0000(es):0x9000(bx)
mov dh, 2
mov dl, [BOOT_DRIVE]
call disk_load

mov dx, [0x9000] ; print first loaded word
call print_hex

mov dx, [0x9000 + 512] ; print first word from the second loaded sector
call print_hex

jmp $

%include "print_string.asm"
%include "print_hex.asm"
%include "disk_load.asm"

; global variables
HEX_OUT: db '0x0000', 0
BOOT_DRIVE: db 0

times 510-($-$$) db 0
dw 0xaa55

times 256 dw 0xdada
times 256 dw 0xface