;/*
;                   IBM Confidential                                  ;AC000;
;                   Copyright (C) IBM Corporation 1981-1992           ;AC000;
;                   All Rights Reserved.                              ;AC000;
; */

IF 1
__TONYI__ = 1   ; Speed/Space hacks.
__TONYI__1 = 1  ; Speed/Space hacks.
VCB_OPT = 1     ; Data area optimizations
ENDIF

        page    ,160
        title   MS-DOS BIOS Int 13 & int 2f handler


        include version.inc     ; set build flags
        include biosseg.inc     ; establish bios segment structure

        include msequ.inc
        include biostruc.inc
        include bpb.inc
        include msbds.inc

        include msgroup.inc     ; establish Bios_Data segment

multMULT                equ     4ah
multMULTGETHMAPTR       equ     1
multMULTALLOCHMA        equ     2

;SR;
; Include file for WIN386 support
;
        include win386.inc


;       Most of the disk routines keep es:[di] set up pointing to the
;         currently selected bds.  This is often assumed to be the standard
;         operating environment and in some cases may not be mentioned in
;         the subroutine headers where it will be assumed.

        extrn   call_orig13:far         ; M030
        extrn   prevoper:word
        extrn   secondary_model_byte:byte
        extrn   orig13:dword
        extrn   start_bds:dword
        extrn   fhave96:byte
        extrn   ec35_flag:byte
        extrn   model_byte:byte
        extrn   number_of_sec:byte
        extrn   disksector:byte
        extrn   old13:dword
        extrn   media_set_for_format:byte

        extrn   i2f_dskentry:far

        extrn   ptrsav:dword
        extrn   SysinitPresent:byte
        extrn   FreeHMAPtr:word
        extrn   MoveDOSIntoHMA:dword

;SR;
;New variables for Win386 support
;
        extrn   IsWin386:byte
        extrn   Win386_SI:byte
        extrn   SI_Next:dword


; close data, open Bios_code segment

        tocode

        extrn   Bios_Data_Word:word
        extrn   set_changed_dl:near
        extrn   install_bds:near

;       int 2f function 13h allows the user to change the orig13 int_13 vector
;       after booting.  this allows testing and implementation of custom int_13
;       handlers, without giving up ms-dos error recovery
;       entry:  ds:dx   == addr. of new int_13 handler
;               es:bx   == addr. of new int_13 vector used by warm boot (int19)
;       exit:   orig13  == address of new int_13 handler
;               ds:dx   == old orig13 value
;               es:bx   == old old13  value
;
; int 2f handler for external block drivers to communicate with the internal
; block driver in msdisk. the multiplex number chosen is 8. the handler
; sets up the pointer to the request packet in [ptrsav] and then jumps to
; dsk_entry, the entry point for all disk requests.
;
; on exit from this driver, we will return to the external driver
; that issued this int 2f, and can then remove the flags from the stack.
; this scheme allows us to have a small external device driver, and makes
; the maintainance of the various drivers (driver and msbio) much easier,
; since we only need to make changes in one place (most of the time).
;
;   ax=800h - check for installed handler - reserved
;   ax=801h - install the bds into the linked list
;   ax=802h - dos request
;   ax=803h - return bds table starting pointer in ds:di
;          (ems device driver hooks int 13h to handle 16kb dma overrun
;           problem.  bds table is going to be used to get head/sector
;           informations without calling generic ioctl get device parm call.)

        public  i2f_handler
i2f_handler proc far
        assume  ds:nothing,es:nothing

        cmp     ah,13h
        jz      int2f_replace_int13
        cmp     ah,8
        jz      mine

;
;Check for WIN386 startup and return the BIOS instance data
;
        cmp     ah,MULTWIN386
        jz      win386call

        cmp     ah, multMULT
        jne     @f
        jmp     handle_multmult
@@:
        iret

int2f_replace_int13:
IFDEF VCB_OPT
; Disable interrupts before changing interrupt vectors
        cli
ENDIF
        push    ax                      ; free up a register for caller's ds
        mov     ax,ds                   ;  then we can use ds: -> Bios_Data
        mov     ds,Bios_Data_Word
        assume  ds:Bios_Data
IFDEF __TONYI__1
;---------------------------------------------------------------------
; We can eliminate half of these pushes with XCHG's, and save 10 bytes.
;---------------------------------------------------------------------
;;;;;;;;push    word ptr [orig13]       ;   save old value of old13 and
        push    word ptr [orig13 + 2]   ;   orig13 so that we can
;;;;;;;;push    word ptr [old13]        ;   return them to caller
        push    word ptr [old13 + 2]

        xchg    word ptr [orig13],dx    ;   orig13 := addr. of new int_13
        mov     word ptr [orig13+2],ax
        xchg    word ptr [old13],bx     ;    old13 := addr. of new boot_13
        mov     word ptr [old13+2],es

        pop     es                      ;    es:bx := old old13 vector
;;;;;;;;pop     bx
        pop     ds                      ;    ds:dx := old orig13 vector
        assume  ds:nothing
;;;;;;;;pop     dx
ELSE
        push    word ptr [orig13]       ;   save old value of old13 and
        push    word ptr [orig13 + 2]   ;   orig13 so that we can
        push    word ptr [old13]        ;   return them to caller
        push    word ptr [old13 + 2]

        mov     word ptr [orig13],dx    ;   orig13 := addr. of new int_13
        mov     word ptr [orig13+2],ax
        mov     word ptr [old13],bx     ;    old13 := addr. of new boot_13
        mov     word ptr [old13+2],es

        pop     es                      ;    es:bx := old old13 vector
        pop     bx
        pop     ds                      ;    ds:dx := old orig13 vector
        assume  ds:nothing
        pop     dx
ENDIF
        pop     ax                      ; restore caller's ax

i2f_iret:
        iret


mine:
        assume  ds:nothing
        cmp     al,0f8h                 ; iret on reserved functions
        jae     i2f_iret
        or      al,al                   ; a get installed state request?
        jnz     disp_func
        mov     al,0ffh
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this was dumb.  Just stick the IRET here!
; Save a byte and run faster.
;--------------------------------------------------
        iret
ELSE
        jmp     i2f_iret
ENDIF

disp_func:
        cmp     al,1                    ; request for installing bds?
        jz      do_subfun_01
        cmp     al,3                    ; get bds vector?
        jz      do_get_bds_vector

; set up pointer to request packet

        push    ds
        mov     ds,Bios_Data_Word       ; ds: -> Bios_Data
        assume  ds:Bios_Data

        mov     word ptr [ptrsav],bx    ;otherwise dos function.
        mov     word ptr [ptrsav+2],es
        pop     ds
        assume  ds:nothing
        jmp     i2f_dskentry            ; NOTE:  jump to a FAR function, not an
;                                       ;  IRET type function.  Callers of
;                                       ;  this int2f subfunction will have
;                                       ;  to be careful to do a popf

do_subfun_01:
        assume  ds:nothing
        push    es                      ; save caller's ds, es
        push    ds

        push    ds                      ; put his argument into es
        pop     es

        mov     ds,Bios_Data_Word       ; point ds: -> Bios_Data
        assume  ds:Bios_Data

        call    install_bds

        pop     ds                      ; restore caller's registers
        assume  ds:nothing
        pop     es
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this was dumb.  Just stick the IRET here!
; Save a byte and run faster.
;--------------------------------------------------
        iret
ELSE
        jmp     i2f_iret
ENDIF


do_get_bds_vector:                      ; al=3
        assume  ds:nothing
        mov     ds,Bios_Data_Word
        assume  ds:Bios_Data

        lds     di,[start_bds]
        assume  ds:nothing
ii2f_iret:
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this was dumb.  Just stick the IRET here!
; Save a byte and run faster.
;--------------------------------------------------
        iret
ELSE
        jmp     i2f_iret
ENDIF

;
;WIN386 startup stuff is done here. If starting up we set our WIN386 present
;flag and return instance data. If exiting, we reset the WIN386 present flag
;NOTE: We assume that the BIOS int 2fh is at the bottom of the chain.

win386call:
        push    ds
        mov     ds,cs:Bios_Data_Word
        assume  ds:Bios_Data

        cmp     al, Win386_Init         ; is it win386 initializing?
        je      Win386Init
        cmp     al, Win386_Exit         ; is it win386 exiting?
        jne     win_iret                ; if not, continue int2f chain

Win386Exit:
        test    dx, 1                   ; is it win386 or win286 dos extender?
        jnz     win_iret                ; if not win386, then continue
        and     [IsWin386], 0           ; indicate that win386 is not present
        jmp     short win_iret

Win386Init:
        test    dx, 1                   ; is it win386 or win286 dos extender?
        jnz     win_iret                ; if not win386, then continue

        or      [IsWin386], 1           ; Indicate WIN386 present
        mov     word ptr [SI_Next], bx  ; Hook our structure into chain
        mov     word ptr [SI_Next + 2], es
        mov     bx, offset Win386_SI    ; point ES:BX to Win386_SI
        push    ds
        pop     es

win_iret:
        pop     ds
        assume  ds:nothing
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this was dumb.  Just stick the IRET here!
; Save a byte and run faster.
;--------------------------------------------------
        iret
ELSE
        jmp     short i2f_iret          ;return back up the chain
ENDIF

handle_multmult:
        cmp     al, multMULTGETHMAPTR
        jne     try_2

        push    ds
        call    HMAPtr                  ; get offset of free HMA
        mov     bx, 0ffffh
        mov     es, bx                  ; seg of HMA
        mov     bx, di
        not     bx
        or      bx, bx
        jz      @f
        inc     bx
@@:
        pop     ds
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this was dumb.  Just stick the IRET here!
; Save a byte and run faster.
;--------------------------------------------------
        iret
ELSE
        jmp     ii2f_iret
ENDIF
try_2:
        cmp     al, multMULTALLOCHMA
        jne     try_3

        push    ds
        mov     di, 0ffffh              ; assume not enough space
        mov     es, di
        call    HMAPtr                  ; get offset of free HMA
        assume  ds:Bios_Data
        cmp     di, 0ffffh
        je      InsuffHMA
        neg     di                      ; free space in HMA
        cmp     bx, di
        jbe     @f
        mov     di, 0ffffh
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this is dumb.  Just stick the POP/IRET here!
; Same size and runs faster.
;--------------------------------------------------
        pop     ds
        iret
ELSE
        jmp     short InsuffHMA
ENDIF
@@:
IFDEF __TONYI__
;-----------------------------------------------------------------------
; Save a byte...
;-----------------------------------------------------------------------
        call    near ptr MovFreeHMAPtrToDI
ELSE
        mov     di, FreeHMAPtr
ENDIF
        add     bx, 15
IFDEF __TONYI__
;-----------------------------------------------------------------------
; Save a byte.  We only need to zonk BL since the high order part of the
; AND mask is FFh.  AND'ing something with FF doesn't do anything to it.
;-----------------------------------------------------------------------
        and     bl, 0f0h
ELSE
        and     bx, 0fff0h
ENDIF
        add     FreeHMAPtr, bx          ; update the free pointer
        jnz     InsuffHMA
        mov     FreeHMAPtr, 0ffffh      ; no more HMA if we have wrapped
InsuffHMA:
        pop     ds
        assume  ds:nothing
IFDEF __TONYI__
;--------------------------------------------------
; Wow, this was super dumb.  Just stick the IRET here!
; Those 2 JMP's were useless...
; Save a 3 bytes and run faster.
;--------------------------------------------------
try_3:
        iret
ELSE
        jmp     ii2f_iret
try_3:
        jmp     ii2f_iret
ENDIF
i2f_handler endp

;
;--------------------------------------------------------------------------
;
; procedure : HMAPtr
;
;               Gets the offset of the free HMA area ( with respect to
;                                                       seg ffff )
;               If DOS has not moved high, tries to move DOS high.
;               In the course of doing this, it will allocate all the HMA
;               and set the FreeHMAPtr to past the end of the BIOS and
;               DOS code.  The call to MoveDOSIntoHMA (which is a pointer)
;               enters the routine in sysinit1 called FTryToMoveDOSHi.
;
;       RETURNS : offset of free HMA in DI
;                 BIOS_DATA, seg in DS
;
;--------------------------------------------------------------------------
;
HMAPtr  proc    near
        mov     ds, Bios_Data_Word
        assume  ds:Bios_Data
        mov     di, FreeHMAPtr
        cmp     di, 0ffffh
        jne     @f
        cmp     SysinitPresent, 0
        je      @f
        call    MoveDOSIntoHMA
IFDEF __TONYI__
;---------------------------------------
; Enable saving a byte above......
;---------------------------------------
MovFreeHMAPtrToDI label near
ENDIF
        mov     di, FreeHMAPtr
@@:
        ret
HMAPtr  endp


;       move a 512 byte sector from ds:si to es:di, do not trash cx
;         but go ahead and update direction flag, si, & di

move_sector proc near
        assume  ds:nothing,es:nothing

;       M014 -- begin changes
;
;       The 80386 microprocessor considers an access to WORD 0FFFFH in
;         any segment to be a fault.  Theoretically, this could be handled
;         by the fault handler and the behavior of an 8086 could be emulated
;         by wrapping the high byte to offset 0000h.  This would be a lot
;         of work and was, indeed, blown off by the Win386 guys.  COMPAQ
;         also handles the fault incorrectly in their ROM BIOS for real
;         mode.  Their fault handler was only designed to deal with one
;         special case which occurred in a magazine benchmark, but didn't
;         handle the general case worth beans.
;
;       Simply changing this code to do a byte loop would work okay but
;         would involve a general case performance hit.  Therefore, we'll
;         check for either source or destination offsets being within one
;         sector of the end of their segments and only in that case fall
;         back to a byte move.

        cld
        push    cx
        mov     cx,512/2                ; number of words in a sector

        cmp     si,0fe00h
        ja      movsec_bytes
        cmp     di,0fe00h
        ja      movsec_bytes

ifdef __TONYI__
        if1
        %out    Put a 386 DWORD optimization here some day!!
        endif
;*** TONYI *** There should be 386 detection here and DWORD moves used...
;*** TONYI *** It even pays (a little) on the SX's.
endif

        rep     movsw
        pop     cx
        ret

movsec_bytes:
        shl     cx,1                    ; get number of bytes
        rep     movsb
        pop     cx
        ret

;       M014 -- end changes

move_sector endp



; check_wrap is a routine that adjusts the starting sector, starting head
; and starting cylinder for an int 13 request that requests i/o of a lot
; of sectors. it only does this for fixed disks. it is used in the sections
; of code that handle ecc errors and dma errors. it is necessary, because
; ordinarily the rom would take care of wraps around heads and cylinders,
; but we break down a request when we get an ecc or dma error into several
; i/o of one or more sectors. in this case, we may already be beyond the
; number of sectors on a track on the medium, and the request would fail.
;
; input conditions:
;       all registers set up for an int 13 request.
;
; output:
;       dh - contains starting head number for request
;       cx - contains starting sector and cylinder numbers
;       (the above may or may not have been changed, and are 0-based)
;       all other registers preserved.

check_wrap proc near
        assume  ds:Bios_Data,es:nothing

        push    ax
        push    bx
        push    es
        push    di

        call    find_bds                ; get pointer to bds for drive in dl
        jc      no_wrap                 ;  finished if DOS doesn't use it

        test    es:[di].bds_flags,fnon_removable
        jz      no_wrap                 ; no wrapping for removable media

        mov     bx,es:[di].BDS_BPB.BPB_SECTORSPERTRACK
        mov     ax,cx
        and     ax,3fh                  ; extract sector number
        cmp     ax,bx                   ; are we going to wrap?
        jbe     no_wrap
        div     bl                      ; ah=new sector #, al=# of head wraps

; we need to be careful here. if the new sector # is 0, then we are on the
; last sector on that track.

        or      ah,ah
        jnz     not_on_bound

        mov     ah,bl                   ; set sector=BDS_BPB.BPB_SECTORSPERTRACK if on boundary
        dec     al                      ; also decrement # of head wraps

not_on_bound:
        and     cl,0c0h                 ; zero out sector #
        or      cl,ah                   ; or in new sector #
        xor     ah,ah                   ; ax = # of head wraps
        inc     ax
        add     al,dh                   ; add in starting head #
        adc     ah,0                    ; catch any carry
        cmp     ax,es:[di].BDS_BPB.BPB_HEADS            ; are we going to wrap around a head?
        jbe     no_wrap_head            ; do not lose new head number!!

        push    dx                      ; preserve drive number and head number
        xor     dx,dx
        mov     bx,es:[di].BDS_BPB.BPB_HEADS
        div     bx                      ; dx=new head #, ax=# of cylinder wraps

; careful here! if new head # is 0, then we are on the last head.

        or      dx,dx
        jnz     no_head_bound
        mov     dx,bx                   ; on boundary. set to BDS_BPB.BPB_HEADS

; if we had some cylinder wraps, we need to reduce them by one!!

        or      ax,ax
        jz      no_head_bound
        dec     ax                      ; reduce number of cylinder wraps
no_head_bound:
        mov     bh,dl                   ; bh has new head number
        pop     dx                      ; restore drive number and head number
        dec     bh                      ; get it 0-based
        mov     dh,bh                   ; set up new head number in dh
        mov     bh,cl
        and     bh,3fh                  ; preserve sector number
        mov     bl,6
        xchg    cl,bl
        shr     bl,cl                   ; get ms cylinder bits to ls end
        add     ch,al                   ; add in cylinder wrap
        adc     bl,ah                   ; add in high byte
        shl     bl,cl                   ; move up to ms end
        xchg    bl,cl                   ; restore cylinder bits into cl
        or      cl,bh                   ; or in sector number

no_wrap:
        clc
        pop     di
        pop     es
        pop     bx
        pop     ax
        ret

no_wrap_head:
        mov     dh,al                   ; do not lose new head number
        dec     dh                      ; get it 0-based
        jmp     short no_wrap
check_wrap      endp

;       this is a special version of the bds lookup code which is
;         based on physical drives rather than the usual logical drives
;         carry is set if the physical drive in dl is found, es:di -> its bds
;         otherwise carry is clear
;
;       guaranteed to trash no registers except es:di


find_bds proc   near
        assume  ds:Bios_Data,es:nothing

        les     di,[start_bds]          ; point es:di to first bds

fbds_1:
        cmp     es:[di].bds_drivenum,dl
        je      fbds_2

        les     di,es:[di].bds_link     ; go to next bds
        cmp     di,-1
        jnz     fbds_1

        stc
fbds_2:
        ret

find_bds endp


doint   proc    near
        assume  ds:Bios_Data,es:nothing

        mov     dl,byte ptr [bp.olddx]          ; get physical drive number
        xor     ah,ah
        or      al,al
        jz      dointdone                       ; if zero sectors, return ax=0

        mov     ah,byte ptr [bp.oldax+1]        ; get request code
        push    [bp.oldf]
        popf                                    ; M030
        call    call_orig13                     ; M030

; M030  call    orig13

        pushf
        pop     [bp.oldf]

dointdone:
        ret

doint   endp


; this is the true int 13 handler.  we parse the request to see if there is
; a dma violation.  if so, depending on the function, we:
;   read/write  break the request into three pieces and move the middle one
;       into our internal buffer.
;
;   format      copy the format table into the buffer
;   verify      point the transfer address into the buffer
;
; this is the biggest bogosity of all.  the ibm controller does not handle
; operations that cross physical 64k boundaries.  in these cases, we copy
; the offending sector into the buffer below and do the i/o from there.

int13frame  struc
oldbp   dw  ?
oldax   dw  ?
oldbx   dw  ?
oldcx   dw  ?
olddx   dw  ?
oldds   dw  ?   ; now we save caller's ds, too
olddd   dd  ?
oldf    dw  ?
int13frame  ends


;   entry conditions:
;       ah = function
;       al = number of sectors
;       es:bx = dma address
;       cx = packed track and sector
;       dx = head and drive
;   output conditions:
;       no dma violation.

;       use extreme caution when working with this code.  In general,
;         all registers are hot at all times.
;
;       question:  does this code handle cases where dma errors
;         occur during ecc retries, and where ecc errors occur during
;         dma breakdowns????  Hmmmmm.
;

dtype_array     dd      00400090h       ; 40:90 is drive type array

;       stick some special stuff out of mainline


;       we know we're doing a format command.  if we have changeline
;         support, then flag some special changed stuff and set changed
;         by format bit for all logical drives using this physical drive

format_special_stuff:
        assume  ds:Bios_Data,es:nothing

        cmp     fhave96,0               ; do we have changeline support?
        jz      format_special_stuff_done ; brif not

        push    bx
        mov     bx,fchanged_by_format+fchanged
        call    set_changed_dl          ; indicate that media changed by format
        pop     bx
        jmp     short format_special_stuff_done

;       we know we've got ec35's on the system.  Now see if we're doing
;         a floppy.  If so, create a mask and see if this particular
;         drive is an ec35.  If so, set dtype_array[drive]=93h

ec35_special_stuff:
        assume  ds:Bios_Data,es:nothing
        test    dl,dl                   ; floppy or hard disk?
        js      ec35_special_stuff_done ; if hard drive, we're done

        push    ax                      ; see if this PARTICULAR drive is ec35
        push    cx
        mov     cl,dl                   ; turn drive number into bit map:
        mov     al,1                    ;   assume drive 0
        shl     al,cl                   ;   shift over correct number of times
        test    al,ec35_flag            ; electrically compatible 3.5 incher?
        pop     cx
        pop     ax
        jz      ec35_special_stuff_done ; done if this floppy is not an ec35

        push    bx                      ; free up a far pointer (es:bx)
        push    es

        les     bx,[dtype_array]
        add     bl,dl
        adc     bh,0                    ; find entry for this drive
        mov     byte ptr es:[bx],93h    ; establish drive type as:
                                        ;  (360k disk in 360k drive,
        pop     es                      ;  no double-stepping,
        pop     bx                      ;  250 kbs transfer rate)
        jmp     short ec35_special_stuff_done

; ps2_30 machine has some problem with ah=8h(read drive parm), int 13h.
;this function does not reset the common buses after the execution.
;to solve this problem, when we detect ah=8h, then we will save the result and
;will issue ah=1 (read status) call to reset the buses.


ps2_special_stuff:
        assume  ds:Bios_Data,es:nothing
        cmp     [prevoper],8            ; read driver parm ?
        jz      ps2_30_problem
        cmp     prevoper,15h            ; apparently function 15h fails, too
        jnz     ps2_special_stuff_done  ; it isn't one of the problem functions

ps2_30_problem:                         ; ps2_30 = ps2 model 30.
        push    ax                      ; preserve ax result from int13
        mov     ah,1                    ; read status call resets the bus


        call    call_orig13             ; M030
        pop     ax                      ; restore results of initial int13
        jmp     short ps2_special_stuff_done

;       here is the actual int13 handler

        public i13z
i13z proc       far
        assume  ds:nothing,es:nothing

;       cas -- inefficient!  could push ds and load ds-> Bios_Data before
;         vectoring up here from Bios_Data

        push    ds                      ; save caller's ds register first thing
        mov     ds,Bios_Data_Word       ; and set up our own ds -> Bios_Data
        assume  ds:Bios_Data

; let the operation proceed.  if there is a dma violation, then we do things.

        mov     [prevoper],ax           ; save request
        cmp     ah,romformat
        jz      format_special_stuff    ; go do special stuff for format
format_special_stuff_done:

        cmp     ec35_flag,0             ; any electrically compat 3.5 inchers?
        jnz     ec35_special_stuff      ; go handle it out of line if so
ec35_special_stuff_done:




        call    call_orig13             ; M030

        pushf                           ; save result flags
        cmp     [model_byte],mdl_ps2_30 ; is this a ps2/30?
        jz      ps2_special_stuff       ;  exit mainline to address special
ps2_special_stuff_done:                 ;   ps2/30 problem if so
        popf

        jc      goterr13                ; error on original orig13 call-thru?
ret_from_i13:
        pop     ds
        ret     2                       ; restore ds & iret w/flags

;       most of our code exits through here.  If carry isn't set, then
;         just do a simple exit.  Else doublecheck that we aren't getting
;         a changeline error.

i13ret_ck_chglinerr:
        jnc     ret_from_i13            ; done if not an error termination

i13_ret_error:
        cmp     ah,06h                  ; did i see a change event?
        jnz     int13b                  ; skip if wrong error

        or      dl,dl                   ; is this for the hard disk?
        js      int13b                  ; yes, ignore

        cmp     fhave96,0
        jz      int13b                  ; just in case ROM returned this
                                        ;  error even though it told us it
                                        ;  never would
        push    bx
        mov     bx,fchanged
        call    set_changed_dl
        pop     bx
int13b:
        stc                             ; now return the error
        jmp     ret_from_i13

; some kind of error occurred.  see if it is dma violation

goterr13:
        cmp     ah,09h                  ; dma error?
        jz      gotdmaerr               ; M046
goterr13_xxxx:                          ; M046
        cmp     ah,11h                  ; ecc error?
        jnz     i13_ret_error           ; other error.  just return back.

;M046 chk_validmedia_err13:

;M008 start --- removed check for bds for this physical drive.  Sometimes
;               (like when we're FDISK'ing a virgin partition and the buffer
;               is on a DMA boundary), we can just go ahead without having
;               a BDS for the drive, since we'll always read one sector at
;               a time in that case anyway.  Fixing bug 335.
;
;                                       ;if find_bds fails, then just
;       push    es                      ;    return back to int 13h caller,
;       push    di                      ;      without performing ecc, dma
;       call    find_bds                ;       error handling.
;       pop     di
;       pop     es
;       jc      i13_ret_error
;
;M008 end
;
; test of bit pattern 08h let other errors be passed as dma errors
;
;M046   cmp     ah,09h                  ; dma error code
;M046   jz      gotdmaerr
;
;       M046 -- the following comments no longer apply.  ECC correction
;               is performed on all machines.
;
; soft ecc bug is only applied to pc1 and pc-xt. so, we will enforce
;this ecc error handler for them.   also, since cmc hardfiles in pc at also
;generate a lot of unnecessary ecc errors, we will still cover pc ats as
;it is done in the earlier version of msbio.
;during format/verify operation, we are going to consider any soft ecc as a
;hard error.
;
;       see if the machine we are operating on is a pc, xt or at by checking
;       the model byte.  the soft ecc bug is only on these machines and if
;       the machine we are operating on is not one of these three then we
;       needn't do anything special.  if we are operating one these however
;       we check to see if the error occured during format by checking
;       media_set_for_format.  if it did occur during format we cannot do
;       anything but if not during format then check to see if the error
;       returned in ah is the soft_ecc error and if so go to ok11 since
;       the error can be ignored.

        cmp     [media_set_for_format],1 ; formatting?
        je      i13_ret_error









;
; we have an error status 11h.  this indicates an ecc-corrected error.  note
; that this indicates that the data is probably correct but not certainly
; correct. the roms on pc-1s and pc_xts have a 'bug' in that if an ecc error
; occurs for a multi-sector read, only the sectors up to the one where the
; error occurred are read in. we have no way of knowing how many were read in
; this case, so we redo the operation, reading one sector at a time. if we
; get an ecc error on reading one sector, we ignore the error because the
; sector has been read in.


;       M046 -- begin additions

        cmp     byte ptr [prevoper].1,romread   ; ECC correction only applies to reads
        jnz     i13_ret_error

;       M046 -- end additions

;M046 go_chk_ecc:
        xor     ah,ah


        call    call_orig13     ; M030 reset.  don't care about the result

        mov     ax,[prevoper]
        xor     ah,ah           ; return code = no error
        cmp     al,1                    ; if request for one sector, assume ok
        jz      ret_from_i13            ; return with carry clear

        push    bx
        push    cx
        push    dx
        mov     [number_of_sec],al

loop_ecc:


        mov     ax,201h                 ; M046 read one sector

; we do reads one sector at a time. this ensures that we will eventually
; finish the request since ecc errors on one sector do read in that sector.
;
; we need to put in some "intelligence" into the ecc handler to handle reads
; that attempt to read more sectors than are available on a particular
; track.
;
; we call check_wrap to set up the sector #, head # and cylinder # for
; this request.
;
; at this point, all registers are set up for the call to orig13, except
; that there may be a starting sector number that is bigger than the number
; of sectors on a track.
;
        call    check_wrap              ; get correct parameters for int 13


        call    call_orig13             ; M030
        jnc     ok11_op

        cmp     ah,9                    ; M046  DMA error during ECC read?
        jz      handle_dma_during_ecc   ; M046

        cmp     ah,11h                  ; only allow ecc errors
        jnz     ok11_exit_err           ; other error?

IFNDEF VCB_OPT
; Delete useless mov ah,0 before xor ax,ax
        mov     ah,0                    ; ecc error. reset the system again.
ENDIF


        xor     ax,ax                   ; clear the error code so that if this
                                        ; was the last sector, no error code
                                        ; will be returned for the corrected
                                        ; read. (clear carry too.)
ok11_op:
        dec     [number_of_sec]
        jz      ok11_exit               ; all done?
        inc     cl                      ; advance sector number
        inc     bh                      ; add 200h to address
        inc     bh
        jmp     loop_ecc

;       locate error returns centrally


ok11_exit_err:
        stc                             ; set carry bit again.
ok11_exit:
        pop     dx
        pop     cx
        pop     bx
        jmp     i13ret_ck_chglinerr

;       M046 begin additions

handle_dma_during_ecc:

;       do the single sector read again, this time into our temporary
;          buffer, which is guaranteed not to have a DMA error, then
;          move the data to its proper location and proceed

        push    es
        push    bx
        mov     bx,offset disksector
        push    ds
        pop     es                      ; point es:bx to buffer
        mov     ax,201h                 ; read one sector
        call    call_orig13
        pop     bx
        pop     es
        jnc     handle_dma_during_ecc_noerr
        cmp     ah,11h
        jnz     ok11_exit_err           ; if anything but ecc error, bomb out

;       now we're kosher.  Copy the data to where it belongs and resume
;         the ECC looping code.

handle_dma_during_ecc_noerr:
        push    si
        push    di
        mov     di,bx
        mov     si,offset disksector
        call    move_sector
        pop     di
        pop     si
        jmp     ok11_op

;       M046 end additions

; we truly have a dma violation.  restore register ax and retry the
; operation as best we can.

gotdmaerr:
        mov     ax,[prevoper]
        sti
        cmp     ah,romread              ; save user flags
        jb      i13_done_dmaerr         ; just pass dma error thru for
                                        ;   functions we don't handle

        cmp     ah,romverify
        jz      intverify

        cmp     ah,romformat
        jz      intformat
        ja      i13_done_dmaerr

; we are doing a read/write call.  check for dma problems

;       ******** set up stack frame here!!! ********

        push    dx
        push    cx
        push    bx
        push    ax
        push    bp
        mov     bp,sp
        mov     dx,es                   ; check for 64k boundary error

IFDEF __TONYI__
;---------------------------------------
; Faster on 386's
;---------------------------------------
        add     dx, dx
        add     dx, dx
        add     dx, dx
        add     dx, dx
ELSE
        shl     dx,1
        shl     dx,1
        shl     dx,1
        shl     dx,1                    ; segment converted to absolute address
ENDIF

        add     dx,bx                   ; combine with offset
        add     dx,511                  ; simulate a transfer

; if carry is set, then we are within 512 bytes of the end of the segment.
; we skip the first transfer and perform the remaining buffering and transfer

        jnc     no_skip_first
        jmp     bufferx                 ; restore dh=head & do buffer

;       M046 -- also moved a few things around to make relative branches
;          reach where possible.

; dx is the physical 16 bits of start of transfer (+511).  compute remaining
; sectors in segment.

no_skip_first:
        shr     dh,1                    ; dh = number of sectors before address
        mov     ah,128                  ; ah = max number of sectors in segment
        sub     ah,dh

; ah is now the number of sectors that we can successfully write in this
; segment.  if this number is above or equal to the requested number, then we
; continue the operation as normal.  otherwise, we break it into pieces.
;
;       wait a sec.  this is goofy.  the whole reason we got here in the
;         first place is because we got a dma error.  so it's impossible
;         for the whole block to fit, unless the dma error was returned
;         in error.

        cmp     ah,al                   ; can we fit it in?
        jb      doblock                 ; no, perform blocking.

;       yes, the request fits.  let it happen.

        mov     dh,byte ptr [bp.olddx+1]        ; set up head number
        call    doint
        jmp     bad13                   ; and return from this place

i13_done_dmaerr:
        mov     ah,9                    ; pass dma error thru to caller
        stc
        jmp     ret_from_i13            ; return with error, we know it's not
                                        ;  a changeline error

; verify the given sectors.  place the buffer pointer into our space.

intverify:
        push    es                      ; save caller's dma address
        push    bx

        push    ds                      ; es:bx -> Bios_Data:disksector
        pop     es
dosimple:
        mov     bx,offset disksector    ; do the i/o from Bios_Data:disksector



        call    call_orig13             ; M030
        pop     bx
        pop     es

        jmp     i13ret_ck_chglinerr

; format operation.  copy the parameter table into Bios_Data:disksector

intformat:
        push    es
        push    bx

        push    si
        push    di
        push    ds

;       point ds to the caller's dma buffer, es to Bios_Data
;         in other words, swap (ds, es)

        push    es
        push    ds
        pop     es
        pop     ds
        assume  ds:nothing
        mov     si,bx
        mov     di,offset disksector

        call    move_sector             ; user's data into Bios_Data:disksector

        pop     ds
        assume  ds:Bios_Data
        pop     di
        pop     si                      ; do the i/o from
        jmp     dosimple                ;   Bios_Data:disksector



; we can't fit the request into the entire block.  perform the operation on
; the first block.
;
; doblock is modified to correctly handle multi-sector disk i/o.
; old doblock had added the number of sectors i/oed (ah in old doblock) after
; the doint call to cl.  observing only the lower 6 bits of cl(=max. 64) can
; represent a starting sector, if ah was big, then cl would be clobbered.
; by the way, we still are going to use cl for this purpose since checkwrap
; routine will use it as an input.  to prevent cl from being clobbered, a
; safe number of sectors should be calculated like "63 - # of sectors/track".
; doblock will handle the first block of requested sectors within the
; boundary of this safe value.

doblock:

;try to get the # of sectors/track from bds via rom drive number.
;for any mini disks installed, here we have to pray that they have the
;same # of sector/track as the main dos partition disk drive.
;
        mov     dx,word ptr [bp.olddx]  ;get head #, drive #

        push    cx                      ; prepare a temporary register

        push    es                      ; save user's transfer segment
        push    di                      ;ah - # of sectors before dma boundary
                                        ;al - requested # of sectors for i/o.
        call    find_bds                ; get bds pointer for this disk.
        mov     cx,es:[di].BDS_BPB.BPB_SECTORSPERTRACK          ; fetch up # sectors/trk
        test    es:[di].bds_flags,fnon_removable ;don't have to worry about floppies.
        pop     di
        pop     es                      ; restore original es/di

        mov     al,ah                   ; set al=ah for floppies
        jz      doblockflop             ;they are track by track operation.

        mov     ah,63                   ; ah = 63-secpt (# safe sectors??)
        sub     ah,cl                   ;al - # of sectors before dma boundary
doblockflop:
        pop     cx

doblockcontinue:
        cmp     ah,al                   ;if safe_# >= #_of_sectors_to_go_before dma,
        jae     doblocklast             ;then #_of_sectors_to_go as it is for doint.

        push    ax                      ;save ah, al
        mov     al,ah                   ;otherwise, set al to ah to operate.
        jmp     short doblockdoint      ;doint will set ah to a proper function
                                        ; in [bp.oldax]

doblocklast:
        mov     ah,al
        push    ax                      ;save ah
doblockdoint:                           ;let ah = al = # of sectors for this shot
        call    doint
        jc      bad13                   ;something happened, bye!

        pop     ax
        sub     byte ptr [bp.oldax],ah  ;decrement by the successful operation
        add     cl,ah                   ;advance sector #. safety gauranteed.
        add     bh,ah                   ;advance dma address
        add     bh,ah                   ;twice for 512 byte sectors.
        cmp     ah,al                   ;check the previous value
        je      buffer                  ;if #_of_sectors_to_go < safe_#,
                                        ; then we are done already.

        sub     al,ah                   ;otherwise, #_sector_to_go = #_of_sector_to_go - safe_#
        call    check_wrap              ;get new cx, dh for the next operation.
        jmp     short doblockcontinue   ;handles next sectors left.


bufferx:
        mov     dh,byte ptr [bp.olddx+1] ; set up head number
buffer:
        push    bx
        mov     ah,byte ptr [bp.oldax+1]
        cmp     ah,romwrite
        jnz     doread

; copy the offending sector into local buffer

        push    es
        push    ds
        push    si
        push    di

        push    ds                      ; exchange segment registers
        push    es
        pop     ds
        assume  ds:nothing
        pop     es
        assume  es:Bios_Data

        mov     di,offset disksector    ; where to move
        push    di                      ; save it
        mov     si,bx                   ; source
        call    move_sector             ; move sector into local buffer
        pop     bx                      ; new transfer address (es:bx =
                                        ;    Bios_Data:diskbuffer)
        pop     di                      ; restore caller's di & si
        pop     si
        pop     ds                      ; restore Bios_Data
        assume  ds:Bios_Data
        mov     al,1

; see if we are wrapping around a track or head

        mov     dl,byte ptr [bp.olddx]  ; get drive number
        call    check_wrap              ; sets up registers if wrap-around

;   ah is function
;   al is 1 for single sector transfer
;   es:bx is local transfer addres
;   cx is track/sector number
;   dx is head/drive number
;   si,di unchanged

        call    doint
        pop     es                      ; restore caller's dma segment
        assume  es:nothing
        jc      bad13                   ; go clean up
        jmp     short dotail

; reading a sector.  do int first, then move things around

doread:
        push    es
        push    bx
        push    ds                      ; es = Bios_Code
        pop     es

        mov     bx,offset disksector
        mov     al,1

; see if our request will wrap a track or head boundary

        mov     dl,byte ptr [bp.olddx]  ; get drive number
        call    check_wrap              ; sets up registers if wrap-around

;   ah = function
;   al = 1 for single sector
;   es:bx points to local buffer
;   cx, dx are track/sector, head/drive

        call    doint

        pop     bx                      ; restore caller's dma address
        pop     es
        assume  es:nothing

        jc      bad13                   ; error => clean up

        push    si
        push    di

        mov     di,bx
        mov     si,offset disksector
        call    move_sector

        pop     di
        pop     si

; note the fact that we've done 1 more sector

dotail:
        pop     bx                      ; retrieve new dma area
        add     bh,2                    ; advance over sector
        inc     cx
        mov     al,byte ptr [bp.oldax]
        clc
        dec     al
        jz      bad13                   ; no more i/o

; see if we wrap around a track or head boundary with starting sector
; we already have the correct head number to pass to check_wrap

        mov     dl,byte ptr [bp.olddx]  ; get drive number
        call    check_wrap              ; sets up registers if wrap-around
        call    doint

; we are done.  ax has the final code; we throw away what we got before

;       M046  -- okay gang.  Now we've either terminated our DMA loop,
;                  or we've finished.  If carry is set now, our only
;                  hope for salvation is that it was a read operation
;                  and the error code is ECC error.  In that case, we'll
;                  just pop the registers and go do the old ECC thing.
;                  When the DMA error that got us here in the first
;                  place occurs, it'll handle it.

bad13:
        mov     sp,bp
        pop     bp
        pop     bx                      ; throw away old ax result
        pop     bx
        pop     cx
        pop     dx

        jc      xgoterr13_xxxx          ; M046 go handle ECC errors
        jmp     ret_from_i13            ; M046 non-error exit

xgoterr13_xxxx:
        jmp     goterr13_xxxx           ; M046

handle_ecc_during_dma:
i13z endp


Bios_Code       ends
        end
