; ========================================================================== ;
; IDEHDC.asm                                                                 ;
;   Direct disk I/O module for IDE disk controllers.                         ;
;   Written by Alan Martin.                                                  ;
;   Note - this code doesn't use interrupts!                                 ;
; ========================================================================== ;
idehdc segment use16 'CODE'     ; Direct disk I/O code segment.
                                        ; Initialize segment.
        assume  cs:idehdc                       ; Initial segment assumptions.
        .386                                    ; Assume 386+ code is valid.

                                        ; Stack addressing equates.
_ax     equ     word ptr bp+001Ch               ; Saved AX value.
_al     equ     byte ptr bp+001Ch               ; Saved AL value.
_ah     equ     byte ptr bp+001Dh               ; Saved AH value.
_bx     equ     word ptr bp+0010h               ; Saved BX value.
_bl     equ     byte ptr bp+0010h               ; Saved BL value.
_bh     equ     byte ptr bp+0011h               ; Saved BH value.
_cx     equ     word ptr bp+0018h               ; Saved CX value.
_cl     equ     byte ptr bp+0018h               ; Saved CL value.
_ch     equ     byte ptr bp+0019h               ; Saved CH value.
_dx     equ     word ptr bp+0014h               ; Saved DX value.
_dl     equ     byte ptr bp+0014h               ; Saved DL value.
_dh     equ     byte ptr bp+0015h               ; Saved DH value.

delay macro x                           ; Long delay (for 400ns transition).
local   @loop                                   ; Local labels.
        push    cx                              ; Save CX.
        mov     cx,x                            ; Get repeat count.
 @loop: loop @loop                              ; Loop X times.
        pop     cx                              ; Restore CX.
endm                                            ; End of DELAY macro.

hdcwait macro                           ; Wait for HDC to finish commands.
local   @loop,@ok,@err                          ; Local labels.
        push    ax ecx dx                       ; Save registers.
        mov     ecx,00040000h                   ; 1s delay on 486DX-50.
        mov     dx,01F7h                        ; HDC status register.
        in      al,dx                           ; Read status.
        test    al,80h                          ; Is the HDC busy?
        jz      @ok                             ; If not, end immediately.
 @loop: in      al,dx                           ; Read status.
        delay   000Ah                           ; 50-100 clock delay.
        test    al,80h                          ; Is the HDC busy?
        jz      @ok                             ; If not, end loop.
        loopd   @loop                           ; Otherwise, continue.
        pop     dx ecx ax                       ; Restore registers.
        stc                                     ; Set CF.
        jmp     @err                            ; Exit with error.
 @ok:   pop     dx ecx ax                       ; Restore registers.
        clc                                     ; Clear CF.
 @err:
endm                                            ; End of HDCWAIT macro.

; ------------------------------------------ ;
; IOREAD - Read sectors through direct I/O.  ;
; Input.: AX=cylinder number                 ;
;         BH=head number;  BL=sector number  ;
;         CH=# of sectors; CL=drive (0 or 1) ;
;         DS:DX->buffer for sectors          ;
; Output: CF=0 (OK) or 1 (Error)             ;
;         AL=error code or 0 if no error.    ;
;         AH=device error code if applicable.;
; Errors: AL=0 - No error.                   ;
;            1 - Controller busy or absent.  ;
;            2 - Drive not ready.            ;
;            3 - Drive not ready for read.   ;
;            4 - Device error:               ;
;         AH, bit 0: Address mark not found. ;
;                 1: Track 0 not found.      ;
;                 2: Write fault.            ;
;                 4: Sector not found.       ;
;                 6: Error in data.          ;
;                 7: Sector marked bad.      ;
; Note: There may be errors if a read is     ;
;       across a cylinder (or sometimes even ;
;       a head) boundary.                    ;
; ------------------------------------------ ;
ioread  proc far                        ; Read sectors through direct I/O.
        pushad                                  ; Save all registers.
        mov     bp,sp                           ; Address the stack.
        push    ds es                           ; Save segments.
        in      al,0A1h                         ; Get PIC 2 mask.
        push    ax                              ; Save it.
        or      al,40h                          ; Disable IRQ 14.
        out     0A1h,al                         ; Set PIC 2 mask.
        hdcwait                                 ; Wait for HDC not busy.
        jnc     ir_ok0                          ; Continue if not busy.
        mov     al,01h                          ; Error 1: Controller busy...
        mov     [_al],al                        ;    .
        jmp     ir_err                          ; ...done.
 ir_ok0:mov     al,[_bh]                        ; Get head number.
        mov     ah,[_cl]                        ; Get drive number.
        and     ax,010Fh                        ; Mask out extra bits.
        shl     ah,04h                          ; Adjust AH.
        or      al,ah                           ; Combine data.
        or      al,0A0h                         ; Set 512 bytes + ECC.
        mov     dx,01F6h                        ; Write drive/head numbers...
        out     dx,al                           ; ...done.
        hdcwait                                 ; Wait for HDC not busy.
        jnc     ir_ok1                          ; Continue if not busy.
        mov     al,01h                          ; Error 1: Controller busy...
        mov     [_al],al                        ;    .
        jmp     ir_err                          ; ...done.
 ir_ok1:mov     ecx,000C0000h                   ; 3s delay.
        mov     dx,01F7h                        ; HDC status register.
 ir_l1: in      al,dx                           ; Read status.
        test    al,40h                          ; Drive ready?
        jnz     ir_ok2                          ; Continue if so.
        loopd   ir_l1                           ; Loop for 3s.
        mov     al,02h                          ; Error 2: Drive not ready...
        mov     [_al],al                        ;    .
        jmp     ir_err                          ; ...done.
 ir_ok2:test    al,10h                          ; Drive ready for read?
        jnz     ir_ok3                          ; Continue if so.
        loopd   ir_l1                           ; Loop for 3s.
        mov     al,03h                          ; Error 3: Cannot read data...
        mov     [_al],al                        ;    .
        jmp     ir_err                          ; ...done.
 ir_ok3:mov     al,10h                          ; Set to >8 heads...
        mov     dx,03F6h                        ;    .
        out     dx,ax                           ; ...done.
        mov     dx,01F2h                        ; Write read parameters...
        mov     al,[_ch]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_bl]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_al]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_ah]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_bh]                        ;    .
        mov     ah,[_cl]                        ;    .
        and     ax,010Fh                        ;    .
        shl     ah,04h                          ;    .
        or      al,ah                           ;    .
        or      al,0A0h                         ;    .
        out     dx,al                           ; ...done.
        mov     dx,01F1h                        ; Write Precompensation = 0...
        xor     al,al                           ;    .
        out     dx,al                           ; ...done.
        hdcwait                                 ; Wait for HDC not busy.
        jnc     ir_ok4                          ; Continue if not busy.
        mov     al,01h                          ; Error 1: Controller busy...
        mov     [_al],al                        ;    .
        jmp     ir_err                          ; ...done.
 ir_ok4:xor     cx,cx                           ; Get sector count...
        mov     cl,[_ch]                        ; ...done.
        push    ds                              ; Put DS in ES...
        pop     es                              ; ...done.
        mov     di,[_dx]                        ; Get offset.
        mov     dx,01F7h                        ; Send read command...
        mov     al,20h                          ;    .
        out     dx,al                           ; ...done.
 ir_l2: mov     dx,01F7h                        ; Get status port.
        delay   000Ah                           ; Delay for >400ns.
        in      al,dx                           ; Get status.
        test    al,80h                          ; Busy?
        jnz     ir_l2                           ; Loop if so.
        test    al,29h                          ; Loop if no change...
        jz      ir_l2                           ; ...done.
        test    al,08h                          ; Ready for data?
        jnz     ir_rda                          ; If so, read it.
        test    al,21h                          ; Error in command?
        jnz     ir_dev                          ; If so, return device error.
        jmp     ir_l2                           ; Continue loop.
 ir_rda:push    cx                              ; Save CX.
        mov     cx,0100h                        ; Repeat count.
        mov     dx,01F0h                        ; 16-bit transfer port.
        rep     insw                            ; Read data.
        pop     cx                              ; Restore CX.
        loop    ir_l2                           ; Loop until done.
        mov     al,12h                          ; Deactivate controller...
        mov     dx,03F6h                        ;    .
        out     dx,ax                           ; ...done.
        mov     al,00h                          ; No error - return 0...
        mov     [_al],al                        ; ...done.
        pop     ax                              ; Reset PIC 2 mask...
        out     0A1h,al                         ; ...done.
        clc                                     ; No error: CF=0.
        pop     es ds                           ; Restore segments.
        popad                                   ; Restore all registers.
        ret                                     ; Return (far).
 ir_dev:mov     al,04h                          ; Error 4: Device fault...
        mov     [_al],al                        ; ...done.
        mov     dx,01F1h                        ; Get error code...
        in      al,dx                           ;    .
        mov     [_ah],al                        ; ...done.
        mov     dx,01F6h                        ; Recalibrate head...
        mov     al,[_bh]                        ;    .
        mov     ah,[_cl]                        ;    .
        and     ax,010Fh                        ;    .
        shl     ah,04h                          ;    .
        or      al,ah                           ;    .
        or      al,0A0h                         ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,10h                          ;    .
        out     dx,al                           ; ...done.
        hdcwait                                 ; Wait for HDC not busy.
        mov     al,12h                          ; Deactivate controller...
        mov     dx,03F6h                        ;    .
        out     dx,ax                           ; ...done.
 ir_err:pop     ax                              ; Reset PIC 2 mask...
        out     0A1h,al                         ; ...done.
        stc                                     ; Error: CF=1.
        pop     es ds                           ; Restore segments.
        popad                                   ; Restore all registers.
        ret                                     ; Return (far).
ioread  endp                                    ; End of IOREAD procedure.

; ------------------------------------------ ;
; IOWRITE - Write sectors through direct I/O.;
; Input.: AX=cylinder number                 ;
;         BH=head number;  BL=sector number  ;
;         CH=# of sectors; CL=drive (0 or 1) ;
;         DS:DX->buffer for sectors          ;
; Output: CF=0 (OK) or 1 (Error)             ;
;         AL=error code or 0 if no error.    ;
;         AH=device error code if applicable.;
; Errors: AL=0 - No error.                   ;
;            1 - Controller busy or absent.  ;
;            2 - Drive not ready.            ;
;            3 - Drive not ready for write.  ;
;            4 - Device error:               ;
;         AH, bit 0: Address mark not found. ;
;                 1: Track 0 not found.      ;
;                 2: Write fault.            ;
;                 4: Sector not found.       ;
;                 6: Error in data.          ;
;                 7: Sector marked bad.      ;
; Note: There may be errors if a write is    ;
;       across a cylinder (or sometimes even ;
;       a head) boundary.                    ;
; ------------------------------------------ ;
iowrite proc far                        ; Write sectors through direct I/O.
        pushad                                  ; Save all registers.
        mov     bp,sp                           ; Address the stack.
        push    ds es                           ; Save segments.
        in      al,0A1h                         ; Get PIC 2 mask.
        push    ax                              ; Save it.
        or      al,40h                          ; Disable IRQ 14.
        out     0A1h,al                         ; Set PIC 2 mask.
        hdcwait                                 ; Wait for HDC not busy.
        jnc     iw_ok0                          ; Continue if not busy.
        mov     al,01h                          ; Error 1: Controller busy...
        mov     [_al],al                        ;    .
        jmp     iw_err                          ; ...done.
 iw_ok0:mov     al,[_bh]                        ; Get head number.
        mov     ah,[_cl]                        ; Get drive number.
        and     ax,010Fh                        ; Mask out extra bits.
        shl     ah,04h                          ; Adjust AH.
        or      al,ah                           ; Combine data.
        or      al,0A0h                         ; Set 512 bytes + ECC.
        mov     dx,01F6h                        ; Write drive/head numbers...
        out     dx,al                           ; ...done.
        hdcwait                                 ; Wait for HDC not busy.
        jnc     iw_ok1                          ; Continue if not busy.
        mov     al,01h                          ; Error 1: Controller busy...
        mov     [_al],al                        ;    .
        jmp     iw_err                          ; ...done.
 iw_ok1:mov     ecx,000C0000h                   ; 3s delay.
        mov     dx,01F7h                        ; HDC status register.
 iw_l1: in      al,dx                           ; Read status.
        test    al,40h                          ; Drive ready?
        jnz     iw_ok2                          ; Continue if so.
        loopd   iw_l1                           ; Loop for 3s.
        mov     al,02h                          ; Error 2: Drive not ready...
        mov     [_al],al                        ;    .
        jmp     iw_err                          ; ...done.
 iw_ok2:test    al,10h                          ; Drive ready for write?
        jnz     iw_ok3                          ; Continue if so.
        loopd   iw_l1                           ; Loop for 3s.
        mov     al,03h                          ; Error 3: Cannot write...
        mov     [_al],al                        ;    .
        jmp     iw_err                          ; ...done.
 iw_ok3:mov     al,10h                          ; Set to >8 heads...
        mov     dx,03F6h                        ;    .
        out     dx,ax                           ; ...done.
        mov     dx,01F2h                        ; Write write parameters...
        mov     al,[_ch]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_bl]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_al]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_ah]                        ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,[_bh]                        ;    .
        mov     ah,[_cl]                        ;    .
        and     ax,010Fh                        ;    .
        shl     ah,04h                          ;    .
        or      al,ah                           ;    .
        or      al,0A0h                         ;    .
        out     dx,al                           ; ...done.
        mov     dx,01F1h                        ; Write Precompensation = 0...
        xor     al,al                           ;    .
        out     dx,al                           ; ...done.
        hdcwait                                 ; Wait for HDC not busy.
        jnc     iw_ok4                          ; Continue if not busy.
        mov     al,01h                          ; Error 1: Controller busy...
        mov     [_al],al                        ;    .
        jmp     iw_err                          ; ...done.
 iw_ok4:xor     cx,cx                           ; Get sector count...
        mov     cl,[_ch]                        ; ...done.
        mov     si,[_dx]                        ; Get offset.
        mov     dx,01F7h                        ; Send write command...
        mov     al,30h                          ;    .
        out     dx,al                           ; ...done.
 iw_l2: mov     dx,01F7h                        ; Get status port.
        delay   000Ah                           ; Delay for >400ns.
        in      al,dx                           ; Get status.
        test    al,80h                          ; Busy?
        jnz     iw_l2                           ; Loop if so.
        test    al,29h                          ; Loop if no change...
        jz      iw_l2                           ; ...done.
        test    al,08h                          ; Ready for data?
        jnz     iw_wda                          ; If so, write it.
        test    al,21h                          ; Error in command?
        jnz     iw_dev                          ; If so, return device error.
        jmp     iw_l2                           ; Continue loop.
 iw_wda:push    cx                              ; Save CX.
        mov     cx,0100h                        ; Repeat count.
        mov     dx,01F0h                        ; 16-bit transfer port.
        rep     outsw                           ; Write data.
        pop     cx                              ; Restore CX.
        loop    iw_l2                           ; Loop until done.
        mov     al,12h                          ; Deactivate controller...
        mov     dx,03F6h                        ;    .
        out     dx,ax                           ; ...done.
        mov     al,00h                          ; No error - return 0...
        mov     [_al],al                        ; ...done.
        pop     ax                              ; Reset PIC 2 mask...
        out     0A1h,al                         ; ...done.
        clc                                     ; No error: CF=0.
        pop     es ds                           ; Restore segments.
        popad                                   ; Restore all registers.
        ret                                     ; Return (far).
 iw_dev:mov     al,04h                          ; Error 4: Device fault...
        mov     [_al],al                        ; ...done.
        mov     dx,01F1h                        ; Get error code...
        in      al,dx                           ;    .
        mov     [_ah],al                        ; ...done.
        mov     dx,01F6h                        ; Recalibrate head...
        mov     al,[_bh]                        ;    .
        mov     ah,[_cl]                        ;    .
        and     ax,010Fh                        ;    .
        shl     ah,04h                          ;    .
        or      al,ah                           ;    .
        or      al,0A0h                         ;    .
        out     dx,al                           ;    .
        inc     dx                              ;    .
        mov     al,10h                          ;    .
        out     dx,al                           ; ...done.
        hdcwait                                 ; Wait for HDC not busy.
        mov     al,12h                          ; Deactivate controller...
        mov     dx,03F6h                        ;    .
        out     dx,ax                           ; ...done.
 iw_err:pop     ax                              ; Reset PIC 2 mask...
        out     0A1h,al                         ; ...done.
        stc                                     ; Error: CF=1.
        pop     es ds                           ; Restore segments.
        popad                                   ; Restore all registers.
        ret                                     ; Return (far).
iowrite endp                                    ; End of IOWRITE procedure.

                                        ; Clean up segment.
        assume  nothing                         ; Remove segment assumptions.
idehdc ends                                     ; End of direct I/O segment.
; ========================================================================== ;
