I tried to develop a bootloader using this, but when it is run it shows:
disk read error!
If I ignore it, in a later part, it shows me wrong me
"He's making a list, he's checking it twice..."
Your bootloader starts in the real address mode, so it is best to force your assembler in using 16-bit code. You achieve this in NASM by writing [bits 16]
at the top of your program.
When your bootloader starts, BIOS will have placed it at linear address 00007C00h. It can do this in a number of ways with respect to the combination of segment and offset.
When you explicitly wrote [org 0x7C00]
you (kind of) expected this combination to have the segment part equal to zero. But this is by no means an obligation for BIOS! And so it is up to you to set the segment registers (DS, ES, and SS) manually.
The BIOS teletype function that you use in your print_string routine uses BL and BH as parameters. So you should never use the BX register to address your text. Sure, some BIOSes don't use these BL and BH parameters (any more) but do try to develop programs for the biggest audience.
When you initialized the SP register with 0x9000 you effectively set up a stack that could easily, without you noticing it, overwrite the program beneath it! It would be best to choose a combination of SS and SP that satisfies your needs and nothing more. A 4608 bytes stack that stays above the bootsector at 7C00h and ends at 9000h would require: SS=07E0h SP=1200h. To avoid any problems on 8086 hardware it's best to disable interrupts when changing SS:SP.
You used pusha
and popa
instructions. These are not valid instructions on 8086 hardware. When writing robust software, we should test if the hardware is up to the task. But here the simplest solution is to only push/pop single registers.
You have interpreted the return value from the BIOS function that reads from the disk, but you just abort when an incorrect number of sectors were transferred. This is a wrong approach. When BIOS tells you about an incomplete transfer (this can happen if your BIOS is not multitrack enabled) you have to repeat the call for the remaining number of sectors. Obviously some of the parameters will have to be adjusted: next head, maybe next cylinder, and always sector=1. (A perfect solution would involve retrieving the disk geometry from BIOS or reading it from the BPB present on the disk). I assumed basic 1.44 MB floppy operation.
When reading from the disk doesn't succeed first time you should retry it a number of times. Such first time fails are perfectly normal. Five retries is a good value. In between tries you call the BIOS function that resets the diskdrive.
To make sure QEMU can actually read those additional 15 sectors you should pad this file so it has a total length of 16 sectors worth. The text you linked to did this also!
"Putting it all together"
[bits 16]
[org 0x7C00]
KERNEL_OFFSET equ 0x1000
xor ax, ax
mov ds, ax
mov es, ax
mov [BOOT_DRIVE], dl
mov ax, 0x07E0
cli
mov ss, ax
mov sp, 0x1200
sti
mov si, MSG_REAL_MODE
call print_string
call load_kernel
jmp $
print_string:
push ax
push bx
push si
mov bx, 0x0007 ;BL=WhiteOnBlack BH=Display page 0
mov ah, 0x0E ;Teletype function
loop:
mov al, [si]
cmp al, 0
je return
int 0x10
inc si
jmp loop
return:
pop si
pop bx
pop ax
ret
disk_load:
mov [SECTORS], dh
mov ch, 0x00 ;C=0
mov dh, 0x00 ;H=0
mov cl, 0x02 ;S=2
next_group:
mov di, 5 ;Max 5 tries
again:
mov ah, 0x02 ;Read sectors
mov al, [SECTORS]
int 0x13
jc maybe_retry
sub [SECTORS], al ;Remaining sectors
jz ready
mov cl, 0x01 ;Always sector 1
xor dh, 1 ;Next head on diskette!
jnz next_group
inc ch ;Next cylinder
jmp next_group
maybe_retry:
mov ah, 0x00 ;Reset diskdrive
int 0x13
dec di
jnz again
jmp disk_error
ready:
ret
disk_error:
mov si, DISK_ERROR_MSG
call print_string
jmp $
DISK_ERROR_MSG db "Disk read error!", 0
load_kernel:
mov bx, KERNEL_OFFSET
mov dh, 15
mov dl, [BOOT_DRIVE]
call disk_load
ret
; Global variables
BOOT_DRIVE db 0
SECTORS db 0
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55
; 15 sector padding
times 15*256 dw 0xDADA