I have a function in my code that loads in the FAT and root directory. This function causes some sort of CPU hang on the PCem emulator but not other emulators like QEMU or PCjs. If this is not a bug with PCem, then why would my program act this way?
The FAT loading function:
loadfilesystem:
mov ax,0x0050
mov word [fatseg],ax
mov es,ax
xor bx,bx
mov cx,word [reserved_sects] ;start of fat
mov ax,word [sects_per_fat]
call readsectors ;read fat
;calculate start of root directory on disk
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
add ax,[reserved_sects]
mov cx,ax
push cx ;1
;also calculate where in memory to put the data
xor dx,dx
div bx ;only 1 fat is loaded
mov cl,5
shl ax,cl
add ax,0x50
mov word [rootdirectoryseg],ax
mov es,ax ;the segment
xor bx,bx
mov ax,word [num_rootentries] ;get size of root directory
mov cl,4
shr ax,cl
pop cx ;1
push ax ;1 save size of root directory
call readsectors
The "readsectors" function
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
Entire Program:
cpu 8086
bits 16
jmp short bootstart ;fat 12 entrypoint code
nop
;fat 12 bpb
oem_label db "OS_BOOT " ;oem label (8 bytes)
bytes_per_sect dw 512 ;bytes per sector
sects_per_cluster db 1 ;sectors per cluster
reserved_sects dw 1 ;reserved sectors
num_fats db 2 ;num of fats
num_rootentries dw 320 ;num of root entries
sect_count dw 320 ;sector count
media_type db 0xfe ;media type (0xfe = 5.25 inch, 160kb)
sects_per_fat dw 1 ;sects per fat
sects_per_track dw 8 ;sects per track
num_heads dw 1 ;num of heads
hidden_sects dd 0 ;hidden sectors
large_sects dd 0 ;large sectors when the disk has more than 65535 sectors
drive_num dw 0 ;drive number
signature db 0x28 ;floppy signature
volume_id dd 0 ;volumeid
volume_label db "OS_BOOT51/4" ;volume label (11 bytes)
file_system db "FAT12 " ;file system (8 bytes)
bootstart:
cli ;clear interrupts until they can be used
cld ;clear direction flag for text and other
mov ax,0x07c0
mov ds,ax ;data segment initialisation
mov byte [drive_num],dl ;save drive number stored in dl by the bios
mov ax,0x6c0 ;4096 bytes below bootsector
mov ss,ax
mov sp,0x1000
sti
loadfilesystem:
mov ax,0x0050
mov word [fatseg],ax
mov es,ax
xor bx,bx
mov cx,word [reserved_sects] ;start of fat
mov ax,word [sects_per_fat]
call readsectors ;read fat
;calculate start of root directory on disk
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
add ax,[reserved_sects]
mov cx,ax
push cx ;1
;also calculate where in memory to put the data
xor dx,dx
div bx ;only 1 fat is loaded
mov cl,5
shl ax,cl
add ax,0x50
mov word [rootdirectoryseg],ax
mov es,ax ;the segment
xor bx,bx
mov ax,word [num_rootentries] ;get size of root directory
mov cl,4
shr ax,cl
pop cx ;1
push ax ;1 save size of root directory
call readsectors
;now calculate location of the data area
mov ax,word [sects_per_fat]
mov bl,byte [num_fats]
mul bl
mov bx,ax
pop ax ;1 load size of root directory
push ax ;1 save for later but keep ax for a bit
add ax,bx ;add size of fat
add ax,word [reserved_sects] ;add reserved sectors
;we now have the lba of the data area
mov word [datalba],ax ;save it
;we now need to find the base segment to load the data at
pop ax ;1
mov cl,5
shl ax,cl
add ax,[rootdirectoryseg]
mov word [dataseg],ax ;save the base segment
findfile:
mov ax,word [rootdirectoryseg]
mov es,ax ;now at the offset of the root directory table
xor di,di
mov bx,word [num_rootentries] ;number of entries to search through
findfileloop:
mov si,filename
mov cx,11 ;number of bytes the filename is
repe cmpsb
je short filefound
test bx,bx
je bootfailed ;out of retries
mov ax,es
add ax,0x02 ;increase by 2 segments aka 32 bytes
mov es,ax
xor di,di
dec bx ;number of tries left minus one
jmp short findfileloop
filefound:
mov ax,word [es:di 0x0f] ;get cluster number
xor bx,bx ;data load offset
xor di,di ;fat read offset
push ax ;1 save ax
readcluster:
;set buffer for data
mov cx,word [dataseg]
mov es,cx
;load sector
sub ax,2 ;minus 2 clusters
mov cl,[sects_per_cluster]
mul cl
add ax,word [datalba]
mov cx,ax
mov al,byte [sects_per_cluster]
call readsectors
;increase buffer
mov al,byte [sects_per_cluster]
mov ah,0
mov cl,9
shl ax,cl
add bx,ax
pop ax ;1 restore ax
;set buffer for fat
mov cx,[fatseg]
mov es,cx
mov cl,3 ;multiply by three
mul cl
shr ax,1 ;divide by two
mov di,ax
mov ax,word [es:di] ;get cluster
test al,1 ;even or odd cluster
jnz short evenclus
oddclus:
mov cl,4
shr ax,cl
jmp short evaluatecluster
evenclus:
and ax,0x0fff
evaluatecluster:
cmp ax,0x0ff8 ;end of chain
jae short finishboot
;do nothing to cluster and load
push ax
jmp short readcluster
finishboot:
;this effectively jumps to a pointer
mov ax,word [dataseg]
mov ds,ax
push ax ;push segment
xor ax,ax
push ax ;push offset
retf ;return to offset and segment on stack
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers
call lbatochs ;convert lba to chs
mov ah,0x02 ;read sectors
mov dl,byte [drive_num] ;load drive number
clc ;clear carry for error checking
int 13h
jc short readsectorerror ;error
ret ;success
readsectorerror:
pop dx ;1 clean out stack if error
call resetdrive
jmp short readsectors
lbatochs: ;input: cx for lba, output: cx for cylinder and sector, and dh for head
push ax ;1 save for later
push bx ;2 save bx
;find temp variable
xor dx,dx
mov ax,cx ;ax now has lba
push ax ;3 save lba
mov bx,word [sects_per_track] ;sectors per track
div bx ;ax is now temp
;cylinder
push ax ;4 save temp
xor dx,dx
mov bx,word [num_heads] ;number of heads
div bx ;ax is now cylinder
mov cx,ax ;cx stores cylinder
pop ax ;4 retrieve temp
;cx is now cylinder
;head
push dx ;4 heads already in dx
;sector
pop dx ;4 pop dx to get stack value underneath it
pop ax ;3 retrieve lba
push dx ;3 push dx back on
push cx ;4 save cylinder
xor dx,dx
mov word bx,[sects_per_track]
div bx
inc dx ;dx now has sectors
mov bx,dx ;now bx has sectors
pop cx ;4
pop dx ;3
;put params together
mov ch,cl ;cylinder in ch
mov cl,bl ;sector in cl
mov dh,dl ;head in dh
mov dl,0 ;erase dl
pop bx ;2 load old bx
pop ax ;1 load old ax
ret
resetdrive:
mov byte dl,[drive_num] ;get drive number
mov ah,0x00 ;reset disk interrupt
int 13h
jc bootfailed
ret
bootfailed:
mov si,bootfailmsg
call printstring
jmp hangcpu
printstring: ;video mode is set by bios so no need to set it
lodsb ;load byte from si into al
test al,al ;compare al with 0
jz return ;jump if zero to return
mov bx,0x0007 ;page = bh, color = bl
mov ah,0xe ;type char interrupt
int 10h
jmp short printstring ;go back to start
return:
ret ;return from subroutine
hangcpu:
hlt ;dont run cpu unless interrupt
jmp short hangcpu
;variables for booting
bootfailmsg db "FAILURE!",0
filename db "BOOT BIN"
rootdirectoryseg dw 0
fatseg dw 0
dataseg dw 0
datalba dw 0
times 510-($-$$) db 0
bootsignature dw 0xaa55
CodePudding user response:
readsectors: ;input: cx for lba, al for sectors to read, es:bx for buffer and dl for drive numbers call lbatochs ;convert lba to chs mov ah,0x02 ;read sectors mov dl,byte [drive_num] ;load drive number clc ;clear carry for error checking int 13h jc short readsectorerror ;error ret ;success readsectorerror: pop dx ;1 clean out stack if error call resetdrive jmp short readsectors resetdrive: mov byte dl,[drive_num] ;get drive number mov ah,0x00 ;reset disk interrupt int 13h jc bootfailed ret
It frequently so happens that sectors have to be re-read. That's normal, but in your readsectors code there's a couple of errors:
- When the BIOS.ReadSector function 02h fails, the code goes to readsectorerror where there is an unmatched
pop dx
(leftover from an earlier edit) that makes returning to the main program impossible. - When the resetdrive code successfully resets the drive, the code restarts the readsectors routine, but this time the necessary inputs from
AL
andCX
are no longer there.
readsectors: ;input: cx LBA, al Sectors to read, es:bx Buffer
push ax
push cx
call lbatochs ; -> CH CL DH
mov dl, [drive_num]
clc ;clear carry for error checking
mov ah, 0x02 ;read sectors
int 13h
jnc readsectorOK
mov ah, 0x00 ;reset disk
int 13h
pop cx
pop ax
jnc readsectors
jmp bootfailed
readsectorOK:
pop cx
pop ax
ret
The lbatochs routine has several redundant instructions and is way to complicated!
Knowing that this bootsector sits on a 5.25" 160KB singlesided double density diskette, you can shorten the code a lot (which I believe was your goal).
- There's only 1 head; therefore dividing by the NumberOfHeads will be overkill.
- The SectorsPerTrack is just 8; no division needed - just shift right 3 times.
- Not using a subroutine saves the
CALL
andRET
instructions.
readsectors: ;input: cx LBA, al Sectors to read, es:bx Buffer
push ax
push cx
mov dx, cx ; LBA
shr dx, 1
shr dx, 1
shr dx, 1 ; -> DH is Head == 0
and cx, 7
inc cx ; -> CL is Sector
mov ch, dl ; -> CH is Cylinder
mov dl, [drive_num]
clc ;clear carry for error checking
mov ah, 0x02 ;read sectors
int 13h
jnc readsectorOK
mov ah, 0x00 ;reset disk
int 13h
pop cx
pop ax
jnc readsectors
jmp bootfailed
readsectorOK:
pop cx
pop ax
ret
CodePudding user response:
In the loadfilesystem function, the AL register (sectors to read) was set too high and caused the BIOS to read past a track boundary. This is not supported by some BIOS's and will cause read errors.