Files
wbios/FDD.8
T

753 lines
17 KiB
Plaintext

;
; Floppy BIOS
;
; (C)1997-2001 Pascal Dornier / PC Engines; All rights reserved.
; This file is licensed pursuant to the COMMON PUBLIC LICENSE 0.5.
;
; Limitations:
;
; - Only 3.5" floppy drives supported.
; - Only 1.44 MB mode supported.
; - Only one drive supported.
;
#if def DEBUG
FD_DEBUG: ;& enable debug code, comment out for
;production code
#endif
;
; INT 13 entry
;
int13: sti
push ds ;save registers
push es
pusha
mov bp,sp ;access to stack frame
#if def FD_DEBUG
call v_dump ;& dump registers
#endif
xor di,di ;access BIOS segment
mov ds,di
cmp ah,0 ;reset doesn't care about drive #
jz int13_1
cmp dl,0 ;valid drive ?
jnz fd_badcmd ;:bail out
int13_1: cmp byte [m_fdmed0],0 ;drive doesn't exist ?
jz fd_badcmd ;:bail out
mov di,ax ;command -> index
shr di,8
add di,di
and byte [bp+18h],0feh ;clear return carry
cmp di,19h*2 ;limit command vector
jae fd_badcmd
jmp [cs:di.fd_vectab] ;jump to command
;
; floppy vector table
;
fd_vectab: dw fd_rst ;AH=00: recalibrate drive
dw fd_status ;AH=01: get status
dw fd_read ;AH=02: read
dw fd_write ;AH=03: write
dw fd_verify ;AH=04: verify
dw fd_format ;AH=05: format track
dw fd_badcmd ;AH=06: bad
dw fd_badcmd ;AH=07: bad
dw fd_drvprm ;AH=08: read drive parameters
dw fd_badcmd ;AH=09: bad
dw fd_badcmd ;AH=0A: bad
dw fd_badcmd ;AH=0B: bad
dw fd_badcmd ;AH=0C: bad
dw fd_badcmd ;AH=0D: bad
dw fd_badcmd ;AH=0E: bad
dw fd_badcmd ;AH=0F: bad
dw fd_badcmd ;AH=10: bad
dw fd_badcmd ;AH=11: bad
dw fd_badcmd ;AH=12: bad
dw fd_badcmd ;AH=13: bad
dw fd_badcmd ;AH=14: bad
dw fd_gettyp ;AH=15: get drive type
dw fd_dskchg ;AH=16: get disk change status
dw fd_fmttyp ;AH=17: set drive type for format
dw fd_medtyp ;AH=18: set media type for format
;
; Illegal command
;
fd_badcmd: mov byte [m_fdstat],1 ;illegal command
;
; AH=01: get status
;
fd_status:
fd_exit: mov al,[m_fdstat] ;get error code
fd_exit1: mov [bp._ah],al ;return in AH
and al,al ;error ?
jz fd_exit2
or byte [bp+18h],1 ;yes: set carry
fd_exit2:
#if def FD_DEBUG
call v_dump2 ;& dump registers
#endif
popa ;restore registers
pop es
pop ds
iret ;return from interrupt
;
; AH=00: recalibrate floppy drives
;
fd_rst: les si,[m_fdbase]
in al,pic0+1 ;enable floppy interrupt
and al,0bfh
out iowait,al
out pic0+1,al
mov dx,fdc_rate ;set 500 kb/s data rate
mov al,0
out dx,al
mov dx,fdc_ctrl
mov word [m_fdstat],0 ;clear error status
mov byte [m_fdcnt],0ffh ;prevent motor off event
mov al,[m_fdmot] ;digital output register
rol al,4
and al,11110011b
or al,8 ;enable interrupts
out dx,al ;reset FDC
and byte [m_fdrecal],70h ;clear interrupt and recal flags
mov cx,100 ;wait a bit
fd_rstw: out iowait,ax
loop fd_rstw
xor al,4 ;end reset pulse
out dx,al
call fd_wait ;wait for interrupt
jb fd_rst1 ;:bad controller
mov bl,0c0h ;expected result
fd_rst0: mov al,8 ;sense interrupt
call fd_cmd ;send command
jb fd_rst1 ;:error
call fd_res ;get results
jb fd_rst1 ;:error
cmp byte [m_fdfile],bl ;drive ready changed state ?
jnz fd_rst1 ;no: error
inc bl
cmp bl,0c4h ;done all drives ?
jb fd_rst0
;
; FDC specify command
;
mov al,3 ;specify command
call fd_cmd ;send to FDC
jb fd_spec9
mov al,[es:si] ;m_fdbase -> step rate, head unload
call fd_cmd ;send to FDC
jb fd_spec9
mov al,[es:si+1] ;m_fdbase -> motor on time, DMA mode
call fd_cmd
#if def FDC_FIFO ;NSC PC87306
jb fd_spec9
mov al,13h ;configure command
call fd_cmd
jb fd_spec9
mov al,0
call fd_cmd
jb fd_spec9
mov al,00001000xb ;enable FIFO
call fd_cmd
jb fd_spec9
mov al,0 ;precompensation track
call fd_cmd
#endif
fd_spec9: mov al,[es:si+2] ;m_fdbase -> motor count
mov [m_fdcnt],al ;restore motor timer
jmp fd_exit
fd_rst1: or byte [m_fdstat],20h ;bad controller
fd_rst2: jmp fd_exit
;
; AH=05: format track
;
fd_format: call fd_dma ;convert address ES:BX -> CH:DX
mov ax,0200h+4ah ;sectors * 4 shift, DMA mode write
call fd_dma2 ;complete DMA initialization
jb fd_rw90 ;:error
call fd_seek ;turn on motor, seek to track
jb fd_rw90 ;:error
call cs_waitbx ;wait BX milliseconds
mov al,3 ;specify command
call fd_cmd ;send to FDC
jb fd_rw90
mov al,[es:si] ;m_fdbase -> step rate, head unload
call fd_cmd ;send to FDC
jb fd_rw90
mov al,[es:si+1] ;m_fdbase -> motor on time, DMA mode
call fd_cmd
jb fd_rw90
mov al,4dh ;format command
call fd_cmd
jb fd_rw90
mov al,[bp._dh] ;head
shl al,2
call fd_cmd
jb fd_rw90
mov al,[es:si+3] ;bytes per sector
call fd_cmd
jb fd_rw90
mov al,[es:si+4] ;sectors per track
call fd_cmd
jb fd_rw90
mov al,[es:si+7] ;gap length
call fd_cmd
jb fd_rw90
mov al,[es:si+8] ;fill byte
jmp fd_rw3 ;continue as read / write
;
; AH=03: write sectors
;
fd_write: call fd_dma ;convert address ES:BX -> CH:DX
mov al,4ah ;DMA mode: write
call fd_dma2 ;complete DMA initialization
jb fd_rw90 ;:error
call fd_seek ;turn on motor, seek to track
jb fd_rw90 ;:error
call cs_waitbx ;wait BX milliseconds
mov al,0c5h ;write command
jmp short fd_rw1
fd_rw90: jmp fd_rw9
;
; AH=04: verify
;
fd_verify:
#if def FD_VERF000 ;STPC patch: read to F000 segment
mov bx,0f000h
mov es,bx
xor bx,bx
#endif
call fd_dma ;convert address ES:BX -> CH:DX
xor dx,dx ;clear offset -> no DMA errors
#if def FD_VERF000
mov al,46h ;DMA mode: read
#else
mov al,42h ;DMA mode: verify
#endif
jmp short fd_read2 ;rest like read
;
; AH=02: read
;
fd_read: call fd_dma ;convert address ES:BX -> CH:DX
mov al,46h ;DMA mode: read
fd_read2: call fd_dma2 ;complete DMA initialization
jb fd_rw90 ;:error
call fd_seek ;turn on motor, seek to track
jb fd_rw90 ;:error
fd_read3: mov al,0e6h ;FDC read command
;
; execute read / write command
;
fd_rw1: call fd_cmd ;command
jb fd_rw9 ;:error
mov al,0 ;drive 0
test byte [bp._dh],1 ;other head ?
jz fd_rw2
or al,4 ;set head bit
fd_rw2: call fd_cmd
jb fd_rw9
mov al,[bp._ch] ;track number
call fd_cmd
jb fd_rw9
mov al,[bp._dh] ;head number
call fd_cmd
jb fd_rw9
mov al,[bp._cl] ;sector number
call fd_cmd
jb fd_rw9
mov al,[es:si+3] ;m_fdbase -> bytes / sector
call fd_cmd
jb fd_rw9
mov al,[es:si+4] ;m_fdbase -> end of track sector
call fd_cmd
jb fd_rw9
mov al,[es:si+5] ;m_fdbase -> gap length
call fd_cmd
jb fd_rw9
mov al,[es:si+6] ;m_fdbase -> data length
fd_rw3: call fd_cmd
jb fd_rw9
call fd_wait ;wait for interrupt
jb fd_rw9
call fd_res ;get results
jb fd_rw9
cmp byte [bp._al],3 ;write ?
jz fd_rw4
cmp byte [bp._al],5 ;format ?
jnz fd_rw5
fd_rw4: mov bx,1 ;wait 1 ms after write -> write gate
call cs_waitbx ;delay (prevent immediate step away)
fd_rw5: test byte [m_fdfile],0c0h ;any error ?
jz fd_rw99 ;:no
call fd_error ;translate error
fd_rw9: cmp byte [bp._ah],5 ;format ?
jz fd_rw99
mov byte [bp._al],0 ;clear sector count
fd_rw99: mov al,[es:si+2] ;m_fdbase -> motor count
mov [m_fdcnt],al
jmp fd_exit
;
; translate error code
;
fd_error: mov al,20h ;(FDC error)
test byte [m_fdfile],80h ;invalid command ?
jnz fd_err9 ;:yes
mov al,[m_fdfile+1] ;ST1 status
and al,10110111b ;mask off unused status bits
mov bx,fd_errtab+9 ;^error table
stc ;ensure termination
fd_err1: dec bx
rcr al,1 ;test bit
jnb fd_err1 ;try next
mov al,[cs:bx] ;get correct error code
fd_err9: or [m_fdstat],al
ret
;
; error codes
;
fd_errtab: db 20h ;(bit overflow)
db 4 ;sector not found
db 20h ;(not used)
db 10h ;CRC error
db 8 ;DMA overrun
db 20h ;(not used)
db 4 ;sector not found
db 3 ;write protect
db 2 ;address mark not found
;
; Floppy parameters
;
fd_ptab: db 0dfh ;step rate, head unload
db 02 ;head load, DMA mode
db 25h ;motor wait
db 02 ;512 bytes per sector
db 18 ;end of track
db 24h ;normal gap
db 0ffh ;DTL
db 54h ;gap length for format
db 0f6h ;fill byte for format
db 15 ;head settle time (x 1 ms)
db 8 ;motor start time (x 125 ms)
;
; turn on motor
;
; out: BX = turn-on delay in ms
;
fd_motor: xor bx,bx ;no settling time required
; turn on motor if required
mov byte [m_fdcnt],255 ;prevent motor shutdown
mov byte [m_fdstat],0 ;clear error code
test byte [m_fdmot],1 ;motor running ?
jnz fd_mot1 ;:yes
or byte [m_fdmot],1 ;set motor flag
mov dx,fdc_ctrl ;turn on motor
mov al,00011100xb ;drive 0, DMA enable, not reset
out dx,al
mov bl,[es:si+10] ;m_fdbase -> motor start time
shl bx,7 ;x 128 -> value in ms
fd_mot1: ret
;
; turn on motor, seek to track if needed
;
; -> settling time in ms in BX
;
fd_seek: call fd_motor ;turn on motor
; check and reset disk change
mov dx,fdc_chg ;disk change line active ?
in al,dx
and al,80h
jz fd_seek1x ;:no
#if def FD_DEBUG
pusha ;& write a message on disk change
mov si,deb_dskch ;&
call v_msg ;&
popa ;&
#endif
call fd_seek1z ;recalibrate
cmp byte [bp._ch],0 ;destination = track 0 ?
jnz fd_seek1b
inc byte [bp._ch]
call fd_seek2 ;seek to track 1
dec byte [bp._ch]
fd_seek1b: call fd_seek2 ;seek to track 0 / wanted track
mov dx,fdc_chg ;disk change line still active ?
in al,dx
and al,80h
jz fd_seek1c ;no: ok
mov byte [m_fdstat],80h ;set time-out error
fd_seek1c: ret
; recalibrate if required
fd_seek1x: test byte [m_fdrecal],1 ;need recalibration ?
jnz fd_seek2 ;:no
fd_seek1z: mov al,7 ;recalibrate
call fd_cmd
jb fd_seek9
mov al,0 ;drive 0
call fd_cmd
jb fd_seek9
call fd_wsk ;wait for seek complete
jb fd_seek9
or byte [m_fdrecal],1 ;set bit - done
mov byte [m_fdtrk0],0 ;set current track
or bx,bx ;motor settling time ?
jnz fd_seek2 ;yes, bigger than head settling time
mov bl,[es:si+9] ;m_fdbase -> head settling time
; seek to track if required
fd_seek2: mov al,[bp._ch] ;destination track
cmp al,[m_fdtrk0] ;= current track ?
jz fd_seek9 ;:done (implied clc)
mov al,0fh ;seek command
call fd_cmd
jb fd_seek9
mov al,0 ;drive number
call fd_cmd
jb fd_seek9
mov al,[bp._ch] ;destination track
call fd_cmd
jb fd_seek9
mov [m_fdtrk0],al ;set new position
call fd_wsk ;wait for seek complete
or bx,bx ;motor settling time ?
jnz fd_seek9 ;yes, bigger than head settling time
mov bl,[es:si+9] ;m_fdbase -> head settling time
fd_seek9: ret
;
; wait for seek to complete, get status
;
fd_wsk: call fd_wait ;wait for interrupt
jb fd_wsk8 ;:error
fd_wsk2: mov al,8 ;sense interrupt
call fd_cmd
jb fd_wsk8
call fd_res ;get results
jb fd_wsk8
mov al,[m_fdfile]
and al,01100000xb ;error, seek end bits
cmp al,00100000xb ;no error, seek end ?
jz fd_wsk9 ;:ok
fd_wsk8: or byte [m_fdstat],40h ;set seek error bit
stc
fd_wsk9: ret
;
; initialize DMA controller
;
; ES = buffer segment
; BX = buffer offset
;
fd_dma: ; ES:BX -> physical address CH:DX, init ES:SI, AH
mov dx,es ;buffer segment
rol dx,4
mov ch,dl ;high 4 bits
and dl,0f0h ;mask off low bits
add dx,bx ;add buffer offset
adc ch,0
les si,[m_fdbase] ;load ^ floppy parameters
mov ah,[es:si+3] ;get shift count
add ah,7 ;shift count + 7 (128 byte base)
ret
;
; second half of DMA setup
;
; AL = DMA mode
; AH = shift count (for sector -> byte calculation)
; DX = address (low 16 bits)
; CH = address (high 4 bits)
;
fd_dma2: mov cl,ah ;shift count
mov bl,[bp._al] ;number of sectors
mov bh,0
shl bx,cl ;-> byte count
dec bx ;-1 for DMA
add bx,dx ;test for DMA overflow
jb fd_dma9 ;:error
sub bx,dx ;restore count
fd_dma3: cli ;critical section
push ax
mov al,6 ;disable DRQ2
out dma0+10,al
out iowait,ax
pop ax
out dma0+12,al ;dummy access -> reset hi/lo FF
out iowait,ax
out dma0+11,al ;write DMA mode
out iowait,ax
mov al,dl ;low address
out dma0+4,al
out iowait,ax
mov al,dh ;high address
out dma0+4,al
mov al,ch ;DMA page register
and al,15
out fd_page,al
#if def GX_FDFIX
push dx
mov al,0 ;clear high page register !!!
mov dx,fd_page+0400h
out dx,al
pop dx
#endif
mov al,bl ;low count
out dma0+5,al
out iowait,ax
mov al,bh ;high count
out dma0+5,al
out iowait,ax
mov al,2 ;enable DRQ2
out dma0+10,al
sti ;end of critical section
clc
ret
fd_dma9: mov byte [m_fdstat],9 ;DMA overflow error
ret
;
; write command byte AL to FDC
;
fd_cmd: mov ah,al ;save data
mov dx,fdc_stat
xor cx,cx
fd_cmd1: in al,dx ;read status
and al,0c0h ;RQM / DIO
xor al,80h ;RQM set, DIO clear = write to FDC
jz fd_cmd2 ;:ok
loop fd_cmd1 ;keep trying
or byte [m_fdstat],80h ;time out
stc
ret
fd_cmd2: mov al,ah ;restore data
inc dx
out dx,al ;write data to fdc_data
ret ;(carry clear)
;
; get results from FDC
;
fd_res: mov dx,fdc_stat
xor cx,cx
mov di,m_fdfile ;destination pointer
fd_res1: xor cx,cx ;clear time-out
fd_res2: out iowait,ax
in al,dx ;read status
cmp al,0c0h ;FDC ready for data read ?
jnb fd_res3 ;:yes
loop fd_res2
fd_res8: or byte [m_fdstat],80h ;time out
stc
ret
fd_res3: out iowait,ax
inc dx
in al,dx ;read fdc_data
dec dx
mov [di],al ;store result
inc di
out iowait,ax ;give FDC some time to update status
out iowait,ax
out iowait,ax
out iowait,ax
cmp di,m_fdfile+7 ;max bytes ?
jz fd_res9 ;:done
;
; wait for next byte
;
fd_res4: in al,dx ;get controller status
cmp al,0c0h
jae fd_res3 ;:ready with data
and al,0f0h ;ready for next command ?
cmp al,80h
jz fd_res9 ;:yes, done
loop fd_res4 ;keep trying
jmp fd_res8 ;time-out
fd_res9: clc
ret
;
; Floppy interrupt
;
irq6: push ax
push ds
xor ax,ax
mov ds,ax
or byte [m_fdrecal],80h ;set interrupt flag
mov al,eoi ;end of interrupt
out pic0,al
pop ds
pop ax
iret
;
; Did we get interrupt ? CY set if time-out
;
fd_wait: mov byte [m_fdcnt],0ffh ;keep motor running
push cx
mov cx,[m_timer] ;start time
add cx,19 ;time-out 1 second
fd_wait2: test byte [m_fdrecal],80h ;did we get interrupt ?
jnz fd_wait3 ;:yes
cmp cx,[m_timer]
js fd_wait4 ;:time-out
hlt ;wait for next interrupt
jmp fd_wait2 ;look again
fd_wait3: pop cx
and byte [m_fdrecal],7Fh ;clear interrupt flag
ret
fd_wait4: or byte [m_fdstat],80h ;set time-out status
stc
pop cx
ret
;
; AH=08: read drive parameters
;
fd_drvprm: xor bx,bx ;for non-existent drive
xor cx,cx
xor dx,dx
xor si,si
xor di,di
mov [m_fdstat],bl ;clear status
mov [bp._ax],bx ;clear AX
cmp byte [m_fdmed0],0 ;drive present ?
jz fd_drvp9 ;:no
mov si,cs ;segment -> ES
mov di,fd_ptab
mov bx,4 ;drive type = 1.44 MB
mov cx,79*256+18 ;80 tracks, 18 sectors per track
mov dx,1*256+1 ;1 head, 1 drive
fd_drvp9: mov [bp._bx],bx
mov [bp._cx],cx
mov [bp._dx],dx
mov [bp._es],si
mov [bp._di],di
jmp fd_exit ;return ok status
;
; AH=15: get drive type
;
fd_gettyp: mov al,0 ;drive not present
mov byte [m_fdstat],al ;clear status
cmp dl,0 ;drive 0 ?
jnz fd_gett2
cmp byte [m_fdmed0],0 ;drive present ?
jz fd_gett2 ;:no
mov al,2 ;change line available
fd_gett2: mov [bp._ah],al ;return in AH
jmp fd_exit2 ;don't set CY
;
; AH=17: set drive type for format
;
; since we only support 1.44 MB, basically a no-op
;
fd_fmttyp: cmp al,5
jae fd_fmtt2
cmp al,0
jnz fd_dskchg
fd_fmtt2: jmp fd_badcmd
;
; AH=16: get disk change status
;
fd_dskchg: les si,[m_fdbase] ;^disk parameters
mov al,80h ;drive not ready (no drive)
cmp dl,0 ;drive 0 ?
jnz fd_dsk2
cmp byte [m_fdmed0],0 ;drive present ?
jz fd_dsk2 ;:no
call fd_motor ;turn on motor
call cs_waitbx ;wait for motor startup time
mov byte [m_fdcnt],36 ;restore motor timer
mov dx,fdc_chg ;read disk change line
in al,dx
and al,80h
jz fd_dsk2 ;:not active, AL = 0
mov al,6 ;disk change active
fd_dsk2: mov [m_fdstat],al
jmp fd_exit1 ;set CY if not 0
;
; AH=18: set media type for format
;
fd_medtyp: mov al,80h ;drive not ready (no drive)
cmp dl,0 ;drive 0 ?
jnz fd_medt9
cmp byte [m_fdmed0],0 ;drive present ?
jz fd_medt9 ;:no
mov al,0ch ;invalid format
cmp ch,79 ;80 tracks ?
jnz fd_medt9 ;:no, error
cmp cl,18 ;18 sectors ?
jnz fd_medt9 ;:no, error
mov [bp._es],cs ;return ^disk parameters
mov word [bp._di],fd_ptab
mov al,0 ;ok status
fd_medt9: mov [m_fdstat],al ;set status
jmp fd_exit1 ;set CY if not 0
;
; Initialize floppy drive: detect FDC presence,
; reset FDC, turn on floppy motor, recalibrate
;
fd_init: mov dx,fdc_ctrl ;make sure FDC is present - control
mov al,0 ;register should read / write
out dx,al
dec ax
out iowait,al
in al,dx ;read back
cmp al,0ffh
jz fd_init9 ;:doesn't exist
or byte [m_devflg],1 ;floppy drive present
mov byte [m_fdmed0],17h ;drive A: present
mov ah,0 ;reset drive
int 13h
call fd_motor ;turn on floppy motor
mov al,7 ;recalibrate
call fd_cmd
jb fd_init9
mov al,0 ;drive 0
call fd_cmd
fd_init9: ret
;
; secondary floppy init
;
fd_inb: cmp byte [m_fdmed0],0
jz fd_inb9 ;:no FDC
or byte [m_fdrecal],1 ;set bit - done
mov byte [m_fdtrk0],0 ;set current track
mov byte [m_fdstat],0 ;clear errors
call fd_wsk ;wait for seek complete
jb fd_inb7 ;:error
jmp short fd_inb9 ;ok
; error: drive not present
fd_inb7: mov al,[m_devflg] ;decrement drive count
sub al,40h
jnb fd_inb8 ;there is another "floppy" (e.g. flash)
and al,00111110xb ;no floppy drives present
fd_inb8: mov [m_devflg],al
mov byte [m_fdmed0],0 ;disable drive
fd_inb9: ret