Search code examples
assemblynasmx86-16osdevbochs

FLOPPY: read() on floppy image returns 0


I am trying to create a simple second stage command system in x86 assembly (16-bit real mode). The function com_dir is suppose to read a 720KB floppy and print strings when the sector begins with the letter 'F'.

Here is the second stage:

[BITS 16]
[ORG 0x0000]      

mov ax, cs
mov ds, ax   
mov [drive_num], dl
mov [root_drive], dl
mov bx, load_msg
call str_prt
call new_line
call new_line
xor cx, cx  
call new_line
mov bx, art1
call str_prt
call new_line
mov bx, art2
call str_prt
call new_line
mov bx, art3
call str_prt
call new_line
mov bx, art4
call str_prt
call new_line
mov bx, art5
call str_prt
call new_line
mov bx, art6
call str_prt
call new_line
mov bx, art7
call str_prt
call new_line
mov bx, welcome_msg
call str_prt
call new_line
mov bx, creator_msg
call str_prt
call new_line
mov bx, boot_msg
call str_prt
call new_line

mov bx, [buffer]

call new_line

mov ah, 0x0e
mov al, 0x0a
int 0x10
mov al, 0x0d
int 0x10
mov al, '>'
int 0x10

loop:
xor ah, ah
int 0x16
call key_scan
jmp loop

%include "util.inc"

pro_com:
push bx
push ax
mov bx, buffer
mov al, [bx]
cmp al, 'h'
jne help_end
inc bx
mov al, [bx]
cmp al, 'e'
jne help_end
inc bx
mov al, [bx]
cmp al, 'l'
jne help_end
inc bx
mov al, [bx]
cmp al, 'p'
jne help_end
call com_help
jmp pro_end
help_end:
mov bx, buffer
mov al, [bx]
cmp al, 'd'
jne dir_end
inc bx
mov al, [bx]
cmp al, 'i'
jne dir_end
inc bx
mov al, [bx]
cmp al, 'r'
jne dir_end
call com_dir
jmp pro_end
dir_end:
mov bx, buffer
mov al, [bx]
cmp al, 'o'
jne open_end
inc bx
mov al, [bx]
cmp al, 'p'
jne open_end
inc bx
mov al, [bx]
cmp al, 'e'
jne open_end
inc bx
mov al, [bx]
cmp al, 'n'
jne open_end
call com_open
jmp pro_end
open_end:
mov bx, not_found
call str_prt
call new_line
pro_end:
pop ax
pop bx
ret

com_help:
push bx
call new_line
call draw_line
mov bx, dir_help
call str_prt
call new_line
call draw_line
call new_line
pop bx
ret

com_dir:
push ax
push bx
push cx
push dx
call new_line
call draw_line
mov bx, drive_num
mov dl, [bx]
mov al, 0x01
mov ch, 0x00
mov cl, 0x01
mov dh, 0x00
com_dir_loop:
call read_dir
cmp cl, 0x12
je false1
inc cl
jmp com_dir_loop
false1:
cmp ch, 0x4f
je false2
inc ch
xor cl, cl
add cl, 0x01
jmp com_dir_loop
false2:
call draw_line
call new_line
pop dx
pop cx
pop bx
pop ax 
ret

read_dir:
push ax
push bx
mov bx, 0x1000
mov es, bx
mov bx, 0xe00
call read_disc
clc
mov bx, 0x0e00
mov al, [bx]
cmp al, 'F'
jne read_dir_end
mov bx, 0x0e01
call str_prt
call new_line
read_dir_end:
pop bx
pop ax 
mov bx, 0x1000
mov es, bx
ret

com_open:
push ax
push bx
push cx
push dx
mov bx, drive_num
mov dl, [bx]
mov cl, 0x00
mov al, 0x01
mov dh, 0x00
mov bx, 0x1000
mov es, bx
mov ch, 0x00
mov cl, 0x09
mov bx, 0xe00
;----------
com_open_loop:
call read_disc
call next_load
jc com_open_loop_end
jmp com_open_loop
;----------
com_open_loop_end:
clc
call 0x0e0d
clc
call new_line
mov bx, 0x1000
mov es, bx
mov ds, bx
pop dx
pop cx
pop bx
pop ax 
ret

next_load:

clc
ret

buffer times 20 db 0

drive_num:
db 0
root_drive:
db 0

welcome_msg:
db 'Welcome to matriXos$'
creator_msg:
db 'Created by Vishnu Shankar.B$'
boot_msg:
db 'Booting command line interface...$'
not_found:
db '<Error>Command cannot be resolved! Use command "help"$'
dir_help:
db '    dir - Lists files in a drive$'
line:
db '==============================$'
art1:
db '            ==       ==$'
art2:
db '            #  X   X  #$'
art3:
db '            #   X X   #$'
art4:
db '            #    X    #$'
art5:
db '            #   X X   #$'
art6:
db '            #  X   X  #$'
art7:
db '            ==       ==$'
load_msg:
db 'Successfully loaded operating system$'

jmp $
times 3584 - ($ - $$) db 0

My file util.inc:

key_scan:
cmp al, 0x08
je back_space
cmp al, 0x0d
je enter
cmp cx, 0x0015 
je end
mov ah, 0x0e
int 0x10
mov bx, buffer
add bx, cx
mov [bx], al
inc cx
jmp end
back_space:
cmp cx, 0x00
je end
dec cx
mov ah, 0x0e
mov al, 0x08
int 0x10
mov al, 0x20
int 0x10
mov al, 0x08
int 0x10
jmp end
enter:
xor cx, cx
mov ah, 0x0e
mov al, 0x0a
int 0x10
mov al, 0x0d
int 0x10
call pro_com
call clear_buffer
mov ah, 0x0e
mov al, '>'
int 0x10
end:
ret

clear_buffer:
push ax
push bx
push cx
mov bx, buffer
xor cx, cx
xor ax, ax
start:
cmp cx, 0x41
je end_buff
mov [bx], ax
inc bx
inc cx
jmp start
end_buff:
pop cx
pop bx
pop ax
ret

str_prt:
pusha
str:
mov ah, 0x0e
mov al, [bx]
cmp al, '$'
je str_end
int 0x10
add bx, 1
jmp str
str_end:
popa
ret

draw_line:
push ax
push bx
line_str:
mov bx, line
call str_prt
call new_line
pop bx
pop ax
ret

new_line:
push ax
mov ah, 0x0e
mov al, 0x0a
int 0x10
mov al, 0x0d
int 0x10
pop ax
ret

read_disc:
mov ah, 0x02   
int 0x13   
ret

hex_print:
push ax
push cx
mov ah, 0x0e
mov al, '0'
int 0x10
mov al, 'x'
int 0x10
hex_print_start:
mov al, ch
and al, 0xf0
call hex_map
int 0x10
shl cx, 0x04
mov al, ch
and al, 0xf0
call hex_map
int 0x10
shl cx, 0x04
mov al, ch
and al, 0xf0
call hex_map
int 0x10
shl cx, 0x04
mov al, ch
and al, 0xf0
call hex_map
int 0x10
hex_print_end:
pop cx
pop ax
ret

hex_map:
cmp al, 0x00
jne zero_end
mov al, '0'
ret
zero_end:
cmp al, 0x10
jne one_end
mov al, '1'
ret
one_end:
cmp al, 0x20
jne two_end
mov al, '2'
ret
two_end:
cmp al, 0x30
jne three_end
mov al, '3'
ret
three_end:
cmp al, 0x40
jne four_end
mov al, '4'
ret
four_end:
cmp al, 0x50
jne five_end
mov al, '5'
ret
five_end:
cmp al, 0x60
jne six_end
mov al, '6'
ret
six_end:
cmp al, 0x70
jne seven_end
mov al, '7'
ret
seven_end:
cmp al, 0x80
jne eight_end
mov al, '8'
ret
eight_end:
cmp al, 0x90
jne nine_end
mov al, '9'
ret
nine_end:
cmp al, 0xa0
jne a_end
mov al, 'A'
ret
a_end:
cmp al, 0xb0
jne b_end
mov al, 'B'
ret
b_end:
cmp al, 0xc0
jne c_end
mov al, 'C'
ret
c_end:
cmp al, 0xd0
jne d_end
mov al, 'D'
ret
d_end:
cmp al, 0xe0
jne e_end
mov al, 'E'
ret
e_end:
cmp al, 0xf0
jne f_end
mov al, 'F'
ret
f_end:
ret

For some reason it throws this error in bochs when reading 720KB (720 times):

[FLOPPY] read() on floppy image returns 0

Can someone explain to me what is wrong?

Note: Please ignore com_open, it is still under development.


Solution

  • The error:

    read() on floppy image returns 0

    Suggests that Bochs was attempting to read data from your disk image, but the part of the disk it was trying to access didn't physically exist. This can occur in Bochs if you create a disk image smaller than the floppy drive type being used and you aren't using auto media format.

    You have a couple of ways to resolve this. Set the Type of floppy drive to 720K, and the Type of floppy media to 720K within Bochs. Since your disk image is 720k such a change should cause this error to disappear.

    Another way is to set the Type of floppy drive to a 2.88MB drive, and then set the Type of floppy media to auto. In the real world a 2.88MB floppy drive can also read 1.44MB, 720K, 360k, and 180k 3.5" media. Bochs tries to simulate this with AUTO floppy media type.

    If your disk image is 720K and using AUTO media, Bochs will automatically drop down to the size of the disk image you are actually using. The only requirement is that the disk image is one of exactly 180k, 360k, 720k, 1440k, 2880k). Any size not exactly the size of a known standard 3.5" disk will not work properly with AUTO.


    The code in your original question did this:

    com_dir_loop:
    call read_dir
    cmp cl, 0x12       ; 0x12=18 decimal. CL=sector number
    

    If using 3.5" 720k media, the number of sectors per cylinder is 0x09 (not 0x12). There is a good table of floppy disk geometries in this Wiki article. Your code should not attempt to read a sector higher than what the media allows for. Bochs will throw a different type of error similar to attempt to read/write sector xx past last sector x . Your code could look like this:

    com_dir_loop:
    call read_dir
    cmp cl, 0x09       ; CL=sector number
    

    The other thing you'll observe in the chart for a 720k floppy (at the link given) is that a 720K floppy has 2 heads (0 and 1). Your code only handles dealing with head 0. This means that your code will only be able to access half the contents of the disk. Anything on head 1 will be ignored. You'll need to handle the heads in DH so that when you reach the sector per cylinder(track) limit, you increase the head by 1. If you exceed the 2 head limit, then you will have to adjust the sector back to 1 and head back to 0 and increase the cylinder by 1.


    Since you seem to be writing parts of a kernel, I recommend that you consider creating an LBA(logical block addressing) to CHS routine (and vice-versa). An LBA number is just a sector number from 0 to the number of sectors on a drive. There are 1440 512-byte sectors on a 720k floppy for example. So LBA goes from 0 to 1439. You can convert any LBA to CHS(cylinder head sector) value with the second formula under this Wiki article.

    CHS tuples can be mapped to LBA address with the following formula:[5][6]

    LBA = (C × HPC + H) × SPT + (S - 1)
    

    where

    C, H and S are the cylinder number, the head number, and the sector number
    LBA is the logical block address
    HPC is the maximum number of heads per cylinder (reported by disk drive, typically 16 for 28-bit LBA)
    SPT is the maximum number of sectors per track (reported by disk drive, typically 63 for 28-bit LBA)
    

    LBA addresses can be mapped to CHS tuples with the following formula ("mod" is the modulo operation, i.e. the remainder, and "÷" is integer division, i.e. the quotient of the division where any fractional part is discarded):

    C = LBA ÷ (HPC × SPT)
    H = (LBA ÷ SPT) mod HPC
    S = (LBA mod SPT) + 1
    

    Implementing the second function (converting LBA to CHS) you could then loop from 0 to 1439, convert each LBA to CHS and then call a read sector function. This would be very useful if you ever intend to navigate something like DOS's FAT12, FAT16 disk formats etc.