Home > database >  What causes a CPU hang in this function?
What causes a CPU hang in this function?

Time:12-12

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 and CX 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 and RET 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.

  • Related