Reputation: 107
I've been working on a hobby bootloader and kernel, but the bootloader never finds the kernel no matter what. Here is the bootloader:
%define BUFFER_SEG 0x2000
%define BUFFER_OFF 0x0000
%define LOAD_SEG 0x1000
%define LOAD_OFF 0x0000
[bits 16]
[org 0x7c00]
jmp short start
nop
;DISK DESCRIPTION(BIOS PARAMETER BLOCK)
OEMLabel db "BOOT "
BytesPerSector dw 512
SectorsPerCluster db 1
ReservedForBoot dw 1
NumberOfFats db 2
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880
MediumByte db 0F0h
SectorsPerFat dw 9
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2
HiddenSectors dd 0
LargeSectors dd 0
DriveNo dw 0
Signature db 0x29
VolumeID dd 00000000h
VolumeLabel db "myOS "
FileSystem db "FAT12 "
;BOOTLOADER
start:
xor ax, ax
mov ds, ax
cli
mov ss, ax
mov sp, 0x7c00
cld
clc
sti
mov [drive], dl
load_root:
mov ax, 19
call lba_to_hts
mov ah, 2
mov al, 14
mov si, BUFFER_SEG
mov es, si
mov bx, BUFFER_OFF
int 13h
jc reset
mov si, load_root_str
call print
search_file:
mov di, BUFFER_OFF
mov cx, word [RootDirEntries]
xor ax, ax
.loop_search:
xchg cx, dx
mov si, filename
mov cx, 11
rep cmpsb
je file_found
add ax, 32
mov di, BUFFER_OFF
add di, ax
xchg dx, cx
loop .loop_search
jmp file_not_found
file_found:
mov ax, word [es:di+15]
mov [cluster], ax
mov ax, 1
call lba_to_hts
mov di, BUFFER_OFF
mov bx, di
mov ah, 2
mov al, 9
load_FAT:
mov si, FAT_str
call print
int 13h
jnc load_file
call reset
jnc load_FAT
jmp disk_error
load_file:
mov si, load_file_str
call print
mov ax, LOAD_SEG
mov es, ax
xor bx, bx
mov ah, 2
mov al, 1
.load_sector:
mov ax, word [cluster]
add ax, 31
call lba_to_hts
mov ax, LOAD_SEG
mov es, ax
mov bx, word [pointer]
pop ax
push ax
;stc
int 13h
jnc next_cluster
call reset
jmp .load_sector
next_cluster:
mov ax, [cluster]
xor dx, dx
mov bx, 3
mul bx
mov bx, 2
div bx
mov si, BUFFER_OFF
add si, ax
mov ax, word [ds:si]
or dx, dx
jz .even
.odd:
shr ax, 4
jmp short finish_load
.even:
and ax, 0FFFh
finish_load:
mov word [cluster], ax
cmp ax, 0FF8h
jae .jump_to_file
add word [pointer], 512
jmp next_cluster
.jump_to_file:
pop ax
mov dl, byte [drive]
jmp LOAD_SEG:LOAD_OFF
;SUBROUTINES
file_not_found:
mov si, not_found_str
call print
jmp reboot
print:
pusha
mov ah, 0x0E
.next:
lodsb
cmp al,0
je .done
int 0x10
jmp .next
.done:
popa
ret
lba_to_hts:
push ax
push bx
mov bx, ax
xor dx, dx
div word [SectorsPerTrack]
add dl, 1
mov cl, dl
mov ax, bx
xor dx, dx
div word [SectorsPerTrack]
xor dx, dx
div word [Sides]
mov dh, dl
mov ch, al
pop ax
pop bx
mov dl, [drive]
ret
reset:
mov ah, 0
int 13h ;reset disk
jc disk_error ;if failed jump to search fail
ret
disk_error:
mov si, disk_error_str
call print
reboot:
mov si, reboot_pmpt
call print
mov ax, 0
int 16h
mov ax, 0
int 19h
;DATA
load_root_str db 'Loading Root',13,10,0
disk_error_str db 'Disk Error!',13,10,0
reboot_pmpt db 'PRESS A KEY TO REBOOT',13,10,0
not_found_str db 'KERNEL NOT FOUND',13,10,0
FAT_str db 'Loading FAT',13,10,0
load_file_str db 'Loading KERNEL',13,10,0
drive dw 0
cluster dw 0
pointer dw 0
filename db 'KERNEL BIN',0
;PADDING AND SIGNATURE
times (510-($-$$)) db 0x00
dw 0AA55h
Here is the Kernel:
[bits 16] ;16-bit binary format
;VECTORS
os_vectors:
jmp os_main
;KERNEL
os_main:
cli ;clear interrupts
mov ss, ax ;set stack segment and pointer
mov sp, 0FFFFh
sti ;restore interrupts
cld ;set RAM direction(for strings)
mov ax, 1000h ;set registers to match kernel location
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov si, hello ;print welcome
call print_string
hlt
;SUBROUTINES
print_string:
mov ah, 0x0e
.next_char:
lodsb
cmp al,0
je .done_print
int 0x10
jmp .next_char
.done_print:
ret
;DATA
hello db 'Hello',0
;PADDING
times (512-($-$$)) db 0x00
I padded the rest of the sector out, since I heard some emulators don't read it correctly if it isn't one full sector-size. I use the commands
#! bin/bash
cd image
hdiutil create -fs MS-DOS -sectors 2880 floppy
cd ../system
nasm -f bin boot.asm -o boot.bin
nasm -f bin kernel.asm -o kernel.bin
cd ..
dd conv=notrunc if=system/boot.bin of=image/floppy.dmg
dev=`hdid -nomount image/floppy.dmg`
sudo mkdir tmp-loop
sudo cp system/kernel.bin tmp-loop/
sudo mount -t msdos ${dev} tmp-loop
diskutil umount tmp-loop
hdiutil detach ${dev}
sudo rm -rf tmp-loop
hdiutil convert image/floppy.dmg -format UDTO -o image/image.iso
to build and then emulate it in qemu. I am doing this on a Macbook Air. When I emulate my bootloader in qemu I always get the string telling me it can't find the kernel. I don't know why nor how to fix this.
Upvotes: 1
Views: 278
Reputation: 47573
For the most part your code has the right idea. The primary problems in the bootloader are in load_file
and next_cluster
. There is also a bug in lba_to_hts
. Your kernel has some bugs that need to be rectified as well.
Consider this a serious recommendation - install a copy of BOCHs and use its debugger rather than QEMU. BOCHs is ideal for bootloaders as it properly deals with 20-bit segment:offset addressing. For any real mode code BOCHs is an excellent tool. Learning to use a debugger properly allows you to see what is in the registers; examine memory, set breakpoints etc. You should be able to discover the bugs identified in this answer with some experience.
The bug in lba_to_hts
can be seen here:
lba_to_hts:
push ax
push bx
...
pop ax
pop bx
You push AX then BX on the stack at the start, but you need to pop them off in reverse order. It should be:
push ax
push bx
...
pop bx
pop ax
In next_cluster
you have a problem with this line:
mov ax, word [ds:si]
You have computed the offset in the FAT (FAT12) table where the next cluster can be found. The problem is that DS is not pointing to the segment where the FAT table is in memory, it is set at 0000h. You can't use:
mov ax, word [es:si]
because you have set ES to the kernel load segment (LOAD_SEG
= 1000h). You can choose to save the DS register (push on stack), load DS with BUFFER_SEG
. Then you could use:
mov ax, word [ds:si]
You would then have to restore DS when next_cluster
is finished by POPing the old value from the stack. It should be noted that mov ax, word [ds:si]
is the same as mov ax, word [si]
except that the DS prefix will be output on the instruction unnecessarily. If the registers in the memory operand don't include BP then the memory access is done via DS implicitly, otherwise it is implicitly SS. Rule of thumb: don't add unnecessary overrides as they will increase code size.
I don't recommend this approach. The simplest way of fixing this is to place BUFFER_OFF
in the same segment as the bootloader (segment 0000h). There is 32KiB of free memory from 0000h:8000h through to 0000h:0ffffh. If you modify your code to load the FAT and the root directory structures into 0000h:8000h then you can access the bootloader data, the FAT structure and the root directory entries via DS. When you load the kernel you can switch ES to LOAD_SEG
.
There is another problem in this code:
finish_load:
mov word [cluster], ax
cmp ax, 0FF8h
jae .jump_to_file
add word [pointer], 512
jmp next_cluster
You check to see if you have reached the last cluster for the file by comparing it with 0FF8h. If it is less than 0FF8h you add 512 to [pointer] to advance to the next offset in the buffer to read to. The problem is that jmp next_cluster
doesn't go back to read the next cluster! jmp next_cluster
should be jmp load_sector
In load_file
you have this code:
load_file:
mov si, load_file_str
call print
mov ax, LOAD_SEG
mov es, ax
xor bx, bx
mov ah, 2
mov al, 1
.load_sector:
mov ax, word [cluster]
add ax, 31
call lba_to_hts
mov ax, LOAD_SEG
mov es, ax
mov bx, word [pointer]
pop ax
push ax
;stc
int 13h
jnc next_cluster
call reset
jmp .load_sector
next_cluster:
Just before the .load_sector
label you set the AX and BX registers for the Int 13h
BIOS call. Unfortunately you clobber AX and BX in the lines just after the label .load_sector:
. There is also an unusual POP/PUSH in the middle that makes no sense. You could alter this section of code to:
load_file:
mov si, load_file_str
call print
mov ax, LOAD_SEG ; ES=load segment for kernel
mov es, ax
load_sector:
mov ax, word [cluster] ; Get cluster number to read
add ax, 33-2 ; Add 31 to cluster since FAT data area
; starts at Logical Block Address (LBA) 33
; and we need to subtract 2 since valid
; cluster numbers start at 2
call lba_to_hts
mov bx, word [pointer] ; BX=Current offset in buffer to read to
mov ax, 201h ; AH=2 is read, AL=1 read 1 sector
;stc
int 13h
jnc next_cluster
call reset
jmp load_sector
next_cluster:
The revised version of the code would look like:
%define BUFFER_OFF 0x8000
%define BUFFER_SEG 0x0000
%define LOAD_SEG 0x1000
%define LOAD_OFF 0x0000
[bits 16]
[org 0x7c00]
jmp short start
nop
;DISK DESCRIPTION(BIOS PARAMETER BLOCK)
OEMLabel db "BOOT "
BytesPerSector dw 512
SectorsPerCluster db 1
ReservedForBoot dw 1
NumberOfFats db 2
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880
MediumByte db 0F0h
SectorsPerFat dw 9
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2
HiddenSectors dd 0
LargeSectors dd 0
DriveNo dw 0
Signature db 0x29
VolumeID dd 00000000h
VolumeLabel db "myOS "
FileSystem db "FAT12 "
;BOOTLOADER
start:
xor ax, ax
mov ds, ax
cli
mov ss, ax
mov sp, 0x7c00
sti
cld
mov [drive], dl
mov si, BUFFER_SEG ; ES=buffer segment. Only has to be set once
mov es, si
mov bx, BUFFER_OFF
load_root:
mov ax, 19 ; Root directory starts at LBA 19
call lba_to_hts
mov ax, (2<<8) | 14 ; Root directory for this media fits in 14 sectors
; Combine 2 moves (AH/AL) into one
; same as 'mov ah, 2' and 'mov al, 14'
int 13h
jc reset
mov si, load_root_str
call print
search_file:
mov di, BUFFER_OFF
mov cx, word [RootDirEntries]
xor ax, ax
.loop_search:
xchg cx, dx
mov si, filename
mov cx, 11
rep cmpsb
je file_found
add ax, 32
mov di, BUFFER_OFF
add di, ax
xchg dx, cx
loop .loop_search
jmp file_not_found
file_found:
mov ax, word [di+15] ; Buffer and Bootloader now in same segment DS
; Don't need ES:
mov [cluster], ax
mov ax, 1
call lba_to_hts
mov bx, BUFFER_OFF
mov ax, (2<<8) | 9 ; Combine 2 moves (AH/AL) into one
; same as 'mov ah, 2' and 'mov al, 9'
load_FAT:
mov si, FAT_str
call print
int 13h
jnc load_file
call reset
jnc load_FAT
jmp disk_error
load_file:
mov si, load_file_str
call print
mov ax, LOAD_SEG ; ES=load segment for kernel
mov es, ax
load_sector:
mov ax, word [cluster] ; Get cluster number to read
add ax, 33-2 ; Add 31 to cluster since FAT data area
; starts at Logical Block Address (LBA) 33
; and we need to subtract 2 since valid
; cluster numbers start at 2
call lba_to_hts
mov bx, word [pointer] ; BX=Current offset in buffer to read to
mov ax, (2<<8) | 1 ; AH=2 is read, AL=1 read 1 sector
; Combine 2 moves (AH/AL) into one
; same as 'mov ah, 2' and 'mov al, 1'
int 13h
jnc next_cluster
call reset
jmp load_sector
next_cluster:
mov ax, [cluster]
xor dx, dx
mov bx, 3
mul bx
mov bx, 2
div bx
mov si, BUFFER_OFF
add si, ax
mov ax, word [si]
or dx, dx
jz .even
.odd:
shr ax, 4
jmp short finish_load
.even:
and ax, 0FFFh
finish_load:
mov word [cluster], ax
cmp ax, 0FF8h
jae .jump_to_file
add word [pointer], 512 ; We haven't reached end of kernel. Add 512 for next read
jmp load_sector ; Go back and load the next sector
.jump_to_file:
mov dl, byte [drive]
jmp LOAD_SEG:LOAD_OFF
;SUBROUTINES
file_not_found:
mov si, not_found_str
call print
jmp reboot
print:
pusha
mov ah, 0x0E
.next:
lodsb
cmp al,0
je .done
int 0x10
jmp .next
.done:
popa
ret
lba_to_hts:
push ax
push bx
mov bx, ax
xor dx, dx
div word [SectorsPerTrack]
add dl, 1
mov cl, dl
mov ax, bx
xor dx, dx
div word [SectorsPerTrack]
xor dx, dx
div word [Sides]
mov dh, dl
mov ch, al
pop bx ; Need to POP in reverse order to the pushes!
pop ax
mov dl, [drive]
ret
reset:
mov ah, 0
int 13h ;reset disk
jc disk_error ;if failed jump to search fail
ret
disk_error:
mov si, disk_error_str
call print
reboot:
mov si, reboot_pmpt
call print
mov ax, 0
int 16h
mov ax, 0
int 19h
;DATA
load_root_str db 'Loading Root',13,10,0
disk_error_str db 'Disk Error!',13,10,0
reboot_pmpt db 'PRESS A KEY TO REBOOT',13,10,0
not_found_str db 'KERNEL NOT FOUND',13,10,0
FAT_str db 'Loading FAT',13,10,0
load_file_str db 'Loading KERNEL',13,10,0
drive dw 0
cluster dw 0
pointer dw 0
filename db 'KERNEL BIN',0
;PADDING AND SIGNATURE
times (510-($-$$)) db 0x00
dw 0AA55h
You improperly set up the segment registers and the stack should be on an even byte boundary. If you set SP to zero the first push will subtract 2 from SP placing data at 0000-2=0fffe at the top of the segment. I would simply set ES=DS=FS=GS=SS to CS. Secondly when you do a HLT
instruction it only halts until the next interrupt and then falls into the instruction after the HLT
. If you want to HLT
indefinitely turn off interrupts with CLI
first. It is still a good idea to put HLT
in a loop in case you received a Non-Maskable Interrupt (NMI) that isn't masked by CLI
.
Your kernel could be altered in this way:
[bits 16] ;16-bit binary format
;VECTORS
os_vectors:
jmp os_main
;KERNEL
os_main:
mov ax, cs ;CS is segment where we were loaded
cli ;clear interrupts
mov ss, ax ;set stack segment and pointer
xor sp, sp ;SP=0. First push will wrap SP to 0fffeh
sti ;restore interrupts
cld ;set RAM direction(for strings)
mov ds, ax ;DS=ES=FS=GS=CS
mov es, ax
mov fs, ax
mov gs, ax
mov si, hello ;print welcome
call print_string
cli ;Turn off interrupts so that HLT doesn't continue
;when an interrupt occurs
.hlt_loop:
hlt
jmp .hlt_loop ; Infinite loop to avoid NMI dropping us into the code of
; print_string
;SUBROUTINES
print_string:
mov ah, 0x0e
.next_char:
lodsb
cmp al,0
je .done_print
int 0x10
jmp .next_char
.done_print:
ret
;DATA
hello db 'Hello',0
There are a number of inefficiencies in your code but I'll address some of the bigger ones. Although your next_cluster
code works it uses more registers than it needs to, and it is longer encoding wise in memory. In order to multiply any value by 3 you can multiply the value by 2 and add the original value to that. The formula would look like:
valtimes3 = (value * 2) + value
This is important because to multiply a value in a register by 2 you only need to shift the bits left 1 bit with the SHL
instruction. Division by 2 is done by right shifting the bits in a register by 1 with SHR
instruction. The advantage with SHR
is that the bit you shift out of the register is placed in the Carry Flag (CF). If CF is set then the value was odd, and if it is clear then the number was even. The next_cluster
code could look like:
next_cluster:
mov bx, [cluster] ; BX = current cluster number
mov ax, bx ; AX = copy of cluster number
shl bx, 1 ; BX = BX * 2
add bx, ax ; BX = BX + AX (BX now contains BX * 3)
shr bx, 1 ; Divide BX by 2
mov ax, [bx+BUFFER_OFF] ; Get cluster entry from FAT table
jnc .even ; If carry not set by SHR then result was even
.odd:
shr ax, 4 ; If cluster entry is odd then cluster number is AX >> 4
jmp short finish_load
.even:
and ah, 0Fh ; If cluster entry is even then cluster number is AX & 0fffh
; We just need to and AH with 0fh to achieve the same result
finish_load:
You can simplify the lba_to_hts
by rearranging the standard calculation. I have written a previous Stackoverflow answer on doing just that and there is a drop in replacement using the revised formula:
lba_to_chs
function that takes an LBA and converts it to CHS and only works for well known IBM compatible floppy disk formats.; Function: lba_to_chs ; Description: Translate Logical block address to CHS (Cylinder, Head, Sector). ; Works **ONLY** for well known IBM PC compatible **floppy disk formats**. ; ; Resources: http://www.ctyme.com/intr/rb-0607.htm ; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion ; https://stackoverflow.com/q/45434899/3857942 ; Sector = (LBA mod SPT) + 1 ; Head = (LBA / SPT) mod HEADS ; Cylinder = (LBA / SPT) / HEADS ; ; Inputs: SI = LBA ; Outputs: DL = Boot Drive Number ; DH = Head ; CH = Cylinder ; CL = Sector ; ; Notes: Output registers match expectation of Int 13h/AH=2 inputs ; lba_to_chs: push ax ; Preserve AX mov ax, si ; Copy 16-bit LBA to AX div byte [SectorsPerTrack] ; 16-bit by 8-bit DIV : LBA / SPT mov cl, ah ; CL = S = LBA mod SPT inc cl ; CL = S = (LBA mod SPT) + 1 xor ah, ah ; Upper 8-bit of 16-bit value set to 0 for DIV div byte [NumberOfHeads] ; 16-bit by 8-bit DIV : (LBA / SPT) / HEADS mov ch, al ; CH = C = (LBA / SPT) / HEADS mov dh, ah ; DH = H = (LBA / SPT) mod HEADS mov dl, [boot_device] ; boot device, not necessary to set but convenient pop ax ; Restore scratch register ret
You just need to change the name of the function to lba_to_hts
; change NumberOfHeads
to Sides
; change boot_drive
to drive
; and alter the code so the LBA is passed in via AX rather than SI. AX doesn't even need to be preserved the way your existing code is written.
When you find that you need to read another cluster into memory you effectively add 512 to [pointer]
to go to the next position in memory. The problem is that you limit yourself to a kernel that is 65536 bytes long. Once you reach 128 512-byte sectors read into memory you will exceed 65536 (128*512=65536). [pointer]
will wrap and start back at 0 and you'll overwrite the part of the kernel you have already read. You can fix this issue by always doing a disk read to offset 0 (BX=0) and add 32 to ES. When you add 1 to a segment register you advance 16 bytes (a paragraph) in memory. If you want to advance 512 bytes you add 32 to ES (32*16=512). In your load_sectors
code you can change:
call lba_to_hts
mov bx, word [pointer] ; BX=Current offset in buffer to read to
to:
call lba_to_hts
xor bx, bx
In Finish_load
you can change:
finish_load:
mov word [cluster], ax
cmp ax, 0FF8h
jae .jump_to_file
add word [pointer], 512 ; We haven't reached end of kernel. Add 512 for next read
jmp load_sector ; Go back and load the next sector
.jump_to_file:
mov dl, byte [drive]
jmp LOAD_SEG:LOAD_OFF
to:
finish_load:
mov word [cluster], ax
cmp ax, 0FF8h
jae .jump_to_file
mov ax, es
add ax, 32 ; Increasing segment by 1 advances 16 bytes (paragraph)
; in memory. Adding 32 is same advancing 512 bytes (32*16)
mov es, ax ; Advance ES to point at next 512 byte block to read into
jmp load_sector ; Go back and load the next sector
.jump_to_file:
mov dl, byte [drive]
jmp LOAD_SEG:LOAD_OFF
A version of boot.asm
that implements these changes could look like:
%define BUFFER_OFF 0x8000
%define BUFFER_SEG 0x0000
%define LOAD_SEG 0x1000
%define LOAD_OFF 0x0000
[bits 16]
[org 0x7c00]
jmp short start
nop
;DISK DESCRIPTION(BIOS PARAMETER BLOCK)
OEMLabel db "BOOT "
BytesPerSector dw 512
SectorsPerCluster db 1
ReservedForBoot dw 1
NumberOfFats db 2
RootDirEntries dw 224 ; Number of entries in root dir
; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors dw 2880
MediumByte db 0F0h
SectorsPerFat dw 9
SectorsPerTrack dw 18 ; Sectors per track (36/cylinder)
Sides dw 2
HiddenSectors dd 0
LargeSectors dd 0
DriveNo dw 0
Signature db 0x29
VolumeID dd 00000000h
VolumeLabel db "myOS "
FileSystem db "FAT12 "
;BOOTLOADER
start:
xor ax, ax
mov ds, ax
cli
mov ss, ax
mov sp, 0x7c00
sti
cld
mov [drive], dl
mov si, BUFFER_SEG ; ES=buffer segment. Only has to be set once
mov es, si
mov bx, BUFFER_OFF
load_root:
mov ax, 19 ; Root directory starts at LBA 19
call lba_to_hts
mov ax, (2<<8) | 14 ; Root directory for this media fits in 14 sectors
; Combine 2 moves (AH/AL) into one
; same as 'mov ah, 2' and 'mov al, 14'
int 13h
jc reset
mov si, load_root_str
call print
search_file:
mov di, BUFFER_OFF
mov cx, word [RootDirEntries]
xor ax, ax
.loop_search:
xchg cx, dx
mov si, filename
mov cx, 11
rep cmpsb
je file_found
add ax, 32
mov di, BUFFER_OFF
add di, ax
xchg dx, cx
loop .loop_search
jmp file_not_found
file_found:
mov ax, word [di+15] ; Buffer and Bootloader now in same segment DS
; Don't need ES:
mov [cluster], ax
mov ax, 1
call lba_to_hts
mov bx, BUFFER_OFF
mov ax, (2<<8) | 9 ; Combine 2 moves (AH/AL) into one
; same as 'mov ah, 2' and 'mov al, 9'
load_FAT:
mov si, FAT_str
call print
int 13h
jnc load_file
call reset
jnc load_FAT
jmp disk_error
load_file:
mov si, load_file_str
call print
mov ax, LOAD_SEG ; ES=load segment for kernel
mov es, ax
load_sector:
mov ax, word [cluster] ; Get cluster number to read
add ax, 33-2 ; Add 31 to cluster since FAT data area
; starts at Logical Block Address (LBA) 33
; and we need to subtract 2 since valid
; cluster numbers start at 2
call lba_to_hts
xor bx, bx ; Always read a kernel sector to offset 0
mov ax, (2<<8) | 1 ; AH=2 is read, AL=1 read 1 sector
; Combine 2 moves (AH/AL) into one
; same as 'mov ah, 2' and 'mov al, 1'
int 13h
jnc next_cluster
call reset
jmp load_sector
next_cluster:
mov bx, [cluster] ; BX = current cluster number
mov ax, bx ; AX = copy of cluster number
shl bx, 1 ; BX = BX * 2
add bx, ax ; BX = BX + AX (BX now contains BX * 3)
shr bx, 1 ; Divide BX by 2
mov ax, [bx+BUFFER_OFF] ; Get cluster entry from FAT table
jnc .even ; If carry not set by SHR then result was even
.odd:
shr ax, 4 ; If cluster entry is odd then cluster number is AX >> 4
jmp short finish_load
.even:
and ah, 0Fh ; If cluster entry is even then cluster number is AX & 0fffh
; We just need to and AH with 0fh to achieve the same result
finish_load:
mov word [cluster], ax
cmp ax, 0FF8h
jae .jump_to_file
mov ax, es
add ax, 32 ; Increasing segment by 1 advances 16 bytes (paragraph)
; in memory. Adding 32 is same advancing 512 bytes (32*16)
mov es, ax ; Advance ES to point at next 512 byte block to read into
jmp load_sector ; Go back and load the next sector
.jump_to_file:
mov dl, byte [drive]
jmp LOAD_SEG:LOAD_OFF
;SUBROUTINES
file_not_found:
mov si, not_found_str
call print
jmp reboot
print:
pusha
mov ah, 0x0E
.next:
lodsb
cmp al,0
je .done
int 0x10
jmp .next
.done:
popa
ret
; Function: lba_to_hts
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
; Works ONLY for well known IBM PC compatible floppy disk formats.
;
; Resources: http://www.ctyme.com/intr/rb-0607.htm
; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
; https://stackoverflow.com/q/45434899/3857942
; Sector = (LBA mod SPT) + 1
; Head = (LBA / SPT) mod HEADS
; Cylinder = (LBA / SPT) / HEADS
;
; Inputs: AX = LBA
; Outputs: DL = Boot Drive Number
; DH = Head
; CH = Cylinder
; CL = Sector
;
; Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_hts:
div byte [SectorsPerTrack] ; 16-bit by 8-bit DIV : LBA / SPT
mov cl, ah ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
xor ah, ah ; Upper 8-bit of 16-bit value set to 0 for DIV
div byte [Sides] ; 16-bit by 8-bit DIV : (LBA / SPT) / HEADS
mov ch, al ; CH = C = (LBA / SPT) / HEADS
mov dh, ah ; DH = H = (LBA / SPT) mod HEADS
mov dl, [drive] ; boot device, not necessary to set but convenient
ret
reset:
mov ah, 0
int 13h ;reset disk
jc disk_error ;if failed jump to search fail
ret
disk_error:
mov si, disk_error_str
call print
reboot:
mov si, reboot_pmpt
call print
mov ax, 0
int 16h
mov ax, 0
int 19h
;DATA
load_root_str db 'Loading Root',13,10,0
disk_error_str db 'Disk Error!',13,10,0
reboot_pmpt db 'PRESS A KEY TO REBOOT',13,10,0
not_found_str db 'KERNEL NOT FOUND',13,10,0
FAT_str db 'Loading FAT',13,10,0
load_file_str db 'Loading KERNEL',13,10,0
drive dw 0
cluster dw 0
filename db 'KERNEL BIN',0
;PADDING AND SIGNATURE
times (510-($-$$)) db 0x00
dw 0AA55h
When run in QEMU this is what is displayed:
Upvotes: 3