Files
wbios/HDD.8
T

1564 lines
36 KiB
Plaintext

;
; Hard Disk BIOS
;
; (C)1997-2003 Pascal Dornier / PC Engines; All rights reserved.
; This file is licensed pursuant to the COMMON PUBLIC LICENSE 0.5.
;
; Limitations:
;
; - HDD must support command EC (identify device).
; - Read Long, Write Long (hardware-specific number of ECC bytes,
; rarely used) not supported
; - Format not supported (not available on IDE drives)
; - Only AMI / Intel style of CHS translation supported.
; - Extended disk address mode only works with drives that support
; LBA.
; - Because of the way the disk parameter table works (crunched
; down), new drives are only recognized on a cold start, not
; on Ctrl-Alt-Del restart.
;
; Notes:
;
; - Storage of disk parameters requires read/write shadow during POST
; (can be write protected later). This code will break if shadow
; is write protected during drive configuration !
;
; pd 050502 - add HDD_LOOSE option
; pd 040326 - change HDD_NOSLAVE policy, skip slave in any case if
; no master detected.
; pd 030720 - add 32 sector mode for small LBA drives
; pd 030304 - add option HD_INFO -> display config information
; - change dpt structure to add IDE I/O port and master /
; slave data
; - add four drive support, option HD_4DRV
; pd 001019 - add option HD_EDD -> packet interface
; pd 001019 - add functions 41 and 48 to support large drives.
; pd 001017 - fix power saving HLT to avoid race conditions.
; pd 001017 - don't limit cylinder number to 1023 in hd_lba
; (ensure correct result for function 15), do limit in
; function 08.
; pd 000211 - recognize new SanDisk ID
; pd 991020 - add hd_top variable, needed to support M-Systems
; DiskOnChip.
; pd 990501 - add CDBOOT hook
; pd 990427 - add ATAPI identify
; pd 990214 - add hook for IDE speed initialization (cs_ide)
; pd 990210 - add LBA mode support
; pd 981010 - fix read handshake
; pd 980710 - fix function 15: return 0 if drive not present
#if def DEBUG
HD_DEBUG: ;& comment out for production code
#endif
;
; drive parameter structure (stored in data module)
;
dpt_cyl equ 0 ;number of cylinders
dpt_head equ 2 ;number of heads
dpt_sig equ 3 ;signature, $A0
dpt_psec equ 4 ;physical sectors per track
dpt_mul equ 5 ;(precompensation) -> multiple count
dpt_shl equ 7 ;(reserved) -> shift count
dpt_ctl equ 8 ;drive control byte
dpt_pcyl equ 9 ;physical cylinders
dpt_phd equ 11 ;physical heads
dpt_port equ 12 ;IDE I/O port (legacy: landing zone)
dpt_sec equ 14 ;logical sectors per track
dpt_dev equ 15 ;master / slave (legacy: reserved)
dpt_len equ 16 ;length of structure
;
; disk address packet for extended read/write/verify/seek
;
#if def HD_EDD
drq_len equ 0 ;packet size in bytes
drq_res equ 1 ;reserved, must be 0
drq_blk equ 2 ;number of blocks, max. 127
drq_res2 equ 3 ;reserved, must be 0
drq_ofs equ 4 ;transfer buffer offset
drq_seg equ 6 ;transfer buffer segment
drq_lba equ 8 ;block number (8 bytes)
#endif
#if def FLASHDISK
int40: dec dl ;correct floppy drive number
int 40h ;execute floppy interrupt
inc dl ;restore drive number
retf 2 ;return, don't change status
intfld: jmp fldisk
;
; INT 13 entry
;
int13hd: sti
and dl,dl ;flash disk ?
jz intfld
jns int40 ;:floppy
and ah,ah ;reset drive ?
jnz int13hd1 ;:no
cmp dl,byte [cs:hd_top] ;above valid HDD ?
jae int40 ;-> floppy only
int 40h ;reset floppy
mov ah,0
#else
#if def CDBOOT
; redirect to floppy or CD emulation as needed
int40: test byte [cs:d_cdflag],1 ;emulation enabled ?
jz int40a ;:no
test dl,dl ;drive 0 ?
jnz int40b ;:no
jmp cddisk
int40a: int 40h ;execute floppy interrupt
retf 2 ;return, don't change status
int40b: dec dl ;correct floppy drive number
int 40h ;execute floppy interrupt
inc dl ;restore drive number
retf 2 ;return, don't change status
#else
; execute floppy interrupt
int40: int 40h ;execute floppy interrupt
retf 2 ;return, don't change status
#endif
#if def ROMDISK
int13fl: jmp fldisk
#endif
;
; INT 13 entry
;
int13hd: sti
and dl,dl ;HDD ?
jns int40 ;:floppy
cmp dl,byte [cs:hd_top] ;compare with max drive number
#if def ROMDISK
jz int13fl
#endif
jae int40 ;:floppy or DiskOnChip
and ah,ah ;reset drive ?
jnz int13hd1 ;:no
cmp dl,byte [cs:hd_top] ;above valid HDD ?
jae int40 ;-> floppy only
int 40h ;reset floppy
mov ah,0
#endif
;
; dispatch disk commands
;
int13hd1: push ds ;save registers
push es
pusha
mov bp,sp ;access to stack frame
xor di,di ;access BIOS segment
mov ds,di
#if def HD_DEBUG
; test byte [m_kbf],kb_fscrs ;scroll lock ?
; jnz int13dmp1 ;yes: don't display
call v_dump ;dump registers
int13dmp1:
#endif
mov di,ax ;command -> index
shr di,8
add di,di
and byte [bp+18h],0feh ;clear return carry
cmp di,hd_vec99-hd_vecs ;limit command vector
jae hd_badcmd ;:too high
jmp [cs:di.hd_vecs] ;jump to command
;
; Illegal command
;
hd_badcmd: mov byte [m_hdstat],1 ;illegal command
;
; AH=01: get status
;
hd_status: mov al,[m_hdstat] ;get old status
mov [bp._al],al ;return in AL
;
; return status
;
hd_exit0: mov [m_hdstat],al ;set error code
mov [bp._ah],al ;return in AH
hd_exit1: and al,al ;error ?
jz hd_exit2 ;:no
or byte [bp+18h],1 ;yes: set carry
#if def HD_DEBUG
stc
hd_exit2: pushf
test byte [m_kbf],kb_fscrs ;scroll lock ?
jnz int13dmp2 ;yes: don't display
call v_dump2 ;& dump registers
int13dmp2: popf
#else
hd_exit2:
#endif
popa ;restore registers
pop es
pop ds
iret ;return from interrupt
;
; IDE vector table
;
even
hd_vecs: dw hd_rst ;AH=00: recalibrate drive
dw hd_status ;AH=01: get status
dw hd_read ;AH=02: read
dw hd_write ;AH=03: write
dw hd_verify ;AH=04: verify
dw hd_badcmd ;AH=05: format track -> not supported
dw hd_badcmd ;AH=06: bad
dw hd_badcmd ;AH=07: bad
dw hd_getprm ;AH=08: read drive parameters
dw hd_setprm ;AH=09: set drive parameters
dw hd_badcmd ;AH=0A: read long -> not supported
dw hd_badcmd ;AH=0B: write long -> not supported
dw hd_seek ;AH=0C: seek
dw hd_rst2 ;AH=0D: alternate disk reset (HD only)
dw hd_badcmd ;AH=0E: bad
dw hd_badcmd ;AH=0F: bad
dw hd_trdy ;AH=10: test drive ready
dw hd_recal ;AH=11: recalibrate
dw hd_badcmd ;AH=12: bad
dw hd_badcmd ;AH=13: bad
dw hd_diag ;AH=14: controller diagnostics
dw hd_gettyp ;AH=15: get drive type
dw hd_badcmd ;AH=16: bad
dw hd_badcmd ;AH=17: bad
dw hd_badcmd ;AH=18: bad
dw hd_badcmd ;AH=19: bad
dw hd_badcmd ;AH=1A: bad
dw hd_badcmd ;AH=1B: bad
dw hd_badcmd ;AH=1C: bad
dw hd_badcmd ;AH=1D: bad
dw hd_badcmd ;AH=1E: bad
dw hd_badcmd ;AH=1F: bad
dw hd_badcmd ;AH=20: bad
dw hd_badcmd ;AH=21: bad
dw hd_badcmd ;AH=22: bad
#if def HD_TIME
dw hd_timer ;AH=23: set standby timer NON-STANDARD
#else
dw hd_badcmd
#endif
dw hd_setmul ;AH=24: set multiple mode
dw hd_id ;AH=25: identify drive
#if def HD_EDD
dw hd_badcmd ;AH=26: bad
dw hd_badcmd ;AH=27: bad
dw hd_badcmd ;AH=28: bad
dw hd_badcmd ;AH=29: bad
dw hd_badcmd ;AH=2A: bad
dw hd_badcmd ;AH=2B: bad
dw hd_badcmd ;AH=2C: bad
dw hd_badcmd ;AH=2D: bad
dw hd_badcmd ;AH=2E: bad
dw hd_badcmd ;AH=2F: bad
dw hd_badcmd ;AH=30: bad
dw hd_badcmd ;AH=31: bad
dw hd_badcmd ;AH=32: bad
dw hd_badcmd ;AH=33: bad
dw hd_badcmd ;AH=34: bad
dw hd_badcmd ;AH=35: bad
dw hd_badcmd ;AH=36: bad
dw hd_badcmd ;AH=37: bad
dw hd_badcmd ;AH=38: bad
dw hd_badcmd ;AH=39: bad
dw hd_badcmd ;AH=3a: bad
dw hd_badcmd ;AH=3b: bad
dw hd_badcmd ;AH=3c: bad
dw hd_badcmd ;AH=3d: bad
dw hd_badcmd ;AH=3e: bad
dw hd_badcmd ;AH=3f: bad
dw hd_badcmd ;AH=40: bad
dw hd_edd41 ;AH=41: detect extended interface
dw hd_xrd ;AH=42: extended read
dw hd_xwr ;AH=43: extended write
dw hd_xver ;AH=44: extended verify
dw hd_badcmd ;AH=45: bad (lock / unlock drive)
dw hd_badcmd ;AH=46: bad (eject removable media)
dw hd_xsk ;AH=47: extended seek
dw hd_edd48 ;AH=48: get extended parameters
#endif
hd_vec99: ;end of table
;
; AH=00: reset hard disk drives
; AH=0D: alternate reset (doesn't reset floppy)
;
hd_rst:
hd_rst2: call hd_inten ;enable HD interrupt
cmp dl,80h
jb hd_rst3
cmp dl,byte [cs:hd_top]
jb hd_rst4
hd_rst3: mov dl,80h ;hit primary channel
hd_rst4: call hd_parm ;get table index
mov dx,word [cs:di.dpt_port] ;port address
add dx,hdc_ctrl
mov al,4 ;soft reset
out dx,al
out iowait,ax ;wait a bit
out iowait,ax
out iowait,ax
out iowait,ax
out iowait,ax
mov al,0 ;end of reset, interrupt enable
out dx,al ;hdc_ctrl
sub dx,hdc_ctrl ;restore hdc base
call hd_busy18 ;wait while busy
jb hd_rst8 ;:error
and dl,0f0h
or dl,hdc_err ;check error status
in al,dx
and al,7fh
sub al,1
jnz hd_rst8 ;:bad status
mov al,0 ;ok status
jmp hd_exit0 ;return
hd_rst8: mov al,5 ;reset failed
hd_rst9: jmp hd_exit0
;
; AH=02: read sectors
;
hd_read: call hd_sel ;select drive
jb hd_read9
call hd_chs ;translate CHS
jb hd_read9
mov bl,[bp._al] ;get sector count
cld ;forward mode
mov di,[bp._bx] ;get destination address
mov byte [m_hdflag],0 ;clear interrupt flag
mov al,20h ;issue read command
or dl,hdc_cmd
out dx,al
hd_read1: call hd_int ;wait for interrupt
jb hd_read9
or dl,hdc_stat ;read status
in al,dx
mov byte [m_hdflag],0 ;clear interrupt flag for next
test al,1 ;ERR ?
jnz hd_read8
test al,8 ;DRQ ?
jz hd_read8 ;:no
and dl,0f0h
; or dl,hdc_dat ;read 512 bytes from drive
mov cx,256
rep insw
dec bl ;another sector ?
jnz hd_read1 ;:yes
hd_read8: sub byte [bp._al],bl ;adjust sector count to reality
call hd_stat ;get status
hd_read9: jmp hd_exit0
;
; AH=03: write sectors
;
hd_write: mov si,bx ;source address
call hd_sel ;select drive
jb hd_writ9
call hd_chs ;translate CHS
jb hd_writ9
mov bl,[bp._al] ;get sector count
cld ;forward mode
mov al,30h ;issue write command
or dl,hdc_cmd
out dx,al
hd_writ1: or dl,hdc_stat ;read status
xor cx,cx
mov byte [m_hdflag],0 ;clear interrupt flag for next
hd_writ2: in al,dx
test al,8 ;DRQ ?
jnz hd_writ3 ;:yes
test al,21h ;error ?
jnz hd_writ8
loop hd_writ2
mov al,80h ;time-out
jmp short hd_writ9
hd_writ3: and dl,0f0h
;or dl,hdc_dat ;write 512 bytes from drive
mov cx,256
es: rep outsw
call hd_int ;wait for interrupt
jb hd_writ9
dec bl ;another sector ?
jnz hd_writ1 ;:yes
hd_writ8: call hd_stat ;get status
hd_writ9: jmp hd_exit0
;
; AH=04: verify sectors
;
hd_verify: call hd_sel ;select drive
jb hd_ver9
call hd_chs ;translate CHS
mov al,40h
call hd_cmd ;read verify command
jb hd_ver9
call hd_stat ;get status
hd_ver9: jmp hd_exit0
;
; AH=08: get drive parameters
;
hd_getprm: cmp byte [m_hdcnt],0 ;no drives ?
jnz hd_getp1
jmp hd_badcmd ;:bad command
hd_getp1: call hd_parm ;get ^parameters
mov al,7 ;invalid drive number
mov cx,0 ;return 0 size
mov dx,0
jb hd_getp9 ;:not present
mov dh,[cs:di.dpt_head] ;DH = max head number
dec dh
mov dl,[m_hdcnt] ;DL = number of drives
mov cx,[cs:di.dpt_cyl] ;CX = max cylinders (swapped)
dec cx ;-1 for max cyl
#if def HDD_LBA
cmp byte [cs:di.dpt_shl],0fc ;LBA mode ?
jae hd_getp2
#endif
dec cx ;deduct one for diagnostic cylinder
hd_getp2: cmp cx,1023 ;limit cylinder to 1023
jbe hd_getp3
mov cx,1023
hd_getp3: xchg cl,ch
shl cl,6 ;CL high 6 bits = cylinders high
or cl,[cs:di.dpt_sec] ;CL = number of sectors
mov al,0 ;clear status
hd_getp9: mov [bp._cx],cx ;cylinder count
mov [bp._dx],dx ;number drives, heads
jmp hd_exit0
;
; AH=09: set drive parameters
;
hd_setprm: call hd_sel ;select drive
jb hd_setp9
mov ah,[cs:di.dpt_phd] ;number heads (physical)
dec ah
and dl,0f0h
or dl,hdc_drv ;set maximum heads
in al,dx
or al,ah
out dx,al
mov al,[cs:di.dpt_psec] ;(physical)
and dl,0f0h
or dl,hdc_cnt ;sector count
out dx,al
mov al,91h ;set drive parameters
call hd_cmd
jb hd_setp9 ;:error
call hd_stat ;check status
hd_setp9: jmp hd_exit0
;
; AH=0C: seek
;
hd_seek: call hd_sel ;select drive
jb hd_seek9
call hd_chs ;set CHS value
mov al,70h
call hd_cmd ;seek command
jb hd_seek9
call hd_stat ;check status
hd_seek9: cmp al,40h ;seek error ?
jnz hd_seek91
mov al,0 ;don't show it... (Core test will fail
hd_seek91: jmp hd_exit0 ;otherwise)
;
; AH=10: test drive ready
;
hd_trdy: mov cx,0ffffh ;no time-out
call hd_sel0 ;select drive, test ready
jb hd_trdy9
or dl,hdc_stat ;check status
in al,dx
mov [m_hdst],al
mov ah,0aah ;not ready
test al,40h
jz hd_trdy8
mov ah,40h ;seek error
test al,10h
jz hd_trdy8
mov ah,0cch ;write fault
test al,20h
jnz hd_trdy8
mov ah,0 ;ok status
hd_trdy8: mov al,ah
hd_trdy9: jmp hd_exit0
;
; AH=11: recalibrate
;
hd_recal: call hd_sel ;select drive
jb hd_rec9
mov al,10h
call hd_cmd ;recalibrate command
jb hd_rec9
call hd_stat ;get status
hd_rec9: jmp hd_exit0
;
; AH=14: controller diagnostics
;
hd_diag: mov dx,hdc ;primary I/O base
call hd_busy18 ;wait for not busy
mov al,20h ;bad controller
jb hd_diag9 ;:bad
mov al,90h ;diagnostic command
or dl,hdc_cmd
out dx,al
out iowait,al
mov cx,18*6 ;max. 6 seconds (!!!)
call hd_busy ;wait for not busy
mov al,80h ;time-out
jb hd_diag9
and dl,0f0h
or dl,hdc_err ;check error register
in al,dx
and al,7fh
sub al,1
jz hd_diag9 ;:ok
mov al,20h ;bad controller
hd_diag9: jmp hd_exit0
;
; AH=15: read DASD type
;
hd_gettyp: call hd_parm ;get pointer to parameter block
jb hd_gett8 ;:not present
mov al,[cs:di.dpt_head] ;number heads
mul byte [cs:di.dpt_sec] ;number sectors
mov dx,[cs:di.dpt_cyl] ;number cylinders
#if def HDD_LBA
cmp byte [cs:di.dpt_shl],0fc ;LBA mode ?
jae hd_gett2
#endif
dec dx ;minus one for diagnostics
hd_gett2: mul dx
mov cl,3 ;drive present
jmp short hd_gett9
hd_gett8: xor ax,ax ;0 = drive not present
xor cx,cx
xor dx,dx
hd_gett9: mov byte [bp._ah],cl ;0 = not present, 3 = present
mov [bp._cx],dx ;CX = MSB sector count
mov [bp._dx],ax ;DX = LSB sector count
mov al,0 ;ok status
mov [m_hdstat],al
jmp hd_exit1
;
; AH = 24: set multiple mode
;
hd_setmul: call hd_sel ;select drive
jb hd_setm9
and dl,0f0h
or dl,hdc_cnt
mov al,[bp._al] ;number of sectors
out dx,al
mov al,0c6h
call hd_cmd ;set multiple mode command
jb hd_setm9
call hd_stat ;get status
hd_setm9: jmp hd_exit0
;
; AH=25: identify drive
;
hd_id: call hd_selb ;select drive
;ignore time-out here, if drive not ready
;(ATAPI drive doesn't report ready
;until spoken to)
mov al,0ech ;identify drive
call hd_cmd ;issue command
jb hd_id9 ;:bad drive
in al,dx ;hdc_stat
test al,1 ;error ?
jz hd_id1 ;:no
hd_id0: mov al,0a1h ;ATAPI identify drive
call hd_cmd ;issue command
jb hd_id9 ;:time-out
hd_id1: xor cx,cx
hd_id2: in al,dx ;hdc_stat
test al,8 ;DRQ ?
jnz hd_id3 ;:yes
loop hd_id2
mov al,80h ;time-out
jmp short hd_id9
hd_id3: cld ;forward direction
and dl,0f0h
;or dl,hdc_dat
mov cx,256 ;512 bytes
mov di,bx ;destination
rep insw ;read data
call hd_stat ;get status
hd_id9: jmp hd_exit0 ;exit
#if def HD_EDD
;
; AH=41: detect EDD support
;
hd_edd41: cmp bx,55aah ;magic cookie ?
jnz hd_edd419 ;no: bad
mov word [bp._bx],0aa55h ;return cookie
mov word [bp._cx],1 ;support packet commands; no lock /
;eject
mov byte [bp._ah],1 ;major version
mov byte [m_hdstat],0
jmp hd_exit2 ;return carry clear
hd_edd419: jmp hd_badcmd ;return error
;
; AH=42: extended read
;
hd_xrd: call hd_sel ;select drive
jb hd_xrd9
call hd_xadr ;handle address
jb hd_xrd9
mov byte [m_hdflag],0 ;clear interrupt flag
mov al,20h ;issue read command
or dl,hdc_cmd
out dx,al
hd_xrd1: call hd_int ;wait for interrupt
jb hd_xrd9
or dl,hdc_stat ;read status
in al,dx
mov byte [m_hdflag],0 ;clear interrupt flag for next
test al,1 ;ERR ?
jnz hd_xrd8
test al,8 ;DRQ ?
jz hd_xrd8 ;:no
and dl,0f0h
;or dl,hdc_dat ;read 512 bytes from drive
mov cx,256
rep insw
dec bl ;another sector ?
jnz hd_xrd1 ;:yes
hd_xrd8: mov es,[bp._ds] ;access address packet
sub byte [es:si+drq_blk],bl ;adjust sector count to reality
call hd_stat ;get status
hd_xrd9: jmp hd_exit0
;
; AH=43: extended write
;
hd_xwr: call hd_sel ;select drive
jb hd_xwr9
call hd_xadr ;handle address
jb hd_xwr9
mov si,di ;buffer ^
mov al,30h ;issue write command
or dl,hdc_cmd
out dx,al
hd_xwr1: or dl,hdc_stat ;read status
xor cx,cx
mov byte [m_hdflag],0 ;clear interrupt flag for next
hd_xwr2: in al,dx
test al,8 ;DRQ ?
jnz hd_xwr3 ;:yes
test al,21h ;error ?
jnz hd_xwr8
loop hd_xwr2
mov al,80h ;time-out
jmp short hd_xwr9
hd_xwr3: and dl,0f0h
;or dl,hdc_dat ;write 512 bytes from drive
mov cx,256
es: rep outsw
call hd_int ;wait for interrupt
jb hd_xwr9
dec bl ;another sector ?
jnz hd_xwr1 ;:yes
hd_xwr8: call hd_stat ;get status
hd_xwr9: mov es,[bp._ds] ;access address packet
mov si,[bp._si]
sub byte [es:si+drq_blk],bl ;adjust sector count to reality
jmp hd_exit0
;
; AH=44: extended verify
;
hd_xver: call hd_sel ;select drive
jb hd_xver9
call hd_xadr ;handle address
jb hd_xver9
mov al,40h
call hd_cmd ;read verify command
jb hd_xver9
call hd_stat ;get status
hd_xver9: jmp hd_exit0
;
; AH=47: extended seek
;
hd_xsk: call hd_sel ;select drive
jb hd_xsk9
call hd_xadr ;handle address
jb hd_xsk9
mov al,70h
call hd_cmd ;seek command
jb hd_xsk9
call hd_stat ;check status
hd_xsk9: jmp hd_exit0
;
; AH=48: return drive parameters
;
; Note: Phoenix spec says we should return PHYSICAL geometry, but
; Award BIOS returns LOGICAL... Users of this function are most
; interested in the max sector count anyway.
;
hd_edd48: call hd_parm ;get ^parameter block -> DI
jb hd_edd489
mov si,di ;^parameter block
cld ;forward direction
mov es,[bp._ds] ;buffer segment
mov di,[bp._si] ;buffer offset
mov al,1 ;(error code)
cmp word [es:di],26 ;buffer at least 26 bytes long
jb hd_edd489 ;less -> error
mov ax,26 ;buffer length
stosw
mov ax,2 ;flags: valid geometry
stosw
xor eax,eax
mov ax,[cs:si.dpt_cyl] ;number of cylinders
stosd
mov al,[cs:si.dpt_head] ;number of heads
mov ah,0
stosd
mov al,[cs:si.dpt_sec] ;number of sectors
; mov ah,0
stosd
mov al,[cs:si.dpt_head] ;number heads
mul byte [cs:si.dpt_sec] ;number sectors
mov dx,[cs:si.dpt_cyl] ;number cylinders
mul dx
stosw ;-> physical sector count
xchg ax,dx
stosw
xor eax,eax
stosd
mov ax,512 ;bytes per sector
stosw
mov al,0 ;ok status
hd_edd489: jmp hd_exit0
;
; write LBA address to command file
;
; returns sector count in BL, transfer address in ES:DI
;
; this will break on old drives that don't support LBA
;
hd_xadr: mov es,[bp._ds] ;restore segment, SI still OK
cmp byte [es:si+drq_len],16 ;at least 16 bytes
jb hd_xadr9 ;:error
and dl,0f0h
or dl,hdc_cnt ;sector count
mov al,[es:si+drq_blk]
mov bl,al ;return in BL
out dx,al
inc dx
mov eax,[es:si+drq_lba] ;LBA sector number
out dx,al ;hdc_sec sector = LBA 7..0
inc dx
shr ax,8
out dx,al ;hdc_cyl cylinder low = LBA 15..8
inc dx
shr eax,16
out dx,al ;hdc_cyh cylinder high = LBA 23..16
inc dx
in al,dx ;hdc_drv get drive
and al,0b0h ;keep reserved, drive select bits
or al,40h ;set LBA mode
or al,ah
out dx,al ;hdc_drv heads = LBA27..24
mov di,[es:si+drq_ofs] ;get ^transfer buffer
mov es,[es:si+drq_seg]
cld ;forward mode
clc ;ok
ret
hd_xadr9: mov al,1 ;return error
stc
ret
#endif
;
; wait for not busy, check status
;
hd_stat0: call hd_busy18 ;wait until not busy
jb hd_stat9
; Enter here for faster service (assuming normally not busy)
; This is arranged to get fastest response when no error.
hd_stat: or dl,hdc_stat ;test whether busy
in al,dx
test al,80h
jnz hd_stat0 ;:busy
mov [m_hdst],al
mov ah,al ;save status
test al,24h ;write fault / ECC ?
jnz hd_stat1
and al,50h ;not ready, or seek error ?
cmp al,50h
jnz hd_stat2
test ah,1 ;other error ?
jnz hd_stat3
mov al,0 ;return ok status
ret
hd_stat1: mov al,11h ;ECC corrected data
test ah,4
jnz hd_stat9
mov al,0cch ;no - must be write fault
hd_stat9: stc
ret
hd_stat2: mov al,0aah ;not ready
test ah,40h
jz hd_stat9
mov al,40h ;no - must be seek error
stc
ret
hd_stat3: and dl,0f0h
or dl,hdc_err ;read error register
in al,dx
mov [m_hderr],al
mov si,hd_errtab
cmp al,0 ;nothing set -> undefined error
jz hd_stat5
hd_stat4: inc si
shl al,1
jnb hd_stat4
hd_stat5: mov al,[cs:si] ;get error code
stc
ret
;
; error register -> error code translation
;
hd_errtab: db 0e0h ;nothing set - status error
db 0ah ;80 - bad sector flag detected
db 10h ;40 - bad ECC
db 0bbh ;20 - undefined error
db 04h ;10 - record not found
db 01h ;08 - abort -> bad command
db 0bbh ;04 - undefined error
db 40h ;02 - seek error
db 02h ;01 - address mark not found
;
; get pointer to parameter block
;
; entry: DL = drive
; exit: CS:DI = parameter block
;
hd_parm: cmp dl,byte [cs:hd_top] ;valid drive number ?
jae hd_parm9 ;:bad
mov di,dx ;drive number
and di,7fh
shl di,4 ;* 16 dpt_len
add di,offset hd_prm0 ;+ table base
;clc
ret
hd_parm9: mov al,1 ;error code
stc
ret
;
; wait while HD busy, CX ticks, DX port base
;
hd_busy18: mov cx,18 ;18 ticks = 1 second
hd_busy: add cx,[m_timer] ;start time + max number of ticks
and dl,0f0h
or dl,hdc_stat
hd_busy1: in al,dx
test al,80h ;busy ?
jz hd_busy9 ;:no, carry clear
cmp cx,[m_timer]
jns hd_busy1 ;keep waiting
hd_busy8: stc ;time-out
mov al,80h ;status code
hd_busy9: ret
;
; select drive, wait for drive ready
;
; -> CS:DI = ^parameter block
;
; special entry: no drive number check
hd_selb: mov cx,18 ;1 s time-out
and dl,7fh ;mask drive number
jmp short hd_sel0a
; normal entry
hd_sel: mov cx,18 ;1 s time-out for ready wait
hd_sel0: and dl,7fh ;legal drive number ?
cmp dl,[m_hdcnt]
jae hd_parm9 ;:bad
hd_sel0a: mov di,dx
and di,007fh ;mask drive number
shl di,4
add di,offset hd_prm0
mov byte [m_hdflag],0 ;clear interrupt flag
mov al,byte [cs:di.dpt_dev]
or al,0a0h
mov dx,word [cs:di.dpt_port]
or dl,hdc_drv
out dx,al ;set drive
;
; wait until HD ready, CX ticks
;
hd_rdy: add cx,[m_timer] ;start time + max number of ticks
or dl,hdc_stat
hd_rdy1: in al,dx
test al,80h
jnz hd_rdy2 ;:busy
test al,40h
jnz hd_busy9 ;:ready, carry clear
hd_rdy2: cmp cx,[m_timer]
jns hd_rdy1 ;keep waiting
jmp hd_busy8
;
; issue command AL, wait for interrupt
;
hd_cmd: mov byte [m_hdflag],0 ;clear interrupt flag
or dl,hdc_cmd
out dx,al
; wait for HD interrupt
hd_int: mov cx,18*4 ;4 seconds
add cx,[m_timer] ;start time + max number of ticks
hd_int1: cli ;test in critical section as some
;modern drives are "too fast" for
;slower embedded boards.
test byte m_hdflag,0ffh ;interrupt ?
jnz hd_int9 ;:yes, return NC
cmp cx,[m_timer] ;time-out ?
js hd_int8 ;:yes, return CY
sti ;end critical section, HLT follows
hlt ;power-saving wait for next interrupt
jmp hd_int1
hd_int8: stc ;time-out
mov al,80h ;status code
hd_int9: sti ;re-enable interrupts !
ret
;
; IRQ14 entry
;
#if ! def irq14 ;(optional user override)
irq14:
irq15: push ax
push ds
xor ax,ax ;BIOS segment
mov ds,ax
mov byte [m_hdflag],0ffh ;set interrupt flag
mov al,eoi
out pic1,al
out pic0,al
pop ds
pop ax
iret
#endif
;
; set hard disk time-out
;
#if def HD_TIME
hd_timer:
call hd_sel ;select drive, wait for not busy
jb hd_tim9
and dl,0f0h
or dl,hdc_cnt
mov al,HD_TIME
out dx,al
mov al,0e3h
call hd_cmd ;issue command
jb hd_tim9
and dl,0f0h
or dl,hdc_err
in al,dx
and al,7fh
jz hd_tim9
sub al,1 ;ok ?
jz hd_tim9
mov al,20h
stc
hd_tim9: jmp hd_exit0
#endif
;
; write CHS parameters to command file, including CHS translation
;
hd_chs: and dl,0f0h
or dl,hdc_cnt ;sector count
mov al,[bp._al]
out dx,al
inc dx
mov cl,[cs:di.dpt_shl] ;get shift count
#if def HDD_LBA
cmp cl,0fch ;LBA mode ?
jae hd_chs2
#endif
mov bx,[bp._cx] ;sector number, cylinder
mov al,bl ;sector number
and ax,3fh ;(need AH = 0 for divide)
out dx,al ;hdc_sec
mov al,[bp._dh] ;head number
div byte [cs:di.dpt_phd] ;divide by physical heads
;-> AL = heads, AH = cylinders
inc dx
xchg bl,bh ;swap cylinder
shr bh,6 ;bit 7..6 become bits 9..8
shl bx,cl ;shift cylinder for CHS translation
or al,bl ;head
out dx,al ;hdc_cyl - cylinder low
inc dx
mov al,bh ;cylinder high
out dx,al ;hdc_cyh
inc dx
in al,dx ;hdc_drv
or al,ah ;heads
out dx,al
ret
;
; LBA translation
;
#if def HDD_LBA
hd_chs2: push eax ;save eax, ebx
push ebx
xor eax,eax
mov ax,[bp._cx] ;sector number, cylinder
xchg al,ah ;swap cylinder high, low
shr ah,6 ;fix cylinder high
cmp cl,0ff ;FF = 255 heads
jnz hd_chs3
mov ebx,eax
shl eax,8 ;cylinder * 255
sub eax,ebx
jmp hd_chs4
hd_chs3: shl eax,5 ;* 32
cmp cl,0fc ;FC = 32 heads
jz hd_chs4
shl eax,1 ;* 64
cmp cl,0fd ;FD = 64 heads
jz hd_chs4
shl eax,1 ;* 128
hd_chs4: xor ebx,ebx
mov bl,[bp._dh] ;head number
add eax,ebx ;add head
mov ebx,eax
shl eax,6 ;cylinder * 255 + head * 63
sub eax,ebx
xor ebx,ebx
mov bl,[bp._cl] ;sector number
and bl,63
dec bx ;- 1
add eax,ebx
out dx,al ;hdc_sec sector = LBA 7..0
inc dx
shr ax,8
out dx,al ;hdc_cyl cylinder low = LBA 15..8
inc dx
shr eax,16
out dx,al ;hdc_cyh cylinder high = LBA 23..16
inc dx
in al,dx ;hdc_drv get drive
and al,0b0h ;keep reserved, drive select bits
or al,040h ;set LBA mode
or al,ah
out dx,al ;hdc_drv heads = LBA27..24
pop ebx
pop eax
ret
#endif
;
; enable HD interrupt
;
hd_inten: cli
in al,pic1+1 ;enable HD interrupt
#if def HD_4DRV
and al,03fh
#else
and al,0bfh
#endif
out iowait,ax
out pic1+1,al
in al,pic0+1 ;enable cascade interrupt
and al,0fbh
out iowait,ax
out pic0+1,al
sti
ret
;
; HD detect / init
;
hd_init:
#if def HD_WAIT
;
; Some drives take a long time to become responsive to commands,
; because they only store very minimal firmware, and fetch the
; actual code from disk. Some of them are allergic to being touched
; before they are ready.
;
cmp word [m_rstflg],1234h ;Ctrl-Alt-Del ?
jz hd_wait9 ;:skip wait - drives should be ready
xor bx,bx ;clear second counter
mov si,msg_wait
call v_msg
cmp bx,HD_ENA ;0 delay ?
jz hd_wait3 ;yes: bypass
hd_wait1: mov ax,18 ;about 1 second
add ax,[m_timer] ;start time + max number of ticks
hd_wait2: hlt ;low power wait, we'll be here for a
;while
cmp ax,[m_timer] ;time-out ?
jns hd_wait2 ;no: keep waiting
cmp bx,HD_ENA ;can we touch the drive now ?
jb hd_wait8 ;:no
hd_wait3: mov al,0ffh ;place FF on the IDE bus (or loopback)
mov dx,hdc+hdc_dat
out dx,al
or dl,hdc_stat ;does the status register read non-FF ?
in al,dx
cmp al,0ffh
jz hd_wait8a ;FF: no drive attached, bail
test al,80h ;busy ?
jnz hd_wait8 ;:don't touch
mov al,0a0h ;access master drive
and dl,0f0h
or dl,hdc_drv
out dx,al
out iowait,ax
or dl,hdc_stat ;read status
in al,dx
test al,80h ;busy ?
jnz hd_wait8
test al,40h ;drive ready ?
jnz hd_wait8a ;:terminate the wait
hd_wait8: mov si,msg_dot ;display a dot each second
call v_msg
inc bx ;second counter
cmp bx,HD_WAIT
jb hd_wait1
hd_wait8a: mov si,msg_crlf ;go to next line
call v_msg
hd_wait9:
#endif
cli
mov ax,int13hd ;set interrupt vector
xchg [vec13],ax
mov [vec40],ax
mov ax,cs ;old INT13 becomes INT40
xchg [vec13+2],ax
mov [vec40+2],ax
mov word [vec41],hd_prm0 ;set vectors to disk parameters
mov [vec41+2],cs
mov word [vec46],hd_prm1
mov [vec46+2],cs
call hd_inten ;enable HD interrupt
mov al,byte [cs:hd_top] ;set number of drives to start
and al,7fh
mov [m_hdcnt],al
mov byte [m_hdstat],0 ;clear status
; Unfortunately, it is not that easy to detect the slave drive,
; as the master drive will often drive the slave registers to
; "safe" values when the slave is not present.
;
; It is supposed to be possible to detect number of drives with
; the execute drive diagnostic command, but I don't see how.
;
; In the end, if the detection was incorrect, we will time out
; (about a second) when trying to identify the drive.
;
; To save time, skip slave detection if no master was seen.
mov di,hd_prm0-dpt_len ;setup first drive
mov dl,80h-1
hd_init1: inc dl ;next drive
add di,dpt_len
cmp dl,byte [cs:hd_top] ;done ?
jz hd_init2 ;:yes
#if def HDD_NOSLAVE ;time-saver: skip slave drives
test byte [cs:di.dpt_dev],10h ;slave ?
jnz hd_init1 ;yes: skip
#else
test byte [cs:di.dpt_dev],10h ;slave ?
jz hd_init1a ;:no
shr byte [cs:hd_good],1 ;was master good ?
jnc hd_init1 ;no: skip
hd_init1a:
#endif
#if ! def HDD_PRES
mov byte [cs:hd_good],0 ;clear good flag
call hd_pres ;check presence
jb hd_init1 ;:not present
inc byte [cs:hd_good] ;set good flag
#endif
call hd_set ;set parameters
;jb hd_init1 ;(if bad, we will find out later)
jmp hd_init1
hd_init2: mov di,hd_prm0 ;count drives, crunch descriptors
mov dl,0 ;clear drive count
mov dh,byte [cs:hd_top] ;current drive count
sub dh,07fh
push ds ;save DS, ES
push es
push cs ;DS = CS, ES = CS
pop ds
push cs
pop es
lea si,[di.-dpt_len]
hd_init3: add si,dpt_len ;go to next descriptor
hd_init4: dec dh ;another drive ?
jbe hd_init5 ;:no
test word [si.dpt_cyl],0ffffh ;0 cylinders ?
jz hd_init3 ;:drive doesn't exist
mov cx,dpt_len / 2 ;copy descriptor down
rep movsw
inc dl ;count good drives
jmp hd_init4
hd_init5: mov cx,offset hd_prm99 ;fill remaining descriptors
sub cx,di
jz hd_init6
shr cx,1
xor ax,ax
rep stosw
hd_init6: pop es
pop ds
mov byte [m_hdcnt],dl ;store number of drives
or dl,80h
mov byte [cs:hd_top],dl ;store top hard disk number
#if def HD_INFO
jmp hd_cr
#else
ret
#endif
;
; check drive presence, DI = ^device block
;
hd_pres: push dx
mov dx,word [cs:di.dpt_port]
or dl,hdc_drv
mov al,byte [cs:di.dpt_dev]
or al,0a0h
out dx,al
out iowait,ax
out iowait,ax
out iowait,ax
and dl,0f0h
or dl,hdc_cnt ;write test pattern
mov al,55h
out dx,al
and dl,0f0h
or dl,hdc_cyl ;write negative pattern
mov al,0aah
out dx,al
and dl,0f0h
or dl,hdc_cnt ;read test pattern
in al,dx
xor al,55h
jz hd_pres9 ;:ok
hd_pres8:
#if def HD_INFO
pusha
call hd_cr
mov ax,dx ;display port address
and al,0f0 ;(change into base)
call hex
mov si,offset hd_nf ;not found
call v_msg
popa
#endif
stc
hd_pres9: pop dx
ret
;
; set up drive DL, drive parameters DI
;
hd_set: mov ah,25h ;get drive ID
mov bx,tmp_buf
int 13h
jb hd_set9
#if def HD_INFO
call hd_inf1 ;display drive info, part 1
#endif
#if def cs_ide
push di ;save ^drive parameters
mov di,tmp_buf ;DS:DI points to identify buffer
push dx ;DL: drive
call cs_ide ;set drive timing parameters
pop dx
pop di
#endif
#if def HDD_LOOSE
mov ax,[tmp_buf+0] ;device ID
and ax,084c0
cmp ax,08480 ;removable CF
jz hd_set0
cmp ax,08440 ;not removable CF
jz hd_set0
test ah,080 ;ATAPI ?
jz hd_set0
#else
cmp word [tmp_buf+0],848ah ;CompactFlash ?
jz hd_set0 ;:yes
cmp word [tmp_buf+0],844ah ;CompactFlash ? (new SanDisk)
jz hd_set0 ;:yes
test byte [tmp_buf+1],80h ;ATAPI ?
jz hd_set0 ;:no
#endif
; note I/O base and drive ID of ATAPI CD-ROM
; this is assumed to be the first ATAPI device found
#if def CDBOOT
cmp byte [cs:d_cdbase],0 ;is this the first ATAPI drive ?
jnz hd_set9 ;:no
mov word [cs:d_cdbase],01f0h ;set address
test dl,1 ;master ?
jnz hd_set9 ;:slave, default
mov byte [cs:d_cddrv],0a0h ;master drive
#endif
#if def HD_INFO
call hd_inf2 ;say ATAPI
#endif
hd_set9: stc ;error return
ret
hd_set0: mov al,[tmp_buf+12] ;sectors
mov [cs:di.dpt_sec],al
mov [cs:di.dpt_psec],al
mov al,[tmp_buf+94] ;multiple block size
mov [cs:di.dpt_mul],al
mov ax,[tmp_buf+2] ;cylinders
mov [cs:di.dpt_pcyl],ax
mov bl,[tmp_buf+6] ;heads
mov [cs:di.dpt_phd],bl
; CHS translation: shift cylinders right / heads left until
; cylinders < 1024
mov bh,0 ;shift count
#if def cfg_lba
cmp word [cs:cfg_ofs+cfg_lba],0
jz hd_set1 ;0 -> stay in CHS mode
cmp ax,[cs:cfg_ofs+cfg_lba]
jae hd_lba ;select LBA if cylinder count >= config
#else
#if def FORCE_LBA
cmp ax,FORCE_LBA ;force LBA for high cylinder count
ja hd_lba
#endif
#endif
hd_set1: cmp ax,1024
jb hd_set2
shr ax,1 ;cylinders / 2
shl bl,1 ;heads * 2
#if def HDD_LBA
jb hd_lba ;:overflow - use LBA mode for this drive
#else
jb hd_set9 ;:overflow - cannot translate drive
#endif
inc bh ;count the shifts
jmp hd_set1
hd_set2: mov [cs:di.dpt_cyl],ax
mov [cs:di.dpt_head],bl
mov [cs:di.dpt_shl],bh
mov byte [cs:di.dpt_sig],0a0h ;signature
mov ah,9 ;set drive parameters
int 13h
jb hd_set9
mov ah,0dh ;reset drive
int 13h
hd_set2b:
#if def HD_INFO
call hd_inf3 ;display physical / logical CHS
#endif
push dx
mov ah,8 ;get max CHS
int 13h
mov al,dh ;heads
pop dx
jb hd_set9
mov ah,4 ;verify sectors
mov dh,al ;max head
mov al,cl ;max sector -> sector count
and al,3fh
and cl,0c0h ;start sector = 1
or cl,1
sub ch,1 ;cylinder - 1
jnb hd_set3
sub cl,40h
hd_set3: int 13h
jb hd_set9
#if def HD_TIME
mov ah,23h ;set drive time-out
mov al,HD_TIME
int 13h
#endif
ret ;normal return
#if def HDD_LBA
;
; determine LBA parameters
;
hd_lba: test byte [tmp_buf+99],2 ;LBA mode supported ?
jz hd_set9 ;:no
push dx
mov eax,dword [tmp_buf+120] ;number of LBA sectors (low)
mov dx,[tmp_buf+122] ;(high)
mov bx,32*63
mov cx,0fc00+32 ;32 heads
cmp eax,1023*32*63 ;max size ?
jbe hd_lba2
mov bx,64*63 ;64 heads
mov cx,0fd00+64
cmp eax,1023*64*63 ;max size ?
jbe hd_lba2
mov bx,128*63 ;128 heads
mov cx,0fe00+128
cmp eax,1023*128*63
jb hd_lba2
mov bx,255*63 ;255 heads
mov cx,0ff00+255
hd_lba2: div bx ;/ heads / sectors
; set drive parameters
mov [cs:di.dpt_cyl],ax
mov byte [cs:di.dpt_head],cl ;number of heads
mov byte [cs:di.dpt_shl],ch; special shift -> LBA mode
mov byte [cs:di.dpt_sec],63 ;63 sectors
pop dx
jmp hd_set2b ;note we don't set LBA parameters
#endif
;
; display drive information
;
#if def HD_INFO
;
; display drive info (DI = ^drive parms, tmp_buf = identify blk)
;
hd_inf1: pusha
call hd_cr ;display CR/LF
mov ax,[cs:di.dpt_port] ;display port
call hex
mov si,offset hd_mstr ;master
test byte [cs:di.dpt_dev],10h ;slave ?
jz hd_inf1a
mov si,offset hd_slve ;slave
hd_inf1a: call v_msg
mov ax,[tmp_buf+0] ;display config bits
call hex
mov si,offset tmp_buf+27*2
mov cx,20
hd_inf1b: lodsw ;display drive model number
push ax
mov al,ah ;big endian format !
call putc
pop ax
call putc
loop hd_inf1b
popa
ret
;
; say this is an ATAPI device
;
hd_inf2: pusha
mov si,offset hd_atapi
call v_msg
popa
ret
;
; display physical and logical information
;
hd_inf3: pusha
mov si,offset hd_phys
call v_msg
movzx eax,word [tmp_buf+2] ;"phys" cylinders
call hd_ints
movzx eax,word [tmp_buf+6] ;"phys" heads
call hd_ints
movzx eax,word [tmp_buf+12] ;"phys" sectors
call post_itoa
mov si,offset hd_log
call v_msg
movzx eax,word [cs:di.dpt_cyl] ;logical cylinders
call hd_ints
movzx eax,byte [cs:di.dpt_head] ;logical heads
call hd_ints
movzx eax,byte [cs:di.dpt_sec] ;logical sectors
call post_itoa
cmp byte [cs:di.dpt_shl],0ffh ;LBA mode ?
jnz hd_inf3a
mov si,offset hd_vlba ;say so
call v_msg
hd_inf3a: popa
ret
hd_cr: mov si,offset msg_crlf
jmp v_msg
hd_ints: call post_itoa ;display number
mov al,"/" ;display trailing /
jmp putc
hd_mstr: db "Master ",0
hd_slve: db "Slave ",0
hd_atapi: db "ATAPI",0
hd_phys: db 13,10,"Phys C/H/S ",0
hd_log: db " Log C/H/S ",0
hd_vlba: db " LBA",0
hd_nf: db "- no drive found ! ",0
#endif