我正在为 x86 BIOS 开发引导加载程序。在我的第一阶段引导加载程序(MBR)中,我需要从磁盘读取2880个扇区(或更多),然后跳转到放置在磁盘第二个扇区的第二阶段引导加载程序。第二阶段将使用 FAT16 加载内核文件,我稍后将实现它。
该函数适用于低于 65 的 LBA 值,这足以加载用 C 编写的第二阶段。
我已将 BPB_SecPerTrk 定义为 18,将 BPB_NumHeads 定义为 2
这是我的汇编函数代码:
; convert LBA to CHS
; input: si = LBA
; output: ch = cylinder, dh = head, cl = sector
lba_to_chs:
push bx ; save bx
mov ax, si ; load LBA address into ax
; Calculate sectors (CL)
xor dx, dx ; clear dx
div word [BPB_SecPerTrk] ; ax = LBA / SPT, dx = LBA % SPT
mov cl, dl ; cl = (LBA % SPT) + 1 (sector)
inc cl ; increment cl by 1
; Calculate head (DH)
xor dx, dx ; clear dx
div word [BPB_NumHeads] ; ax = LBA / (SPT * NumHeads), dx = (LBA / SPT) % NumHeads
mov dh, dl ; dh = (LBA / SPT) % NumHeads (head)
; Calculate cylinder (CH)
mov ch, al ; ch = ax (cylinder number, lower 8 bits)
mov al, ah ; al = ah (upper 8 bits of cylinder number)
shl al, 6 ; shift upper 2 bits of cylinder to higher bits
or ch, al ; combine them with lower 8 bits of ch
pop bx ; restore bx
ret
这里也是负责磁盘初始化的函数。
disk_init_lba:
pusha
; check if lba extension is supperted
mov ah, 0x41 ; check extensions
mov bx, 0x55AA ; magic number
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
stc ; DEBUG: implicitly disable reading disk using int 0x13 extensions
jc .lba_ext_not_sup ; if carry flag is set, jump to error handler
jmp .read_lba_ext ; if not, jump to read disk using LBA
.read_lba_ext:
mov si, DAPACK ; load DAP address to si
mov ah, 0x42 ; extended read function
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
jc .fail ; if carry flag is set, jump to error handler
jmp .ok ; if not, jump to success handler
.lba_ext_not_sup:
call print_disk_lba_sup_fail ; print failure message
jmp .read_lba_via_chs ; jump to read disk using CHS
.read_lba_via_chs:
clc ; clear carry flag if for some reason it was set
xor si, si ; LBA = 0
xor di, di ; set di to 0
mov bx, START_STAGE1 ; buffer for sector
jmp .loop ; jump to loop
.loop:
inc si ; increment LBA
add bx, 0x200 ; next sector buffer
call lba_to_chs ; convert LBA to CHS
mov ah, 0x02 ; read disk BIOS function
mov al, 0x01 ; number of sectors to read
mov dl, 0x80 ; disk number 0
int 0x13 ; call BIOS
jc .retry ; if carry flag is set, jump to error handler
; FIXME: reading LBAs above 65
; TODO: read up to 1.44 MB (2879 sectors)
cmp si, 65 ; check if we read enough sectors to fill 1.44 MB
jle .loop ; if true read next sector
jmp .ok ; if not, jump to success handler
.retry:
inc di ; increment di
cmp di, 3 ; check if we tried 3 times
jne .loop ; if not, retry
jmp .fail ; if yes, jump to error handler
.fail:
call print_disk_read_fail ; print failure message
jmp .exit ; jump to exit
.ok:
call print_disk_read_ok ; print success message
jmp .exit ; jump to exit
.exit:
popa
ret
我认为这与代码中某些地方的值溢出有关。
@ecm 发现柱面编号的 2 个最高有效位属于 CL 寄存器(位 6 和 7),因此将
or ch, al
更改为 or cl, al
,或者考虑使用下一个较短版本的转换代码:
; IN (si) OUT (cx,dh) MOD (ax,dl)
lba_to_chs:
mov ax, si ; LBA
xor dx, dx
div word [BPB_SecPerTrk]
mov cx, dx
inc cx ; Sector
cwd
div word [BPB_NumHeads]
mov dh, dl ; Head
shl ah, 6
xchg al, ah
or cx, ax ; Cylinder
ret
需要重复磁盘操作的情况并不罕见。这就是 DI 寄存器中重试次数为 3 的原因。然而,您所做的是允许所有扇区一起重试 3 次,而您实际上应该允许每个扇区重试几次。
但更糟糕的是,您根本
不重试同一个扇区! “增量 LBA”和“下一个扇区缓冲区”操作在重试时不得干预。
clc ; clear carry flag if for some reason it was set
xor si, si ; LBA = 0
不需要这个clc
,后面的
xor si, si
无论如何都会清除进位标志。
disk_init_lba:
pusha
...
.read_lba_via_chs:
xor si, si ; LBA = 0
mov bx, 0x7C00 ; buffer for sector
.NextSector:
inc si ; increment LBA
add bx, 0x200 ; next sector buffer
mov di, 3 ; Tries per sector
.NextTry:
call lba_to_chs ; convert LBA to CHS
mov ax, 0x0201 ; read 1 sector
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
jc .retry
; FIXME: reading LBAs above 65
; TODO: read up to 1.44 MB (2879 sectors)
cmp si, 65 ; check if we read enough sectors
jbe .NextSector
jmp .ok ; if not, jump to success handler
.retry:
dec di ; more tries?
jnz .NextTry ; yes
.fail:
call print_disk_read_fail ; print failure message
jmp .exit
.ok:
call print_disk_read_ok ; print success message
.exit:
popa
ret
; FIXME:读取 65 以上的 LBA不要修改 BX,而是保持其固定并将 ES 段寄存器前进 32 (512 / 16)。
disk_init_lba:
pusha
push es
...
.read_lba_via_chs:
xor si, si ; LBA = 0
mov bx, 0x7E00 ; buffer for sector
.NextSector:
inc si ; increment LBA
mov ax, es ; next sector buffer ES:BX
add ax, 32
mov es, ax
mov di, 3 ; Tries per sector
.NextTry:
call lba_to_chs ; convert LBA to CHS
mov ax, 0x0201 ; read 1 sector
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
jc .retry
cmp si, 65 ; check if we read enough sectors
jbe .NextSector
jmp .ok
.retry:
dec di ; more tries?
jnz .NextTry ; yes
.fail:
call print_disk_read_fail ; print failure message
jmp .exit
.ok:
call print_disk_read_ok ; print success message
.exit:
pop es
popa
ret
; TODO:最多读取 1.44 MB(2879 个扇区)您不能希望读取那么多字节。您当前的代码仅限于使用传统内存,因此最多略小于 655360 字节是可行的...
我想说,对于你的第二阶段来说已经足够了。