; ; 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