SCCSID equ  "@(#)pga.asm        6.9 92/03/27"
PAGE 60,132
;
;        IBM/Microsoft Confidential
;
;        Copyright (c) IBM Corporation 1987, 1989
;        Copyright (c) Microsoft Corporation 1987, 1989
;
;        All Rights Reserved
;
;       Modification History:
;
;       54400   10/08/92 BJH    Added code to _PGSwitchInit to copy the
;                               ptda_NewFiles byte into the TCBNewFlags
;                               field in the first TCB.
;                               This (along with the change in
;                               src\dos\dosinc\tkptda.h) will allow the
;                               kernel to use long file names during init.
;       62856   02/24/93 BJH    Had to redelta the fix for 54400 after it was
;                               blown away by the delta for defect 56884.
;      122474   09/21/95 ChgTeam  Added protection from NMI during TSS modification
;
;      134780   09/29/95 BZ     Added code to pgSwitchContext to clear the
;                               VIP and VIF CPU flags before switching context
;                               from a VME-enabled VDM.
;
;      154438   05/10/96 cgw    mask PIC while in real mode, add drive parm
;
;      154438   05/13/96 cgw    Add second drive parm to hddumpsys for last
;                               dlist entry
;      155605   05/25/96 cgw    clean up debug msgs to reduce memory use
;
;      151221   05/28/96 MT     Must clear conventional memory.
;      158471   06/31/96 TPL    Move high memory clearing code from HIDE.SYS
;                               into _BootDos16 to avoid system lockup on
;                               the Thinkpad 750Cs
;      159979   07/19/96 TPL    Add disk reset before the 1st I/O to
;                               insure normal disk operations
;      159839   07/19/96 TPL    Rollover fix from defect 155351
;      160195   08/03/96 MT     The os2ldr saves the original IVT, BIOS Data Area,
;                               and XBDA which can be retrieved via DosHlpSetDosEnv

.xlist
include dosseg.inc
include basemaca.inc
include fnomem.inc
include osmaca.inc
include osconst.inc
include ostypes.inc
include mi.inc
include dossymc.inc
include sigdef.inc
include except.inc
include pgexport.inc
include protmode.inc
include seldesc.inc
include tkexport.inc
include vmexport.inc
include infoseg.inc
include curdir.inc
include dirent.inc
include find.inc
include screen.inc
include sem.inc
include tkptda.inc
include mvdmexp.inc
include pglocal.inc
include selxport.inc
include hybernat.inc
include bpb.inc

ifdef INCDEBUG
include dbgex.inc
endif ;DEBUG

include proc.inc
include doshlp.inc
include sft.inc
include mft.inc
include pageio.inc
include iodelay.inc
.list

        ModName pga, FNO_PGA_ASM

xpte_s struc                    ; BUGBUG: until h2inc sizes unions correctly
        dd      ?
xpte_s ends

PTESHIFT        equ     2       ; to convert from index to PDE offset

.errnz  (size vmlh_s) - (size fakelock_s)
.errnz  (size pgdata_s) - (size pgdatafake_s)
.errnz  (size pgpc_s) - (size pgpcfake_s)

CpuMode 386

ifdef INCDEBUG
        EXTRN32         _DBGDRDisableVDM
        EXTRN32         _DBGDREnableVDM
endif ;DEBUG

;       extrn           _TKRing2Write:near
        extrn           RealModeNoInt:near
        extrn           RealMode:near
ifdef HYBERI13
        extrn           ProtMode:near
endif ;HYBERI13
        extrn           MSSTACKSIZE:ABS
        EXTRNF32        _TKSetSSBase
        EXTRN32         _SELSetDescInfo
        EXTRN32         _SELConvertToLaddr
        EXTRN32         _VMSetMem
        EXTRN32         _PGKStackMap
        EXTRN32         _pgPageToPte
ifdef PMDEBUG
        EXTRN32         _TKWakeup
        EXTRN32         _TKSleep
endif
        EXTRN16         IntNMI

        ReferGlobal     TSS,byte
        ReferGlobal     _Lis2,byte
        ReferGlobal     _Sis2,byte
        ReferGlobal     SISData,byte
        ReferGlobal     DFTss,byte
        ReferGlobal     NWDTss,byte
        ReferGlobal     DHF,dword
        ReferGlobal     pgMstable,byte
        ReferGlobal     APM_Flags,word
        ReferGlobal     ABIOS_present,byte                      ; 122474

        ReferGDT        GDT_PTDA,qword
        ReferGDT        GDT_LDT,word
        ReferGDT        GDT_MSSTACK,WORD
        ReferGDT        GDT_SFT,WORD

        ReferFlat       _CpuType,dword
        ReferFlat       _pPTDACur,dword
        ReferFlat       _pPTDAFirst,dword
        ReferFlat       _pTCBCur,dword
        ReferFlat       _pTSDCur,dword
        ReferFlat       _SELGDTLaddr,dword
        ReferFlat       _TKSSBase,dword
        ReferFlat       _TKTCBBias,dword
        ReferFlat       _pgpPageDir,dword
        ReferFlat       _pgData,byte
        ReferFlat       _ppteKStackBase,dword
        ReferFlat       _pKStackBase,dword

ifdef INCDEBUG
        ReferFlat       _DBGlmskDR7system,dword
        ReferFlat       _DBGpdcbCur,dword
endif ;DEBUG

        ReferFlat       _CpuStepping,dword
        ReferFlat       _pgcPageFaults,dword
        ReferFlat       _pgPageablePAI,dword
        ReferFlat       _pgPhysPages,dword
        ReferFlat       _hyberOpCount,dword
        ReferFlat       _smSFNarr,dword
        ReferFlat       _Pentium_Feature_Flags,dword

DATA    segment

; Moved to sdata.asm for System Anchor Block - f122427
        extrn  _pgPhysPageDir:dword
DATA    ends


_DATA   segment
        public  _PGSCNLocals, _PGSCLocalsInit
SCNLOCALS       equ     1
_PGSCNLocals    dd      SCNLOCALS       ; number of dwords PGSwitchContext
                                        ;    expects on stack
_PGSCLocalsInit dd      F_INTERRUPT     ; initial value(s) for stack variables
                                        ;    during thread creation

    public  _clDescTaskCopy, _clDescThreadCopy, _pGDTTaskCopy, _pGDTThreadCopy
    public  _clDescTIBOffset, _pGDTTIBCopy
_pGDTThreadCopy dd      ?               ; destination of TSD to GDT descriptor
                                        ;  copy for intra-task thread switches

_pGDTTaskCopy   dd      ?               ; destination of TSD to GDT descriptor
                                        ;  copy for inter-task thread switches

_pGDTTIBCopy    dd      ?               ; destination of TSD to GDT descriptor
                                        ;  copy for inter-task thread switches

_clDescTaskCopy dd      4               ; number of dwords copied during
                                        ;  inter-task context switch

_clDescThreadCopy dd    2               ; number copied during intra-task

_clDescTIBOffset dd     8               ; Offset to _pGDTTIBCopy

ifdef   INFOSTRICT
        public  InfoSegsValid
InfoSegsValid   db      FALSE
endif
_DATA   ends


      FARCODE HIGH32
HIGH32CODE segment


;***LP  _pgEnablePaging - Enable paging and edit DGROUP to map linear space
;
;       _pgEnablePaging sets CR3 and enables paging in CR0, then calls the
;       virtual memory manager to make DGROUP point to all of linear memory.
;       It also sets the CR3 image in the TSS and in the Double Fault TSS.
;
;       ENTRY   epg_pdpaddr - page directory paddr
;       EXIT    NONE
;       USES    EAX, CR0, CR3, Flags

Procedure _pgEnablePaging,,,,esp
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar epg_pdpaddr,DWORD

        mov     eax,[epg_pdpaddr]
        mov     [TSS].tss_cr3,eax       ; Load TSS image
        mov     [DFTss].tss_cr3,eax     ; Load DF TSS image
        mov     [NWDTss].tss_cr3,eax    ; Load NWD TSS image
        mov     cr3,eax                 ; Load CR3

        mov     eax,cr0
ifdef ERRATA486
        or      eax,CR0_PG+CR0_WT+CR0_CE ;enable paging and the 486 cache
else
        or      eax,CR0_PG               ;enable paging
endif
        cmp     [_CpuType],CPU_486      ; On a 486?
        jb      short pep4              ;  no, write protection is not available
        or      eax,CR0_WP              ;  yes, enable write protection
pep4:
        mov     cr0,eax

        RETC
EndProc _pgEnablePaging


;***LP  _pgFlushTLB - Flush the TLB.
;
;       _pgFlushTLB flushes the CPU's TLB.  cr3 is reloaded from its shadow
;       image to work around a 386 chip bug.
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    EAX
;
;       FS and GS must be preserved (DevHelp requirement)

Procedure _pgFlushTLB,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
        mov     eax,[_pgPhysPageDir]
        mov     cr3,eax
        RETC
EndProc _pgFlushTLB


;***LP  _lldt - perform an lldt instruction with GDT_LDT
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    EAX

Procedure _lldt,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
        mov     eax,GSEL GDT_LDT
        lldt    ax
        RETC
EndProc _lldt


ifdef OMIT ;these routines have been replaced by allocbit and freebit


;***LP  _pgAllocBit - Allocate a bit from a dword bitmap
;
;       _pgAllocBit allocates a bit from a dword bitmap.
;
;       ENTRY   ab_pbm - bitmap pointer
;               ab_cbits - size of bitmap (in BITs)
;       EXIT    (EAX) = 0 on failure
;               (EAX) = 1-based bit number on success
;       USES    EAX, ECX, EDX, ES, Flags

Procedure _pgAllocBit,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar alb_pbm,DWORD
    ArgVar alb_cbits,DWORD
        EnterProc
        push    edi
        push    esi
        push    ds                      ; (es) = (ds)
        pop     es
        mov     edi,[alb_pbm]           ; (edi) = pointer to bitmap
        mov     ecx,[alb_cbits]         ; (ecx) = size in bits
        add     ecx,31
        shr     ecx,5                   ; round up to size in dwords
        mov     eax,-1                  ; (eax) = pattern to skip
    repe scasd                          ; search for dword with any bit clear
        je      short albe              ; jmp if no bit is clear

        sub     edi,4                   ; point to subject dword
        mov     edx,[edi]               ; (edx) = grab bitmap dword
        mov     eax,1                   ; (eax) = 1-based bit number in dword
        mov     esi,eax                 ; (esi) = bit to test
alb00:  test    edx,esi                 ; test single bit
        jz      short alb10             ; jmp if bit clear
        shl     esi,1                   ; shift bit to test
        inc     eax                     ; inc bit number
        jmp     short alb00

;       Found a clear bit:
;
;       (eax) = 1-based bit number in dword
;       (ecx) = count of unexamined bitmap dwords
;       (edx) = bitmap dword with free bit
;       (esi) = bit mask
;       (edi) = pointer to bitmap dword with free bit

alb10:  or      edx,esi                 ; turn on bit (assume success)
        mov     ecx,edi
        sub     ecx,[alb_pbm]
        shl     ecx,3                   ; convert to bit count
        add     eax,ecx                 ; (eax) += number of bits before dword
        cmp     eax,[alb_cbits]         ; is the found bit valid?
        ja      short albe              ; jmp if out of range
        mov     [edi],edx               ; set the newly allocated bit
        jmp     short albx

albe:   xor     eax,eax                 ; indicate failure
albx:   pop     esi
        pop     edi
        LeaveProc
        RETC
EndProc _pgAllocBit


;***LP  _pgFreeBit - Free a bit in a dword bitmap
;
;       _pgFreeBit frees a bit in a dword bitmap.
;
;       ENTRY   frb_pbm - bitmap pointer
;               frb_bitno - 1-based bit number
;       EXIT    NONE
;       USES    EAX, ECX, EDX, Flags

Procedure _pgFreeBit,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar frb_pbm,DWORD
    ArgVar frb_bitno,DWORD
        EnterProc

        mov     ecx,[frb_bitno]         ; (ecx) = bit number
        dec     ecx                     ; convert to 0-based
        mov     eax,ecx
        shr     eax,5                   ; (eax) = 0-based dword number
        add     eax,[frb_pbm]           ; (eax) += pointer to bitmap
        mov     edx,not 1               ; set all but bit 0
        rol     edx,cl                  ; rotate clear bit into position
        and     [eax],edx               ; clear specified bit

        LeaveProc
        RETC
EndProc _pgFreeBit

endif ; OMIT

;***    PGVerifyInfoSegs - Verify copies of local and global infosegs are in
;                          sync with the master copies.
;
;       PGVerifyInfoSegs is invoked from Exit_Kmode (and macros) and the
;       interrupt manager to check the global and local infoseg copies.  An
;       internal error is generated if the copies are not identical.
;
;       ENTRY   DS - Flat context
;       EXIT    NONE
;       USES    ECX, ESI, EDI, ES, Flags

vie_local:      Panic   "_PGVerifyInfoSegs: local %x", esi
vie_system:     Panic   "_PGVerifyInfoSegs: system %x", esi

Procedure _PGVerifyInfoSegs
    assume ds:FLAT, es:nothing, fs:nothing, gs:nothing, ss:nothing

ifdef   INFOSTRICT
        cmp     InfoSegsValid,FALSE
        je      short pvisx             ; don't check until they're valid

        push    ds
        pop     es
        assume  es:FLAT

;       Verify local infoseg

        cld                             ; must set direction
        mov     edi,[_pPTDACur]
        lea     edi,[edi].ptda_infoseg  ; (edi) = master local infoseg
        mov     esi,[_Lis2].is_va       ; (esi) = local infoseg copy
        mov     ecx,LIS_LEN shr 2
        rep     cmpsd
        jne     short vie_local         ; panic if not identical
        .errnz  (LIS_LEN mod 4)
;;        cmpsw                         ; these next two lines are only needed
;;        jne     vie_local             ;   if the infoseg is odd word-length

;       Verify system infoseg

        mov     edi,offset FLAT:[SISData] ; (edi) = master system infoseg
        mov     esi,[_Sis2].is_va         ; (esi) = system infoseg copy
        mov     ecx,SIS_LEN shr 2
        rep     cmpsd
        jne     short vie_system          ; panic if not identical
        mov     ecx,SIS_LEN mod 4
        rep     cmpsb
        jne     short vie_system
endif
pvisx:  RETC

EndProc   _PGVerifyInfoSegs


;***    pgSwitchContext - macro to switch ptda, LDT, tcb and page table
;                         contexts.
;
;       pgSwitchContext is invoked from PGSwitchContext and PGSwitchContextInit
;       to do the work of switching the current kernel stack, ptda, LDT, tcb,
;       and page table context.  This code is in a macro to avoid execution
;       of conditional code needed for initialization only.
;
;       ENTRY   ptcb - pointer to ascending tcb
;               init - flag to force generation initialization code
;       EXIT    see _PGSwitchContext and _PGSwitchContextInit
;       USES    EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP, ES, FS, GS, SS, Flags

TASKINFOPTE     equ     (SEL_TASKINFOLADDR and not PAGEMASK) shr \
                                                (PAGESHIFT - PTESHIFT)
SYSINFOPTE      equ     (SEL_SYSINFOLADDR and not PAGEMASK)  shr \
                                                (PAGESHIFT - PTESHIFT)
INFOPDE         equ     (SEL_SYSINFOLADDR and PAGEDIRMASK) shr \
                                                (PAGEDIRSHIFT - PTESHIFT)
pscie:          Panic   "PGSwitchContext"

pgSwitchContext macro   ptcb, init
        local   psc5, psc7                                           ;134780
        local   psc10, psc20, psc30, psc40, psc50, psc60, psc65, psc67, psc70
        local   psc75, psc80, psc90, psc91, psc100, psc101
        local   psc_4lwp, psc_3lwp, psc_2lwp, psc_1lwp
        local   psc61, psc68, psc81                                            ;122474

        mov     edx,[_pPTDACur]         ;; fetch old pptda

ifb <init>


        ; Clear the VIF and VIP flags of a VME-enabled VDM, so       ;134780
        ; the next scheduled task does not inherit these flags.      ;134780
        ; (NOTE: Since POPFD does not modify these flags,            ;134780
        ;        we must use an IRETD.)                              ;134780
                                                                     ;134780
        cmp     [edx].ptda_vme,1     ; Is this a VME enabled VDM?    ;134780
        jne     short psc7           ; No, so nevermind....          ;134780
        push    eax                     ; Preserve EAX               ;134780
        pushfd                          ; Get current EFLAGS         ;134780
        pop     eax                     ; (EAX) = EFLAGS             ;134780
        and     eax, NOT (F_VIP + F_VIF); Clear VIP and VIF eflags   ;134780
        push    eax                     ; Push EFLAGS                ;134780
        push    cs                      ; Push cs                    ;134780
        push    offset FLAT:psc5        ; Push return address offset ;134780
        iretd                           ; Clear VIP and VIF          ;134780
                                                                     ;134780
psc5:   pop     eax                     ; Restore EAX                ;134780
psc7:                                                                ;134780

;;      See if either of the infosegs have been written to.  First check
;;      if their page table is present.

        .errnz  (PT_DIRTY or PT_PRESENT) and 0FFFFFF00H
        .errnz  (SEL_TASKINFOLADDR and PAGEDIRMASK) ne \
                (SEL_SYSINFOLADDR  and PAGEDIRMASK)

        mov     eax,[_pgpPageDir]       ;; (eax) = address of page directory
        test    byte ptr [eax + INFOPDE],PT_PRESENT
        jz      short psc60             ;; skip if page table is not present
;; GSR -BUGBUG
        jmp     short psc60
;; GSR end

        mov     esi,[edx].ptda_pgdata+pd_ppte ;; (esi) = base of pte's

;;      check the system infoseg pte

        mov     eax,[esi + SYSINFOPTE]
        and     eax,PT_PRESENT + PT_DIRTY
        cmp     eax,PT_PRESENT + PT_DIRTY
        je      short psc30             ;; the system infoseg has been dirtied

;;      check the local infoseg pte

        mov     eax,[esi + TASKINFOPTE]
        and     eax,PT_PRESENT + PT_DIRTY
        cmp     eax,PT_PRESENT + PT_DIRTY
        jne     short psc60             ;; all is well, continue

;;      kill the descending process

psc20:  mov     ebx,SEL_TASKINFOLADDR   ;; get address that was hit
        jmp     short psc40
psc30:  mov     ebx,SEL_SYSINFOLADDR    ;; get address that was hit

psc40:  and     byte ptr [esi + SYSINFOPTE],NOT PT_DIRTY
        and     byte ptr [esi + TASKINFOPTE],NOT PT_DIRTY
        mov     esi,&ptcb               ;; (esi) = pointer to ascending tcb
;       push    [esi].TCBpPTDA          ;; push pointer to ascending ptda
;       push    ebx                     ;; push infoseg address
;       call    _TKRing2Write
        jmp     pscie                   ; BUGBUG until TKRing2Write exists
        mov     edx,[_pPTDACur]         ;; fetch old pptda again
        mov     edi,[_Sis2].is_ppf      ;; (edi) = pointer to system infoseg pf

;;      (ESI) = base of descending processes ptes
;;      (EDI) = pointer to system infoseg's pf structure

;;      copy the global infoseg

psc50:  mov     esi,offset FLAT:[SISData]   ;; (esi) = address of master
        mov     edi,[_Sis2].is_va           ;; (edi) = address of copy
        mov     ecx,SIS_LEN SHR 2
        rep     movsd
        mov     ecx,SIS_LEN AND 3
        rep     movsb
endif

;;      Set global variables for the current tcb, ptda and the SSToDS
;;      macro to point to the ascending task's per-task data.  Fetch
;;      the old tcb along the way.  But first, update the TIB pointer
;;      in the system infoseg (THIS IS AN UNDOCUMENTED FIELD).
;;      (edx) = descending pptda

psc60:  mov     ebx,&ptcb               ;; (ebx) = pointer to ascending tcb

; disable NMI here, don't bother with reading port 71                   122474
;
        cli                             ; stop clock from enabling NMI  122474
        mov al,0dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short psc61                 ; jump if it is (MCA machine)   122474
        mov al,8dh                      ; no, it's an ISA machine       122474
psc61:                                  ;                               122474
        out 70h,al                      ;                               122474

ifb     <init>
        mov     esi,[_Sis2].is_va       ;; (esi) = address of infoseg copy
        mov     eax,[ebx].TCBptib       ;; (eax) = TIB pointer
        mov     [esi].SIS_LEN,eax       ;; Store it in system infoseg
        .errnz  (SIS_LEN gt PAGESIZE - 8)
endif

        mov     esi,[ebx].TCBpPTDA      ;; (esi) = pointer to ascending ptda
        mov     [_pPTDACur],esi         ;; set new current ptda
        mov     ecx,[ebx].TCBBiasTSD
        mov     [_TKSSBase],ecx         ;; set SSToDS() bias
        mov     ecx,[ebx].TCBBiasTCB
        mov     [_TKTCBBias],ecx        ;; set current TCB to flat bias
        mov     ecx,ebx
        xchg    [_pTCBCur],ecx          ;; set new current tcb, fetch old
        mov     eax,[ebx].TCBCurrTCB    ;; (eax) = segment relative TCB offset
        mov     [esi].CurrTCB,ax        ;; set CurrTCB
        mov     eax,[ebx].TCBCurrTSD    ;; (eax) = segment relative TSD offset
        mov     [esi].CurrTSD,ax        ;; set CurrTSD

;;      (EBX) = new ptcb
;;      (ECX) = old ptcb
;;      (EDX) = old pptda
;;      (ESI) = new pptda

ifb <init>
        cmp     ecx,ebx                 ;; Switching to same thread?
        jz      psc80                   ;; yes - skip most everything

        NullContext <fs,gs>,di                                          ;57406

;;      Update the PIB pointer in the global infoseg (THIS IS AN
;;      UNDOCUMENTED INFOSEG FIELD)

        mov     edi,[_Sis2].is_va       ;; (edi) = address of infoseg copy
        mov     eax,[esi].ptda_ppib     ;; (eax) = PIB pointer
        mov     [edi].SIS_LEN+4,eax     ;; Store it in system infoseg
        .errnz  (SIS_LEN gt PAGESIZE - 8)
endif

        mov     edi,[ebx].TCBpTSD       ;; (edi) = new thread's TSD
        mov     [_pTSDCur],edi          ;; update current TSD pointer
        mov     edi,[ecx].TCBpTSD       ;; (edi) = old thread's TSD
        pushfd                          ;; save interrupt flag
        .errnz SCNLOCALS - 1            ;; should equal number of dwords pushed
ifdef PMDEBUG                                                           ;159839
    ifb <init>                                                          ;159839
        mov     [edi].TSDKernelEBP,ebp  ;; save old thread's ring 0 bp  ;159839
    endif                                                               ;159839
endif                                                                   ;159839
        mov     [edi].TSDKernelESP,esp  ;; save old thread's ring 0 esp
                                        ;; (also sets dirty and accessed bits
                                        ;; in the TSD's PTE--this is crucial!)

ifnb <init>
;;      The SysInit TSD resides at a different address in TASKAREA than
;;      CurrTSD does.  We adjust for that here.  _PGSwitchContextInit may
;;      be called more than once, but the esp value will differ.

        cmp     esp,TKOTSD              ;; SysInit TCB (absolute first switch?)
        jb      short psc65             ;; no - continue.

        mov     eax,esp
        sub     eax,TKOTSD              ;; Adjust ESP from old TCB
        add     eax,[ebx].TCBCurrTSD    ;; Adjust ESP to new TCB
        mov     [edi].TSDKernelESP,eax  ;; Set initial dispatch ESP
psc65:
endif

ifb <init>
        cmp     edx,esi                 ;; are both threads in the same task?
        jz      psc70                   ;; yes, skip infoseg copy & pde editing

ifdef INCDEBUG
;;      If the old or new process is a debuggee, update the debug registers

        mov     eax,[edx].ptda_flDbgEnabled
        mov     ecx,[esi].ptda_flDbgEnabled

;;      The ptda_flDbgEnabled and the ptda_pdcb are actually the same field.
;;      If either process is being debugged, we'll need these values so we
;;      test by xor'ing the values together  (This works because no two
;;      processes can have the same dcb).  If we do take the jump, we can
;;      restore the original values by just doing another xor.

        xor     eax,ecx                 ;; old/new pptda debugged?
        jnz     psc90                   ;; yes, update debug registers

endif ;DEBUG

;;      Do not insert any code between the above jump and the following
;;      label!  The code that updates the debug registers returns to the
;;      following label.

psc67:

;;      Zero page directory entries for the descending task, and
;;      set up valid page directory entries for the ascending task.
;;
;;      Copy the local infoseg from the ptda to the shared arena
;;      (via system arena alias)

; disable NMI here, don't bother with reading port 71                   122474
;
        cli                             ; stop clock from enabling NMI  122474
        mov al,0dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short psc68                 ; jump if it is (MCA machine)   122474
        mov al,8dh                      ; no, it's an ISA machine       122474
psc68:                                  ;                               122474
        out 70h,al                      ;                               122474

        mov     eax,esi                 ;; save new pptda
        lea     esi,[esi].ptda_infoseg
        mov     edi,[_Lis2].is_va       ;; (edi) = pointer to system arena alias
        mov     ecx,LIS_LEN SHR 2
        rep     movsd
;;        movsw                         ;; only needed if infoseg is odd-word
        .errnz  (LIS_LEN mod 4)         ;;    length
        mov     esi,eax

endif

        lea     edx,[edx].ptda_pgdata
        push    ebx

;;      (EDX) = ppgdata (old task)
;;      (ESI) = new pptda

        mov     ebx,[edx].pd_base       ;; (ebx)->low CR pdes in page directory
        shr     ebx,PAGEDIRSHIFT - PAGESHIFT - PTESHIFT
        add     ebx,[_pgpPageDir]

        xor     eax,eax
        movzx   ecx,[edx].pd_cpdelow
        mov     edi,ebx
        rep stosd                       ;; zero pdes at low end of CR arena
        movzx   ecx,[edx].pd_cpdehigh
        movzx   edi,[edx].pd_maxpde
        dec     edi
        lea     edi,[ebx+edi*size xpte_s]
        std
        rep stosd                       ;; zero pdes at high end of CR arena
        cld
        lea     edx,[esi].ptda_pgdata

;;      (EDX) = ppgdata (new task)

        mov     ebx,[edx].pd_base
        shr     ebx,PAGEDIRSHIFT - PAGESHIFT - PTESHIFT
        add     ebx,[_pgpPageDir]

        movzx   ecx,[edx].pd_cpdelow
        mov     esi,[edx].pd_ppde
        mov     edi,ebx
        rep movsd                       ;; set up pdes at low end of CR arena

        movzx   ecx,[edx].pd_cpdehigh
        movzx   edi,[edx].pd_maxpde
        dec     edi
        mov     esi,[edx].pd_ppde
        lea     esi,[esi+edi*size xpte_s]
        lea     edi,[ebx+edi*size xpte_s]
        std
        rep movsd                       ;; set up pdes at high end of CR arena
        cld

        pop     ebx

;;      Update descriptors that need to be updated during inter-task
;;      context switches.

;;      WARNING: DO NOT PUSH OR POP!  Do not access the SS segment
;;      in any way from here until the END WARNING below.
;;      The mapping of the stack is changing!

        cli
        mov     esi,[ebx].TCBpTSD       ; (esi) = new thread's TSD
        mov     esp,[esi].TSDKernelESP  ; restore new thread's ring 0 esp
        lea     esi,[esi].TSDdescTaskCopy ; (esi) = ptr to source descriptors
        mov     edi,[_pGDTTaskCopy]     ; (edi) = pointer to GDT destination
        mov     ecx,[_clDescTaskCopy]   ; (ecx) = # of dwords to copy
        rep     movsd

        mov     ecx,GSEL GDT_LDT
        lldt    cx                      ; load ldt register
        jmp     short psc75             ; jump to stack pte mapping

psc70:  ;; we jump to this label if we are doing an intra-task thread switch

;;      Update descriptors that need to be updated during intra-task
;;      context switches.

;;      WARNING: DO NOT PUSH OR POP!  Do not access the SS segment
;;      in any way from here until the END WARNING below.
;;      The mapping of the stack is changing!

        cli
        mov     esi,[ebx].TCBpTSD       ; (esi) = new thread's TSD
        mov     esp,[esi].TSDKernelESP  ; restore new thread's ring 0 esp
        lea     esi,[esi].TSDdescThreadCopy ; (esi) = ptr to source descriptors
        mov     edi,[_pGDTThreadCopy]   ; (edi) = pointer to GDT destination
        mov     ecx,[_clDescThreadCopy] ; (ecx) = # of dwords to copy
        rep     movsd

psc75:  add     esi, [_clDescTIBOffset] ; (esi) = new thread's TIB
        mov     edi,[_pGDTTIBCopy]      ; (edi) = pointer to GDT destination
        movsd
        movsd

        mov     esi,GSEL GDT_PTDA
        mov     ss,esi                  ; load ss with GSEL GDT_PTDA

;;      Setup new ptes underlying the kernel SS
;;
;;      (EBX) = new ptcb

;;      WARNING: Do not debug this section.

        mov     esi,[ebx].TCBpPTDA      ; (ebx) = new ptda

        mov     edi,[_ppteKStackBase]
        add     edi,CPAGETCBSKIP*4      ;; Skip to the TCB PTEs
        lea     esi,[ebx].TCBpteKStackTCB0
        movsd                           ;; set up TCB pte 0 (always valid)
        movsd                           ;; set up TCB pte 1
        sub     edi,(CPAGETCBSKIP + 2 - (CPAGETSDSKIP + TSDSTART/PAGESIZE))*4
        movsd                           ;; set up Kernel Stack TSD pte
        add     edi,(16 - 3 - 1 - (CPAGETSDSKIP + TSDSTART/PAGESIZE))*4
        movsd                           ;; set up PTDA pte 0
        movsd                           ;; set up PTDA pte 1
        movsd                           ;; set up PTDA pte 2 (always valid)

.errnz  (TCBpteKStackTCB1  - TCBpteKStackTCB0 - 4)      ;; Dependent on data
.errnz  (TCBpteKStackTSD - TCBpteKStackTCB1 - 4)        ;; ordering here.
.errnz  (TCBpteKStackPTDA0 - TCBpteKStackTSD - 4)
.errnz  (TCBpteKStackPTDA1 - TCBpteKStackPTDA0 - 4)
.errnz  (TCBpteKStackPTDA2 - TCBpteKStackPTDA1 - 4)

;;      Flush the TLB after editing the page directory and tables.

        mov     eax,[_pgPhysPageDir]
        mov     cr3,eax

;       END OF WARNINGS: New stack is valid again.

ifdef PMDEBUG
    ifb <init>
        Entry pgSwitchRet
    endif
endif

        popfd                           ;; restore interrupt flag
        .errnz SCNLOCALS - 1            ;; should equal number of dwords popped

;;      Edit the TSS to point to the ascending task's ring 0 and 2 stacks.
;;
;;      (EBX) = new ptcb

psc80:  mov     edx,offset FLAT:TSS     ;; (EDX) = pTSS
        movzx   eax,[ebx].TCBCpl2_SS    ;; (ax) = ring 2 ss
        mov     [edx].tss_ss2,ax
        mov     eax,[ebx].TCBCpl2_ESP   ;; (eax) = ring 2 esp
        mov     [edx].tss_esp2,eax

        mov     eax,[ebx].TCBCurrTSD    ;; (eax) = segment relative TSD addr
        add     eax,TSDPMStack          ;; (eax) = segment relative ring 0 esp
        mov     [edx].tss_esp0,eax

; enable NMI here, don't bother with reading port 71                    122474

        push ax                         ;                               122474
        mov al,8dh                      ; prepare for MCA               122474
        cmp ABIOS_present,1             ; Is ABIOS present              122474
        jz  short psc81                 ; jump if it is (MCA machine)   122474
        mov al,0dh                      ; ISA machine                   122474
psc81:                                  ;                               122474
        out 70h,al                      ;                               122474
        pop ax                          ;                               122474

        RETC

ifb <init>

ifdef INCDEBUG
aplwplbl label  dword
        dd      offset FLAT:psc_1lwp    ;; 1 local watchpoint
        dd      offset FLAT:psc_2lwp    ;; 2 local watchpoints
        dd      offset FLAT:psc_3lwp    ;; 3 local watchpoints
        dd      offset FLAT:psc_4lwp    ;; 4 local watchpoints

;;      Update debug registers
;;      EAX, ECX, EDI are all usable
;;
;;      Initially, (ECX) = new pptda->pdcb
;;                 (EAX) = old pptda->pdcb XOR ECX
;;                 (EDX) = old pptda
;;                 (ESI) = new pptda
;;
;;      If we come here, we know at least one of the process' is a debuggee

psc90:  xor     eax,ecx                 ;; (EAX) = old pptda->pdcb
                                        ;; (ECX) = new pptda->pdcb
        jz      short psc100            ;; Is old pptda a debuggee?
                                        ;;  No, don't disable local DRs

        cmp     [edx].ptda_ProcType,PT_VDM ;; VDM?
        jne     short psc91             ;;  no, skip ahead
        SaveReg <eax,ecx,edx>
        CCALL32 _DBGDRDisableVDM,<eax>  ;; Disable VDM watchpoints
        RestoreReg <edx,ecx,eax>
        jmp     short psc100            ;; See about new process

psc91:  cmp     [eax].dcb_clwpcb,0      ;; Does old process have locals?
        jz      short psc100            ;;  No, don't disable local DRs

        cli
        mov     edi,dr7                 ;; Disable old process' local DRs
        and     edi,[_DBGlmskDR7system] ;;  by masking them out
        mov     dr7,edi
        sti

psc100: mov     [_DBGpdcbCur],ecx       ;; Update current Debug Control Block
        or      ecx,ecx                 ;; Is new process a debuggee?
        jz      psc67                   ;;  No, don't enable new locals
        cmp     [esi].ptda_ProcType,PT_VDM ;; VDM?
        jne     short psc101            ;;  no, skip ahead
        SaveReg <eax,ecx>
        CCALL32 _DBGDREnableVDM,<ecx>   ;; Enable VDM watchpoints
        RestoreReg <ecx,eax>
        mov     [_DBGpdcbCur],0         ;; No current DosDebug client
        jmp     psc67                   ;; See about new process

psc101: mov     eax,[ecx].dcb_clwpcb    ;; Does new process have locals?
        dec     eax                     ;;  (EAX) = number of locals - 1
        js      psc67                   ;;  No, don't enable local DRs

;;      Local debug registers are always allocated from DR0 to DR3.
        jmp     aplwplbl[eax * type aplwplbl]

psc_4lwp:
        mov     eax,[ecx].dcb_alDRladdr3
        mov     dr3,eax                 ;; Set laddr for local watchpoint 4
psc_3lwp:
        mov     eax,[ecx].dcb_alDRladdr2
        mov     dr2,eax                 ;; Set laddr for local watchpoint 3
psc_2lwp:
        mov     eax,[ecx].dcb_alDRladdr1
        mov     dr1,eax                 ;; Set laddr for local watchpoint 2
psc_1lwp:
        mov     eax,[ecx].dcb_alDRladdr0
        mov     dr0,eax                 ;; Set laddr for local watchpoint 1

        cli
        mov     edi,dr7
        or      edi,[ecx].dcb_lDR7local ;; Enable new locals
        mov     dr7,edi
        sti

endif ;DEBUG

        jmp     psc67
endif

        endm


;***EP  _PGSwitchContext - switch ptda, LDT, tcb and page table contexts.
;
;       _PGSwitchContext switches the current kernel stack, ptda, LDT, tcb,
;       and page table context.  It also updates the local infoseg, and
;       checks if the descending task has illegally written to either of
;       the infosegs.
;
;       ENTRY   psc_ptcb - pointer to ascending tcb
;       EXIT    NONE
;       USES    EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP, ES, FS, GS, SS, Flags
;
;       WARNING The above registers *are* used; the ascending task returns
;               with some of the descending task's registers.  This
;               procedure necessarily violates the C register convention.

Procedure _PGSwitchContext,,,,esp
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar psc_ptcb,dword

        pgSwitchContext psc_ptcb        ; invoke macro

EndProc _PGSwitchContext


ifdef NEEDED

;***EP  _PGTouch - quickly force present 4k+1 or less bytes of data
;
;       This routine is useful for making sure that a block of code or
;       data less that 4k+1 bytes long is present until you next block.
;
;       It works by touching the starting and ending addresses until it
;       can do so without faulting.
;
;       ENTRY   pgt_laddr1 - starting linear addresses to force present
;               pgt_laddr2 - ending linear address to force present
;       EXIT    NONE
;
;       USES    eax, ecx, edx
Procedure _PGTouch,,,,esp
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar pgt_laddr1,dword
    ArgVar pgt_laddr2,dword

        mov     eax,[pgt_laddr1]                ; (eax) = laddr1
        mov     edx,[pgt_laddr2]                ; (edx) = laddr2

ifdef PGSTRICT
        sub     edx,eax                         ; (edx) = size of range
        cmp     edx,PAGESIZE+1                  ; too big?
        ja      short pgtie                     ; if so, panic
        mov     edx,[pgt_laddr2]                ; (edx) = laddr2
endif

pgt10:  mov     ecx,[_pgcPageFaults]            ; (ecx) = starting fault count

;       touch the pages.  It is ok to trash al since changing the low byte
;       of the addresses will never change which page we are touching.

        mov     al,[eax]
        mov     al,[edx]
        cmp     ecx,[_pgcPageFaults]            ; any faults during touch?
        jnz     short pgt10                     ; if so, loop and try again

        RETC                                    ; no faults, return

pgtie:  Panic "PGTouch: more than one page"
EndProc _PGTouch
endif

ifdef PMDEBUG
;***    Block - block a thread forever
;
;       ENTRY:  NONE
;       EXIT:   Doesn't
;
Procedure Block
        FlatContext     <ds,es>,ax

b10:    CCALL32 _TKSleep <0b10ch, -1, 0, 0>
        jmp     short b10
EndProc Block

;***    Wakeup - wakeup a blocked thread
;
;       ENTRY:  NONE
;       EXIT:   Doesn't
;
Procedure Wakeup
        push    ds
        push    es
        pushad
        pushfd
        FlatContext <ds,es>,ax

        ; Put the blockid in eax here!
        int     3

        CCALL32 _TKWakeup <eax, 0, 0>

        popfd
        popad
        pop     es
        pop     ds
        int     3
EndProc Wakeup
endif

;***EP  pgwTrap2FindPage - finds physical page that caused the trap2
;
;       pgwTrap2FindPage is the worker for PGTrap2FindPage.
;       Using a single pte, it marches down all of memory (dword at a time)
;       searching for the memory that caused the trap2.  Once the dword is
;       found, the bytes within that dword are tested, so that we can retrun
;       a byte ganular address, which will eventually be delayed to the user.
;       This routine will not block (and must not block).
;
;       ENTRY   ppte        - pointer to pte that maps laddr
;               laddr       - linear address
;       RETURN  ulong_t     - physical address of failing memory or
;                             -1 if none found
;
Procedure _pgwTrap2FindPage
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
ArgVar  ppte,DWORD
ArgVar  laddr,DWORD

LocalVar frame,DWORD
LocalVar nranges,DWORD
LocalVar ppr,DWORD
LocalVar lastframe,DWORD

        EnterProc

        SaveReg <ebx,esi,edi>
        mov     eax,_pgPageablePAI.pai_pprt
        mov     ppr,eax                         ; page range table
        mov     eax,_pgPageablePAI.pai_nranges;
        mov     nranges,eax                     ; number of ranges in table
        dec     eax
        mov     ebx,size pagerange_s            ;
        mul     ebx
        add     ppr,eax                         ; ptr to end of page range tbl
        mov     ebx,ppr

;NO Longer need this if INTNMI resets parity
;;;;;;  call    DHF.DosHlpResetParity           ; reset parity so we can do a memory scan

pgt2f1: mov     edx,dword ptr [ebx].pr_firstframe         ;
        mov     eax,dword ptr [ebx].pr_lastframe
        mov     lastframe,eax

pgt2f2: mov     edi,ppte
        mov     eax,edx
        shl     eax,PT_FRAMESHIFT
        or      eax,1                   ; set present bit
        mov     [edi],eax

;       Flush TLB

        mov     eax,[_pgPhysPageDir]
        mov     cr3,eax

        mov     esi,laddr
        mov     ecx,PAGESIZE

        call    DHF.DosHlpFindParity
        jnc     short pgt2f5

pgt2f4: shl     edx,PAGESHIFT
        mov     eax,laddr
        and     eax,PAGEMASK
        or      edx,eax                     ; edx =

        call    DHF.DosHlpResetParity       ; reset parity so we can do a memory scan
        jmp     short pgt2f6

pgt2f5: inc     edx
        cmp     edx,lastframe
        jb      short pgt2f2
        sub     ebx,size pagerange_s
        dec     nranges
        jnz     short pgt2f1
        mov     edx,-1
pgt2f6: mov     eax,edx                 ; physical address of failure
        RestoreReg <edi,esi,ebx>
        LeaveProc
        RETC
EndProc _pgwTrap2FindPage


;***EP  pgGetErrorLogFrame - Returns the physical frame of error log
;
;       pgGetErrorLogFrame returns the physical frame of a previous
;       parity error if it was logged.
;
;       ENTRY   LogNum      - Log Number
;               pFrame      - pointer to store frame
;       RETURN  NONE
;
;
Procedure _pgGetErrorLogFrame
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
ArgVar  LogNum,DWORD
ArgVar  pFrame,DWORD

        EnterProc
        SaveReg <ebx,esi,edi>
        mov     eax,LogNum
        call    DHF.DosHlpReadErrorLog
        mov     esi,pFrame
        mov     [esi],edx
        RestoreReg <edi,esi,ebx>

        LeaveProc
        RETC
EndProc _pgGetErrorLogFrame

ifdef INCDEBUG
;***LP  _int3 - do an int3 to get into debugger
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    NONE

Procedure _int3,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
        int     3
        RETC
EndProc _int3
endif


HIGH32CODE ends


        FARCODE HIGH32
CINIT_TEXT segment


;***EP  _PGSwitchContextInit - switch ptda, LDT, tcb and page table contexts
;                              at initialization time.
;
;       _PGSwitchContextInit unconditionally switches the current kernel stack,
;       ptda, LDT, tcb, and page table context.  It does not copy the local
;       infoseg, nor check if the infosegs have been illegally written to in
;       the descending task.
;
;       ENTRY   psc_ptcb - pointer to ascending tcb
;       EXIT    NONE
;       USES    EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP, ES, FS, GS, SS, Flags
;
;       WARNING The above registers *are* used; the ascending task returns
;               with some of the descending task's registers.  This
;               procedure necessarily violates the C register convention.

Procedure _PGSwitchContextInit,,,,esp
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar psc_ptcb,dword

        CCALL32 _PGKStackMap  <[psc_ptcb]> ; setup TSD descriptor images
        pgSwitchContext psc_ptcb, init ; invoke macro and flag init time

EndProc _PGSwitchContextInit


;***EP  _PGSwitchInit - initialize for _PGSwitchContext
;
;       _PGSwitchInit sets up for _PGSwitchContext
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    EAX, EBX, ECX, EDX, ESI, EDI, ESP, SS, Flags
;       EFFECTS global variables: _pPTDACur, _pTCBCur, _pPTDAFirst
;               TSS variables:    ring 0 esp, ring 2 ss:esp
;               ptda variables:   pPTDASelf
;               tcb variables:    TCBpPTDA
;       WARNING switches stacks from sysinit to GDT_PTDA


Procedure _PGSwitchInit
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

;       ppteKStackBase = pgPageToPte((ulong_t) pKStackBase >> PAGESHIFT, 0)

        mov     eax,[_pKStackBase]
        shr     eax,PAGESHIFT
        CCALL32 _pgPageToPte <eax, 0>
        mov     [_ppteKStackBase],eax

;       Get the linear address of TASKAREA into edx:

        mov     esi,[_SELGDTLaddr]      ; (edi) = (esi) = pointer to GDT
        mov     edi,esi
        add     edi,GSEL GDT_PTDA       ; (edi) -> GDT_PTDA descriptor
        mov     cx,TASKAREA
        movzx   ecx,cx
        add     esi,ecx                 ; (esi) -> TASKAREA descriptor

        lodsd                           ; copy TASKAREA to GDT_PTDA
        mov     edx,eax                 ; (edx) = low word
        stosd
        lodsd                           ; (eax) = high word
        or      eax,D_UVIRT shl 16
        .errnz  (d_attr - (4 + 2))
        stosd
        .errnz  (size desctab - 2*4)

        rol     eax,8                   ; (eax) = base of GDT_PTDA
        .errnz  (d_hiaddr - (4 + 0))
        .errnz  (d_extaddr - (4 + 3))
        mov     dl,ah
        mov     dh,al
        ror     edx,16
        .errnz  (d_loaddr - 2)

;       Change to Kernel stack
;
;       (EDX) = flat address of TASKAREA

        lea     edi,[edx+TKOTSD+TSDUserSS] ; (edi) = top of kernel stack

;       Stack configuration (10 extra bytes on stack):
;           dd  far 16 bit return (to sysinit)
;           dw  near 16 bit return (to caller's caller)
;           dd  near 32 bit return (to calling thunk)
;
;       (EDX) = pointer to ptda
;       (EDI) = pointer to top word of ptda stack
;       (SS:ESP) = sysinit's stack

        pop     ecx                     ; (ecx) = near 32 bit return address
        pop     bx                      ; (bx) = near 16 bit return address
        pop     esi                     ; (esi) = far 16 bit return address

        std                             ; stack operations; high to low
        mov     eax,ss                  ; store ss
        stosd
        mov     eax,esp
        stosd                           ; store esp
        mov     eax,ss                  ; use any valid selector instead of cs
        stosd                           ; store "cs"
        movzx   eax,si                  ; ignore paragraph address in cs
        stosd                           ; store eip
        add     edi,2                   ; std in effect: mixing stosw & stosd
        mov     eax,ebx                 ; put near 16 bit return addr on stack
        stosw                           ; store 16 bit near return address
        sub     edi,2                   ; std in effect: mixing stosw & stosd
        mov     eax,ecx
        stosd                           ; store 32 bit near return address
        cld
        add     edi,4                   ; point new "esp" to last stosd word
        sub     edi,edx                 ; make new "esp" segment relative
        mov     eax,GSEL GDT_PTDA       ; switch to kernel stack
        mov     ss,eax
    assume ss:TASKAREA
        mov     esp,edi

;       (EDX) = flat address of TASKAREA

        lea     ebx,[edx+TKOTSD]        ; (ebx) = pointer to SysInit TSD
        lea     ecx,[edx+TKOTCB1]       ; (ecx) = pointer to sysinit TCB
        add     edx,TKOPTDA             ; (edx) = pointer to sysinit PTDA

;       (EBX) = ptsd
;       (ECX) = ptcb
;       (EDX) = pptda

        mov     [ecx].TCBpTSD,ebx       ; Cross-link TCB and TSD
        mov     [ebx].TSDpTCB,ecx
        mov     [ecx].TCBpPTDA,edx      ; ptcb->TCBpPTDA = pptda
        mov     [_pTSDCur],ebx          ; set current TSD
        mov     [_pTCBCur],ecx          ; set current tcb
        mov     [_pPTDACur],edx         ; set current ptda
        mov     [_pPTDAFirst],edx
        mov     [edx].pPTDASelf,edx     ; set pptda->pPTDASelf
        mov     ax, TASKAREA
        mov     [edx].ThisPTDA,ax
        mov     al,[edx].ptda_NewFiles          ;54400
        mov     [ecx].TCBNewFlags,al            ;54400

;;      Setup the context switching TCB variabes

        CCALL32 _PGSwitchContextInit <[_pTCBCur]>  ;_PGSwitchContextInit(ptcb)
        RETC
EndProc _PGSwitchInit



;***EP  _pgInitSetDosTable - set machine physical memory size variable in DosTable
;
;       _pgInitDosTable sets variable in DosTable for IFSs and DDs
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES
;       EFFECTS DosTab2 - total physical size variable


Procedure _pgInitDosTable
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
        EnterProc

        mov    eax,[_pgPhysPages]
        shl    eax,PAGESHIFT
        SaveReg <ds,ebx>
        DosContext DS
        lea    ebx,pgMstable
        mov    [ebx],eax
        RestoreReg <ebx,ds>

        LeaveProc
        RETC
EndProc _pgInitDosTable

CINIT_TEXT ends

PROFSWAP_TEXT   segment

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; hybernation code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

include int.inc

RTCCTL          equ     070h
RTCDTA          equ     071h
TIMER_0         equ     040h
TIMER_1         equ     041h
TIMER_2         equ     042h
TIMERCB         equ     043h                    ; timer control byte

DELAY   macro
        local   dly

        push    ecx
        ; arbitrary value - just to let registers settle a bit
        mov     ecx, 0ffffh
dly:
        loop    short dly
        pop     ecx
        endm

;***LP  _hyberGrabState - save necessary state for hynernation return
;
;       ENTRY   [esp+4] = base of hyberInfoHdr
;       EXIT    NONE
;       USES    NONE

Procedure _hyberGrabState,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
        mov     eax, [esp+4]                    ; base of hyberInfoHdr
        pushad

        mov     ebx, eax
        add     ebx, offset hih_gdt             ; gdt
        sgdt    fword ptr [ebx]
        mov     bx, cs
        mov     [eax + offset hih_cs], bx       ; cs
        mov     bx, ds
        mov     [eax + offset hih_ds], bx       ; ds
        mov     bx, ss
        mov     [eax + offset hih_ss], bx       ; ss
        mov     bx, fs
        mov     [eax + offset hih_fs], bx       ; fs
        mov     bx, gs
        mov     [eax + offset hih_gs], bx       ; gs
        mov     ebx, eax
        add     ebx, offset hih_idt             ; idt
        sidt    fword ptr [ebx]
        mov     [eax + offset hih_esp], esp     ; esp
        mov     [eax + offset hih_ebp], ebp     ; ebp
        sldt    [eax + offset hih_ldt]          ; ldt
        str     [eax + offset hih_tr]           ; tr
        mov     ebx, CR0
        mov     [eax + offset hih_cr0], ebx     ; cr0
        mov     ebx, CR3
        mov     [eax + offset hih_cr3], ebx     ; cr3
        ; The loader uses the first page dir entry to get us back paging.
        ; The consequence is that we need to restore it; therefore we save it
        add     ebx, HYBERPHYSMEM               ; make it physical
        mov     ebx, [ebx]                      ; page dir 0
        mov     [eax + offset hih_pt0], ebx
ifdef HYBERI13
        ; The loader also uses 2 gdt entries.  Save them.
        mov     esi,[eax].hih_gdt2
        add     esi,TEMPGDT
        lea     edi,[eax].hih_gdtsave
        mov     ecx,4
    rep movsd
        ; Since we save in real mode, _TKSSBase is incorrect.
        ; Save a copy for later restore.  Save its address also
        mov     ebx, _TKSSBase
        mov     [eax + offset hih_tkssb], ebx
        mov     ebx, offset FLAT:_TKSSBase
        mov     [eax + offset hih_ptkssb], ebx
endif ;HYBERI13

        ; if we are on a Pentium like machine grab cr4
        test    _Pentium_Feature_Flags, VME_supported
        jz      short noCR4
;       mov     ebx, cr4
        db      00fh
        db      020h
        db      0e3h
        mov     [eax + offset hih_cr4], ebx     ; cr4
        or      [eax + offset hih_hyberflags], HF_LOADCR4
noCR4:

        pushfd
        pop     ebx
        mov     [eax + offset hih_flags], ebx   ; flags

        ; get the counter status from the 8254
        mov     ebx, eax
        mov     al, 0e2h                        ; read timer 0 status
        out     TIMERCB, al
        DELAY
        in      al, TIMER_0
        mov     [ebx + offset hih_tmr0], al
        mov     al, 0e4h                        ; read timer 1 status
        out     TIMERCB, al
        DELAY
        in      al, TIMER_1
        mov     [ebx + offset hih_tmr1], al
        mov     al, 0e8h                        ; read timer 2 status
        out     TIMERCB, al
        DELAY
        in      al, TIMER_2
        mov     [ebx + offset hih_tmr2], al

        ; get the interrupt mask values from the 8259
        in      al, PIC1_PORT1
        mov     [ebx + offset hih_pic1mask], al
        in      al, PIC2_PORT1
        mov     [ebx + offset hih_pic2mask], al

        ; get status a & b from the rtc
        mov     al, 0ah                         ; status reg a
        out     RTCCTL, al
        DELAY
        in      al, RTCDTA
        mov     [ebx + offset hih_rtca], al
        mov     al, 0bh                         ; status reg b
        out     RTCCTL, al
        DELAY
        in      al, RTCDTA
        mov     [ebx + offset hih_rtcb], al

        ; get state from comm ports
        mov     ecx, ebx
        add     ecx, offset hih_com1
        mov     edx, COM1_PORT
        call    hyberCommState
        mov     ecx, ebx
        add     ecx, offset hih_com2
        mov     edx, COM2_PORT
        call    hyberCommState

        ; note APM state for when we restore
        test    APM_Flags, 1
        jz      short apm1
        or      [ebx + offset hih_hyberflags], HF_APM16
apm1:
        test    APM_Flags, 2
        jz      short apm2
        or      [ebx + offset hih_hyberflags], HF_APM32
apm2:

        popad
        RETC
EndProc _hyberGrabState

;***    hyberCommState - utility routine to capture state of a comm port
;
;       ENTRY   edx = port address
;               ecx = pointer to hyberInfoHdr
;       EXIT    NONE
;       USES    EAX

Procedure hyberCommState,near
        push    ebx
        mov     ebx, edx                        ; save port base
        add     edx, COM_IEN                    ; interrupt enable
        in      al, dx
        shl     eax, 8
        mov     edx, ebx                        ; port base
        add     edx, COM_MCR                    ; modem control
        in      al, dx
        shl     eax, 8
        mov     edx, ebx                        ; port base
        add     edx, COM_LCR                    ; line control
        in      al, dx
        push    eax                             ; save lcr
        mov     al, 80h
        out     dx, al                          ; set DLAB
        mov     edx, ebx                        ; port base
        add     edx, COM_DLL                    ; divisor latch, low byte
        in      al, dx
        shl     eax, 8
        mov     edx, ebx                        ; port base
        add     edx, COM_DLM                    ; divisor latch, low byte
        in      al, dx
        mov     [ecx], eax                      ; save in infohdr
        pop     eax                             ; get lcr
        mov     edx, ebx                        ; port base
        add     edx, COM_LCR                    ; line control
        out     dx, al                          ; restore it
        mov     [ecx+4], al
        pop     ebx                             ; restore ebx
        ret
EndProc hyberCommState

ifdef INCDEBUG
;***LP  _hyberChksumPage - do a check sum on the indicated page
;
;       ENTRY   [esp+4] = frame #
;       EXIT    EAX
;       USES    EAX

Procedure _hyberChksumPage,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        push    ebx
        mov     ebx, [esp+8]                    ; frame #
        shl     ebx, PAGESHIFT
        add     ebx, HYBERPHYSMEM
        push    ecx
        mov     ecx, 1024
        xor     eax, eax
chksum1:
        xor     eax, [ebx]
        add     ebx, 4
        loop    short chksum1
        pop     ecx
        pop     ebx
        ret     4
EndProc _hyberChksumPage
endif

;***LP  _hyberPgCopy - copy a page using physical addressing
;
;       ENTRY   [esp+4] = dest frame #
;       ENTRY   [esp+8] = src  frame #
;       EXIT    NONE
;       USES    NONE

Procedure _hyberPgCopy,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      src,DWORD
    ArgVar      dest,DWORD

        EnterProc
        SaveReg <esi, edi, ecx>
        mov     esi, src
        shl     esi, PAGESHIFT
        add     esi, HYBERPHYSMEM
        mov     edi, dest
        shl     edi, PAGESHIFT
        add     edi, HYBERPHYSMEM
        mov     ecx, 1024
        cld
        rep     movsd
        RestoreReg <ecx, edi, esi>
        LeaveProc
        RETC
EndProc _hyberPgCopy

;***LP  _hyberDone - concludes the unhybernation process
;
;       ENTRY   [esp+4] = return esp
;       EXIT    EAX = NO_ERROR
;       USES    NONE

Procedure _hyberDone,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        xor     eax, eax
        mov     esp, [esp+4]
        pop     ebp
        ret
EndProc _hyberDone

Procedure _hyberSpcl,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      param,DWORD

        EnterProc
        mov     eax, param
        call    eax

        LeaveProc
        RETC
EndProc _hyberSpcl

;***EP  _getSwapperPath
;
;       This function returns a pointer to the swapfile's path
;
;       ENTRY:
;               NONE
;
;       EXIT:
;               EAX - FLAT pointer
;

Procedure _getSwapperPath,near
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing

        EnterProc

        push    es
        mov     eax, _smSFNarr
        mov     eax, [eax]
        mov     eax, [eax].PgIOSave_SFT                 ; 16:16 pointer to sft
        ror     eax, 16
        mov     es, ax
        shr     eax, 16
        mov     eax, es:[eax].sf_MFT                    ; flat pointer to mft
        add     eax, offset mft_name
        pop     es

        LeaveProc
        RETC

EndProc _getSwapperPath

;***EP  _getSFTExtent
;
;       This function returns a pointer to the base of the SFT
;
;       ENTRY:
;               NONE
;
;       EXIT:
;               EAX - FLAT pointer
;

Procedure _getSFTExtent,near
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing
    ArgVar      addr, DWORD
    ArgVar      sft_len, DWORD

        EnterProc

        push    edx
        push    es
;       xor     eax, eax
        mov     eax, GSEL GDT_SFT
        mov     es, eax
        movzx   eax, word ptr es:0                                ; SFN selector
        CCALL32 _SELConvertToLaddr, eax
        add     eax, size SFT
        mov     edx, addr
        mov     [edx], eax
;       xor     eax, eax
        movzx   eax, word ptr es:CALLSFN
        mov     edx, sft_len
        mov     [edx], eax
        pop     es
        pop     edx

        LeaveProc
        RETC

EndProc _getSFTExtent

;***EP  _BootDos32 - Boot Dos
;
;       This function calls the BootDos16 Function
;
;       ENTRY:
;               NONE
;
;       EXIT:
;               Does not return
;

Procedure _BootDos32
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing

        ; if pentium like - blast cr4
        test    _Pentium_Feature_Flags, VME_supported
        jz      short notPent
        xor     ebx, ebx
;       mov     cr4, ebx
        db      00fh
        db      022h
        db      0e3h
notPent:

        CALL16  BootDos16

EndProc _BootDos32

Procedure _hyberReboot
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing

        CALL16  hyberReboot16

EndProc _hyberReboot

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This next section contains the hybernation I/O routines.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ifndef HYBERI13


include hyberhd.inc

;***LP  _inb - do inb instruction
;
;       ENTRY   [esp+4] = i/o address
;       EXIT    EAX
;       USES    EAX

Procedure _inb,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        push    edx
        mov     edx, [esp+4]
        in      al, dx
        pop     edx
        ret
EndProc _inb

;***LP  _outb - do outb instruction
;
;       ENTRY   [esp+4] = i/o address
;       ENTRY   [esp+8] = data byte
;       EXIT    NONE
;       USES    NONE

Procedure _outb,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      data,DWORD
    ArgVar      addr,DWORD

        EnterProc
        SaveReg <eax, edx>
        mov     edx, addr
        mov     eax, data
        out     dx, al
        RestoreReg <edx, eax>
        LeaveProc
        RETC
EndProc _outb

;***LP  _hyberDelay - delay 500 ns
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    NONE

Procedure _hyberDelay,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        kIODelay32                              ; uses eax
        ret
EndProc _hyberDelay

;***LP  _hdWaitReady - check to see if drive is busy
; check to see if its in use - return when free with status reg in eax
; note: this uses alternate status register - no interrupt acknowledge
;
;       ENTRY   NONE
;       EXIT    EAX
;       USES    EAX

Procedure _hdWaitReady,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        push    edx
        xor     eax, eax
        mov     edx, HD_ASR                     ; alternate status reg
notready:
        in      al, dx
        test    al, HD_BSY + HD_DRQ
        jz      short itsready
        DELAY
        jmp     short notready
itsready:
        pop     edx
        ret
EndProc _hdWaitReady

;***LP  _hdWaitNotBusy - wait till drive is not busy
;
;       ENTRY   NONE
;       EXIT    EAX
;       USES    EAX

Procedure _hdWaitNotBusy,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        push    edx
        xor     eax, eax
        mov     dx, HD_C0 + HD_SR
stillbsy:
        in      al, dx
        test    al, HD_BSY
        jnz     short stillbsy
        pop     edx
        ret
EndProc _hdWaitNotBusy

;***LP  _hdWaitForIRQ14 - wait till the drive asserts IRQ14
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    EAX
;       ASSUMES steering register on PIC2 was set for read of IRR

Procedure _hdWaitForIRQ14,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

        push    edx
        mov     edx, PIC2_PORT0
noIRQ:
        in      al, dx
        test    al, 40h                         ; int 14
        jz      short noIRQ
        pop     edx
        ret
EndProc _hdWaitForIRQ14

;***LP  _hdReadBlock - read from disk
;
;       ENTRY   [esp+4]  = address *** physical ***
;       ENTRY   [esp+8]  = length
;       ENTRY   [esp+12] = maxblksize
;       EXIT    NONE
;       USES    NONE

Procedure _hdReadBlock,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      addr, DWORD
    ArgVar      bytecount, DWORD
    ArgVar      maxblksiz, DWORD

        EnterProc

        push    ecx
        push    ebx
        push    edi
        mov     ecx, bytecount                  ; length
        mov     edi, addr                       ; addr
        xor     ebx, ebx                        ; count
        mov     edx, HD_C0 + HD_DR
again:
        in      ax, dx
        mov     [edi + ebx], ax
        add     ebx, 2
        test    ebx, 1ffh                       ; end of sector?
        jnz     short again                     ; no - no need to check for end
        dec     _hyberOpCount
        jnz     short skip
        mov     eax, maxblksiz
        mov     _hyberOpCount, eax
        call    _hdWaitNotBusy                  ; clear the interrupt
skip:
        cmp     ecx, ebx                        ; done?
        jne     short again

        call    _hdWaitNotBusy
        pop     edi
        pop     ebx
        pop     ecx
        LeaveProc
        RETC
EndProc _hdReadBlock

;***LP  _hdWriteBlock - write to disk
;
;       ENTRY   [esp+4]  = address *** physical ***
;       ENTRY   [esp+8]  = length
;       ENTRY   [esp+12] = maxblksize
;       EXIT    NONE
;       USES    NONE

Procedure _hdWriteBlock,near
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      addr, DWORD
    ArgVar      bytecount, DWORD
    ArgVar      maxblksiz, DWORD

        EnterProc

        push    edx
        push    ecx
        push    ebx
        push    esi
        mov     ecx, bytecount                  ; length
        mov     esi, addr                       ; addr
        cld
        add     ecx, esi                        ; end point
        mov     ebx, _hyberOpCount              ; op count
        mov     edx, HD_C0 + HD_DR
ifndef conservative
chkfordata:
        ; verify the drive has data ready for us - this should not be necessary
        ; but drives have been seen that are not ready with data at this point
        call    _hdWaitNotBusy                  ; get status reg
        test    al, HD_DRQ
        jnz     short more                      ; its ready for more
        test    al, HD_ERR                      ; verify not because of error
        jnz     writerr
        ; the following code is to accomodate a problem with the posting of
        ; status on some type drives (e.g. WD's)
        ; we really should not have to do this
        push    ebx
        mov     ebx, 500                        ; limit our attempts
tryagain:
        call    _hdWaitNotBusy                  ; get the status reg
        test    al, HD_DRQ
        je      short chkforerr
        pop     ebx
        jmp     short more                      ; finally ready
chkforerr:
        test    al, HD_ERR
        jnz     short errout
        dec     ebx
        jne     short tryagain
        or      al, HD_ERR + HD_BSY             ; generate phoney err
errout:
        pop     ebx
        jmp     short writerr
; end of funny code

        align   4
else
chkfordata:
endif
more:
        lodsd
        out     dx, ax
        shr     eax, 16
        out     dx, ax
        lodsd
        out     dx, ax
        shr     eax, 16
        test    si, 1ffh                        ; end of sector?
        out     dx, ax
        jnz     short more                      ; no - no need to check for end
        dec     ebx
        jnz     short chkdone
        mov     ebx, maxblksiz
        call    _hdWaitForIRQ14
        call    _hdWaitNotBusy                  ; clear the interrupt
        test    al, HD_ERR
        jnz     short writerr
chkdone:
        cmp     ecx, esi                        ; done?
        jne     short chkfordata
        mov     _hyberOpCount, ebx
        xor     eax, eax

writerr:
        pop     esi
        pop     ebx
        pop     ecx
        pop     edx
        LeaveProc
        RETC
EndProc _hdWriteBlock

endif   ;HYBERI13

; mark the end of the hd stuff so it can be made present
        public  _hdEnd
_hdEnd: dd      ?

PROFSWAP_TEXT   ends

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; and some code to get us to dos
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        FARCODE LOW
CODE segment

;;151221 BEGIN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Function:    DosStartCode
;;
;; Notes:       This function is loaded at physical address 0x540 by
;;              BootDos16 code.
;;
;;              Never grow this code section to 0x700 because ibmbio.com
;;              is loaded there.
;;
;;
;;
;; Pseudo:      save <eax, ebx, ecx, edx, edi> for ibmbio.com
;;              locate the extended bios data area segment (XBDA)
;;              loop thru segment 9000h down to 1000h
;;                if XBDA then
;;                  clear up to XBDA
;;                else
;;                  clear 64K memory
;;              restore <eax, ebx, ecx, edx, edi> for ibmbio.com
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
dos_start_code:
Procedure DosStartCode,<far16>
    cli
    push    eax
    push    ebx
    push    ecx
    push    edx
    push    edi

        xor     dx, dx
        mov     ah, 0C1h
        int     15h
        jc      short DSC_01
        mov     dx, es          ; dx == XBDA segment

DSC_01:
        xor     eax, eax        ;
        xor     ecx, ecx        ;

        mov     cx, 9           ; 9 segments
        mov     bx, dx          ; retrieve XBDA segment
        shr     bx, 12          ; get the first HEX digit of the XDBA segment
next_64K:
        push    cx              ; clear the next 64K
        cmp     cx, bx          ; is it XBDA segment?
        je      short EBA_seg
        shl     cx, 12          ;
        mov     es, cx          ; es = 1000h, 2000h,..., 9000h
        xor     edi, edi
        mov     ecx, 4000h      ; dword operation: 0x10000/4
        cld
        rep     stosd
        jmp     short not_EBA
EBA_seg:
        ; zero fill up to the beginning of EBA
        shl     cx, 12
        mov     es, cx          ; segment = x000h
        and     dx, 0FFFh       ; dx = number of bytes
        mov     cx, dx
        shr     cx, 2           ; dword operation
        xor     edi, edi        ; offset = 0
        cld
        rep     stosd

not_EBA:
        pop    cx
        loop   short next_64K   ; decrement and loop back

        ;;Work with the first 64K segment.
        ;;starting from 0x0D00 ---> 0x7C00
        mov     edi, 0D00h
        mov     ecx, (7000h-0D00h)/4
        xor     eax, eax
        mov     es, ax
        cld
        rep     stosd

        ;;from 0x7E00--->0xAB00
        mov    edi, 7E00h
        mov    ecx, (0AB00h-7E00h)/4
        rep    stosd

        ;;from 0xAB08--->0x10000
        mov    edi, 0AB08h
        mov    ecx, (10000h-0AB08h)/4
        rep    stosd

done_clearing:
    mov     ds, ax                      ; ds = es = 0
    mov     es, ax
    mov     fs, ax
    mov     gs, ax

    mov     eax, 0010h
    mov     cr0, eax
    xor     eax, eax
    mov     cr3, eax

    pop     edi
    pop     edx
    pop     ecx
    pop     ebx
    pop     eax

    sti
    db      0EAh
ibmbio      dd   ?
anything    dd   0

dos_gdtr    label fword
dos_gdtr_limit  dw 0FFFFh
dos_gdtr_linear dd 0

dos_idtr    label fword
dos_idtr_limit  dw 0FFFFh
dos_idtr_linear dd 0

EndProc DosStartCode
dos_start_code_end:


;***LP  BootDos16 - Boot Dos
;
;       This function sets up the registers as required by
;       IBMBIO.COM and branches to it.
;
;       ENTRY:
;               Fake Dir at 0x50:0 (provides first cluster info for bio & dos)
;               IBMBIO.COM is loaded at 0x70:0 (first 3 sectors)
;               BPB of boot drive is loaded at 0:0x7c00
;
;       EXIT:
;               Does not return
;


Procedure BootDos16,<far16>
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing

    cli
    ; restore the hardware to a more dos like state
    mov     eax, cr3                        ; make page 0 writeable
    add     eax, HYBERPHYSMEM
    mov     eax, [eax]                      ; get page dir entry 0
    and     eax, 0fffff000h                 ; get phys loc of page tbl ent 0
    add     eax, HYBERPHYSMEM               ; make it physical
    mov     dword ptr [eax], 3              ; add entry - present, writable
    DosContext ds

    ;;------------------------------------------------------------------------
    ;; Note:  DosHlpSetDosEnv will return saved BIOS segment (real mode) in ax
    ;;        We will need this to restore IVT, BIOS Data Area and XBDA
    ;;------------------------------------------------------------------------

    call    DHF.DosHlpSetDosEnv

    ; RealMode requires that the stack be in low memory
    push    GSEL GDT_MSSTACK
    pop     ss
    mov     sp, MSSTACKSIZE

    ; this is so the debugger can be used till realmode
    push    eax
    push    ecx
    push    edx
    push    ds
    FlatContext ds,ax,NOASSUME
    CALLF32 _TKSetSSBase,,nocheck   ; Must do after every stack switch
    pop     ds
    pop     edx
    pop     ecx
    pop     eax

    call     RealModeNoInt

    ; bugbug - should reset adapter roms first

    sub     sp,4
    mov     bp,sp

    ; ******************** 160195 BEGIN ****************************
    ; Restore IVT, BIOS data area and XBDA here
    push    ds

    mov     ds, ax                      ; ax = segment of saved BIOS
    xor     si, si
    xor     di, di
    mov     es, di                      ; es:[di] --> BIOS IVT
    mov     ecx, 0500h/2                ; Size of IVT and BIOS data
    cld
    rep     movsw

    mov     di, es:[40eh]               ; Get XBDA segment
    cmp     di, 0
    je      bd16_NoXBDA
    mov     es, di                      ; es=BIOS XBDA segment
    xor     di, di
    mov     cl, byte ptr ds:[si]        ; Number of 1K blocks
    xor     ch, ch                      ; Shift left 10 bits to get number of bytes
    shl     cx, 9                       ;    but need to shift right 1 bit for movsw
    rep     movsw

bd16_NoXBDA:
    pop     ds
    ; ****************** 160195 END **************************

    ; send disconnect to APM
    mov     ax, 5304h
    xor     bx, bx
    int     15h

    mov     ah, 88h                 ; get memory above 1 Meg            ;158471
    int     15h                                                         ;158471
    jc      short no_XMemory        ; if NC, AX = # of 1K blocks        ;158471
                                                                        ;158471
    movzx   ecx, ax                 ; ecx = # 1K blocks                 ;158471
                                                                        ;158471
    cli                                                                 ;158471
;-----------------------------------------------------------------------------
; munge es to flat memory
;-----------------------------------------------------------------------------
    mov     eax, cr0                                                    ;158471
    or      al, CR0_PE              ;                                   ;158471
    mov     cr0, eax                ; switch to protected mode          ;158471
    jmp     short $+2                                                   ;158471
    FlatContext es,bx,NOASSUME      ; load es with base=0, lim=4G       ;158471
    and     al, NOT CR0_PE                                              ;158471
    mov     cr0, eax                ; switch to real mode               ;158471
    shl     ecx, 08                 ; convert to dwords                 ;158471
    mov     edi, 100000h            ; clear memory starting at 1 Meg    ;158471
    xor     eax, eax                ;                                   ;158471
    cld                             ;                                   ;158471
rep stos    es:dword ptr [edi]      ;                                   ;158471

no_XMemory:
    ; now we head off to the dos world
    sub     ax,ax
    mov     ds,ax
    mov     bx,7c0bh

    mov     al,[bx].BPB_NumberOfFATs
    mul     [bx].BPB_SectorsPerFAT
    add     ax,word ptr [bx].BPB_HiddenSectors
    adc     dx,word ptr [bx].BPB_HiddenSectors+2
    add     ax,[bx].BPB_ReservedSectors
    adc     dx,0
    mov     word ptr [bp+2],ax
    mov     word ptr [bp],dx

    mov     ax,size dir_entry
    mul     [bx].BPB_RootEntries
    mov     cx,[bx].BPB_BytesPerSector
    add     ax,cx
    dec     ax
    div     cx
    add     word ptr [bp+2],ax
    adc     word ptr [bp],0

    mov     ch,[bx].BPB_MediaDescriptor
    mov     dl,80h                        ; bugbug - must be fixed
    mov     bx,word ptr [bp+2]
    mov     ax,word ptr [bp]
    push    cx

    push    cs
    pop     ds
;   mov     si, 0                   ; set up ibmbio jmp seg:off=70:00
    mov     word ptr ibmbio+0, 0h   ;
;   mov     si, 70h                 ;
    mov     word ptr ibmbio+2, 70h  ;
    lgdt    dos_gdtr
    lidt    dos_idtr

    push    0
    pop     es                      ; es = 0 segment

    ; load DosStartCode and then give control to it
    mov     cx, offset dos_start_code_end
    mov     si, offset dos_start_code
    sub     cx, si
    add     cx, 3
    and     cx, 0FFFCh              ; align dword
    mov     di, 0540h               ; es:di = 0:540h
    shr     cx, 2
    cld
    rep     movsd

    pop     cx
    push    0
    pop     ss                      ; zero stack segment
    mov     sp, 07c00h              ;
    sti
    push    50h                     ; cs = 50
    push    40h                     ; ip = 40
                                    ; Now we "far return to" DosStartCode
    ;mov      sp, 07c00h
    ;push     ds
    ;pop      es

    ;sti
    ;push    70h                                     ; cs
    ;push    0h                                      ; ip
    retf

EndProc BootDos16

;;151221 END

Procedure hyberReboot16,<far16>
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing

        cli
        ; restore the hardware to a more dos like state
        mov     eax, cr3                        ; make page 0 writeable
        add     eax, HYBERPHYSMEM
        mov     eax, [eax]                      ; get page dir entry 0
        and     eax, 0fffff000h                 ; get phys loc of page tbl ent 0
        add     eax, HYBERPHYSMEM               ; make it physical
        mov     dword ptr [eax], 3              ; add entry - present, writable
        DosContext ds
        call    DHF.DosHlpSetDosEnv

        ; RealMode requires that the stack be in low memory
        push    GSEL GDT_MSSTACK
        pop     ss
        mov     sp, MSSTACKSIZE

        push    eax
        push    ecx
        push    edx
        push    ds
        FlatContext ds,ax,NOASSUME
        CALLF32 _TKSetSSBase,,nocheck   ; Must do after every stack switch
        pop     ds
        pop     edx
        pop     ecx
        pop     eax

        call     RealModeNoInt

        ; bugbug - should reset adapter roms first

        sub     sp,4
        mov     bp,sp

        ; ******************** 160195 BEGIN ****************************
        ; Restore IVT, BIOS data area and XBDA here
        push    ds

        mov     ds, ax                      ; ax = segment of saved BIOS
        xor     si, si
        xor     di, di
        mov     es, di                      ; es:[di] --> BIOS IVT
        mov     ecx, 0500h/2                ; Size of IVT and BIOS data
        cld
        rep     movsw

        mov     di, es:[40eh]               ; Get XBDA segment
        cmp     di, 0
        je      r16_NoXBDA
        mov     es, di                      ; es=BIOS XBDA segment
        xor     di, di
        mov     cl, byte ptr ds:[si]        ; Number of 1K blocks
        xor     ch, ch                      ; Shift left 10 bits to get number of bytes
        shl     cx, 9                       ;    but need to shift right 1 bit for movsw
        rep     movsw

r16_NoXBDA:
        pop     ds
        ; ****************** 160195 END **************************

        ; send disconnect to APM
        mov     ax, 5304h
        xor     bx, bx
        int     15h

        ; reset disk (aptiva need)
        xor     ax, ax                          ; ah = 0
        mov     dl, 80h                         ; drive #
        int     13h

        ; and zero out the control registers (win95 req)
        xor     eax, eax
        mov     cr3, eax
        mov     cr2, eax
        mov     cr0, eax
        int     19h                             ; won't return

        DosContext ds
        call    DHF.DosHlpReboot
EndProc hyberReboot16

CODE    ends

ifdef HYBERI13
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; int 13 hybernate IO code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      FARCODE HIGH32
PROFSWAP_TEXT   segment
Procedure _hdDumpSys
    assume ds:nothing,es:nothing,fs:nothing,gs:nothing,ss:nothing
    ArgVar      drive, WORD      ; drive ID for most of the list
    ArgVar      drive2, WORD     ; drive id for last dlist entry
    ArgVar      diskl, DWORD
    ArgVar      diskln, DWORD
    ArgVar      pagel, DWORD
    ArgVar      pageln, DWORD
    ArgVar      iobuf, DWORD
    ArgVar      iobufsz, DWORD
        EnterProc
        pushf

        push iobufsz
        push iobuf
        push pageln
        push pagel
        push diskln
        push diskl
        push drive2
        push drive

        CALL16  hdDumpSys16

        popf
        LeaveProc
        RETC
EndProc _hdDumpSys

PROFSWAP_TEXT   ends

        FARCODE LOW
CODE segment

;##############################################################################
;# DBGMSG - real mode debug message macros and subroutines
;#
;# If DBGMSG = 1 then this will expand into code that uses the BIOS to
;# output messages to either the screen or the serial port.  The code and
;# macros depend only on the stack and code segments being valid.  All
;# registers that are touched are saved and restored.
;#
;# macros:
;#     dbgmsg   0,"text\n"              displays text; \n translates to crlf
;#     dbgmsg   n,value                 displays value as an n byte hex number
;#
;# public symbol dbgmsgflag enables message output:
;#     01 = output on serial
;#     02 = output on video
;# default is 01.
;#
;##############################################################################
if HYBERDEBUG_S GE 1
        public   dbgmsgflag
dbgmsgflag:
        db      01h
dbgmsgcomport:
        db      01h                    ; bios com port 01 = com2

dbgmsg macro  text
endm

dbgmsg  macro  len,value,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16
        local  msg, pastmsg
     IFNB   <len>
     IF len EQ 0
        jmp    short pastmsg
msg:    db     value
        db     0
pastmsg:
        push   offset msg
     ELSEIF len EQ 1
        push   value
     ELSEIF len EQ 2
        push   value
     ELSEIF len EQ 4
        push   value
     ELSE
        .ERR <Invalid len argument, must be 1, 2, or 4>
     ENDIF
        push   len
        call   dbgmsgsub
        dbgmsg  p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16
     ENDIF
endm

dbgmsgxbuf:
        db      "abcdefgh",0

dbgmsgsub:
        assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
        pushf
        pushad


        mov    bp,sp
        mov    cx,ss:[bp+36]           ; move length to ebx
        mov    ebx,ss:[bp+38]          ; move value to ebx

        jcxz   short dbgmsgout         ; 0 means text, go write it
        shl    cx,1                    ; loop twice for each byte
        mov    si, offset dbgmsgxbuf+7; point to end of cvt buf
dbgmsgsub_xloop:
        mov    al,bl                   ; put nibble in al[3:0]
        and    al,00Fh                 ; mask off extra 4 bits
        add    al,030h                 ; convert to ascii
        cmp    al,039h                 ; compare with "9"
        jle    short dbgmsgsub_isdec   ; "9" or less is ok
        add    al,007h                 ; fixup to "A" - "F"
dbgmsgsub_isdec:
        mov    cs:[si],al
        dec    si
        shr    ebx,4                   ; get next 4 bits in bl[3:0]
        loop   short dbgmsgsub_xloop   ; repeat for all nibbles
        mov    bx, si                  ; point to first char in buf
        inc    bx

dbgmsgout:
        mov    cx,1                    ; cx = rep count for write attr
        mov    si,bx
        cld

dbgmsg_loop:
        lods   byte ptr cs:[si]     ; pick up character
        cmp    al,0                 ; test for end of string
        je     short dbgmsgdone
        cmp    al,"\"               ; is it a backslash
        jne    short dbgmsg_outchar
        lods   byte ptr cs:[si]     ; pick up escape code
        cmp    al,0                 ; test for end of string
        je     short dbgmsgdone
        cmp    al,"n"               ; only newline is recognized
        jne    short dbgmsg_outchar
        mov    al,0Dh               ; replace with CR
dbgmsg_outchar:
        test   byte ptr dbgmsgflag,01h
        jz     short dbgmsgnoser
        mov    ah,01h               ; ah=01 = write character to port
        xor    dx,dx
        mov    dl,byte ptr dbgmsgcomport; dx=port; 0=com1, 1=com2
;       mov    dl,0                 ; dx=port; 0=com1, 1=com2
        int    14h

dbgmsgnoser:
        test   byte ptr dbgmsgflag,02h
        jz     short dbgmsgnovid
        mov    bx,00002h            ; 02 = black bg, green fg
        cmp    al,020h              ; test for ctl char
        jl     short dbgmsgwrite
        mov    ah,009h              ; ah=09 = write attr
        int    010h
dbgmsgwrite:
        mov    ah,00eh              ; ah=0e = write tty
        int    010h
dbgmsgnovid:
        cmp    al,0Dh
        jne    short dbgmsg_loop
        mov    al,0Ah               ; add LF to CR
        jmp    short dbgmsg_outchar

dbgmsgdone:
        mov    cx,ss:[bp+36]           ; move length to ebx
        cmp    cx,4
        jge    short dbgmsg_dwret
        popad
        popf
        ret    4                       ; pop word len + word parm
dbgmsg_dwret:
        popad
        popf
        ret    6                       ; pop word len + dword parm
else ;HYBERDEBUG_S GE 1

dbgmsg macro  text
endm

dbgmsgx macro  len,src
endm

endif ;HYBERDEBUG_R GE 1

;***LP  _hdDumpSys
;
;       ENTRY   [esp+4] = disklist address
;       ENTRY   [esp+8] = disklist count
;       ENTRY   [esp+12] = pagelist address
;       ENTRY   [esp+16] = pagelist count
;       EXIT    NONE
;       USES    NONE

Procedure hdDumpSys16,<far16>
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      drive, WORD
    ArgVar      drive2, WORD
    ArgVar      diskl, DWORD
    ArgVar      diskln, DWORD
    ArgVar      pagel, DWORD
    ArgVar      pageln, DWORD
    ArgVar      iobuf, DWORD
    ArgVar      iobufsz, DWORD
        EnterProc
;-----------------------------------------------------------------------------
; save registers
;-----------------------------------------------------------------------------
        push    ds
        push    es

;-----------------------------------------------------------------------------
; setup new stack for real mode, save old sp, ss on new stack
;-----------------------------------------------------------------------------
        mov      ax,ss              ; temp save ss:sp in ax:bx
        mov      bx,sp
        push     GSEL GDT_MSSTACK   ; this is in low memory
        pop      ss
        mov      sp, MSSTACKSIZE
        push     ax                 ; push old ss:sp on new stack
        push     bx
        mov      es,ax              ; set ds to old ss
        assume   es:nothing
        push     es
        FlatContext ds,ax,NOASSUME
        CALLF32   _TKSetSSBase,,nocheck   ; Must do after every stack switch
        pop      es

;-----------------------------------------------------------------------------
; Mask off certain IRQs
;-----------------------------------------------------------------------------
        cli
        DosContext ds
        call     DHF.DosHlpGetMask     ; get current IRQ masks
        push     eax                   ; save on stack
        or       ax,9F9Ah              ; disable  IRQs 15 - - 12   11 10 9 8
                                       ;   and          7 - -  4    3  - 1 -
        call     DHF.DosHlpSetMask     ; Set new IRQ mask

;-----------------------------------------------------------------------------
; copy parms from old stack to new stack
;-----------------------------------------------------------------------------
        mov      eax,es:iobufsz
        push     eax
        mov      eax,es:iobuf
        push     eax
        mov      eax,es:pageln
        push     eax
        mov      eax,es:pagel
        push     eax
        mov      eax,es:diskln
        push     eax
        mov      eax,es:diskl
        push     eax
        mov      ax,es:drive2
        push     ax
        mov      ax,es:drive
        push     ax

;-----------------------------------------------------------------------------
; Switch to real mode
;-----------------------------------------------------------------------------
        call     RealModeNoInt         ; switch to real
        dbgmsg   0,"hddumpsys16 - in real mode\n"

;-----------------------------------------------------------------------------
; call real mode routine
;-----------------------------------------------------------------------------
        call     hddsreal               ; returns rc in ax

;-----------------------------------------------------------------------------
; switch back to protect mode, restore old IRQ masks
;-----------------------------------------------------------------------------
        dbgmsg   0,"hddumpsys16 - leaving real mode\n"
        push     ax                    ; save rc across call to protmode
        call     ProtMode              ; switch back to protect
        pop      bx
        pop      eax                   ; pop saved IRQ masks
        DosContext ds
        call     DHF.DosHlpSetMask     ; restore original IRQ masks
        mov      ax,bx

;-----------------------------------------------------------------------------
; restore protect mode stack
;-----------------------------------------------------------------------------
        pop      bx                    ; pop original sp
        pop      ss                    ; restore original ss
        mov      sp,bx                 ; restore original sp
        push     ax
        FlatContext ds,ax,NOASSUME
        CALLF32  _TKSetSSBase,,nocheck ; Must do after every stack switch
        xor      eax,eax
        pop      ax

;-----------------------------------------------------------------------------
; restore registers and return
;-----------------------------------------------------------------------------
        pop     es
        pop     ds

        LeaveProc
        RETC                            ; return with ax = rc from hddsreal
EndProc hdDumpSys16


Procedure hddsreal,near
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
    ArgVar      drive,WORD
    ArgVar      drive2,WORD
    ArgVar      diskl, DWORD
    ArgVar      diskln, DWORD
    ArgVar      pagel, DWORD
    ArgVar      pageln, DWORD
    ArgVar      iobuf, DWORD
    ArgVar      iobufsz, DWORD
    LocalVar    ncyl,WORD              ; num of cylinders
    LocalVar    nsec,WORD              ; num sectors per track
    LocalVar    nhead,WORD             ; num of heads
    LocalVar    secpcyl,WORD           ; sectors per cylinder
    LocalVar    diskaa,DWORD           ; current disk area sector addr
    LocalVar    diskal,DWORD           ; current disk area sector len
    LocalVar    diskan,DWORD           ; current disk area number
    LocalVar    pageaa,DWORD           ; current page area physical addr
    LocalVar    pageal,DWORD           ; current page area page len
    LocalVar    pagean,DWORD           ; current page area number
        EnterProc

        shr      iobufsz,9             ; change iobufsz from bytes to sectors
;-----------------------------------------------------------------------------
; munge gs to flat memory
;-----------------------------------------------------------------------------
        mov      eax,cr0
        or       al,1
        mov      cr0,eax               ; switch to protect
        jmp      short $+2
        FlatContext gs,bx,NOASSUME     ; load gs with base=0, lim=4G
        and      al,0FEh
        mov      cr0,eax               ; switch to real
        dbgmsg   0,"gs munged to ",2,gs,0,"\n"

;-----------------------------------------------------------------------------
; dump lists for debug
;-----------------------------------------------------------------------------
if HYBERDEBUG_S GE 2
        mov      ebx,diskl
        mov      ecx,diskln
        dbgmsg   0,"diskl=",4,ebx,0," diskln=",4,ecx,0,"\n"
nextdlist:
        mov      edx,gs:[ebx]
        dbgmsg   0,"sect=",4,edx
        add      ebx,4
        mov      edx,gs:[ebx]
        dbgmsg   0," sct=",4,edx,0,"\n"
        add      ebx,4
        loop     nextdlist


        mov      ebx,pagel
        dbgmsg   0,"pagel=",4,ebx
        mov      ecx,pageln
        dbgmsg   0," pageln=",4,ecx,0,"\n"
nextplist:
        mov      dx,gs:[ebx]
        dbgmsg   0,"pg=",2,dx
        add      bx,2
        mov      dx,gs:[ebx]
        dbgmsg   0," pct=",2,dx,0,"\n"
        add      ebx,2
        loop     nextplist

        mov      ebx,iobuf
        dbgmsg   0,"iobuf=",4,ebx
        mov      ecx,iobufsz
        dbgmsg   0," iobufsz=",4,ecx,0,"\n"
endif ;HYBERDEBUG_S GE 2

;-----------------------------------------------------------------------------
; on certain disks, resetting the device will enable I/O to work
;-----------------------------------------------------------------------------
        mov      ah,00h                                                 ;159979
        mov      dl,byte ptr drive                                      ;159979
        int      13h                                                    ;159979

;-----------------------------------------------------------------------------
; get drive parms, store in local vars nhead, nsec, ncyl; calculate secpcyl
;-----------------------------------------------------------------------------
        mov      ah,08h                ; ah = get drive parameters
        mov      dl,byte ptr drive     ; dl = drive number
        int      13h                   ; returns: dh = max head num
        jc       errgdp1               ;          cl[5:0] = max sec num
        inc      dh                    ;          cl[7:6] || ch = max cyl
        shr      dx,8
        mov      nhead,dx
        mov      al,cl
        and      ax,003Fh
        mov      nsec,ax
        shr      cl,6
        ror      cx,8
        inc      cx
        mov      ncyl,cx
        mov      ax,nsec
        mul      nhead
        mov      secpcyl,ax

        dbgmsg   0,"drive=",1,drive,0," nhead=",2,nhead,0," nsec=",2,nsec
        dbgmsg   0," ncyl=",2,ncyl,0," secpcyl=",2,secpcyl,0,"\n"

;-----------------------------------------------------------------------------
; Initialze disk and page area variables from first list entries
;-----------------------------------------------------------------------------
        mov      ebx,diskl
        mov      eax,gs:[ebx]          ; eax = disk list[0] sector addr
        mov      diskaa,eax            ; save in diskaa
        mov      eax,gs:[ebx+4]        ; eax = disk list[0] sector count
        mov      diskal,eax            ; save in diskal
        mov      diskan,1              ; next disk entry to use is [1]

        mov      ebx,pagel
        xor      eax,eax
        mov      ax,gs:[ebx]           ; ax = page list[0] page num
        shl      eax,12                ; convert to physical address
        mov      pageaa,eax            ; save in pageaa
        xor      eax,eax
        mov      ax,gs:[ebx+2]         ; eax = page list[0] page count
        shl      eax,3                 ; convert pages to sectors
        mov      pageal,eax            ; save in pageal
        mov      pagean,1              ; next page entry to use is [1]

;-----------------------------------------------------------------------------
; Find the largest contiguous area in both the current disk and page areas
;-----------------------------------------------------------------------------
hddsrealloop:
        mov      ebx,diskal            ; ebx = remaining sectors in disk area
        mov      eax,pageal            ; eax = remaining sectors in page area
        cmp      eax,ebx               ;
        jg       short gotsize         ; jump if page area larger
        mov      ebx,eax               ; replace disk sz w/smaller page sz
gotsize:                               ; here, ebx=min(disk sz, page sz) in sec
;-----------------------------------------------------------------------------
; call hddswrt to write it to disk
;-----------------------------------------------------------------------------
        push     ebx                   ; save sector count across call
        push     drive
        push     nsec
        push     secpcyl
        push     iobufsz
        push     iobuf
        push     ebx                   ; sector count
        push     pageaa                ; page address
        push     diskaa                ; sector number

        call     hddswrt
        pop      ebx                   ; restore sector count
        or       ax,ax
        jnz      hddsrealexit

;-----------------------------------------------------------------------------
; update disk and page area addresses and lengths by amount written
;-----------------------------------------------------------------------------
        add      diskaa,ebx            ; increment disk area address
        sub      diskal,ebx            ; decrement disk area len
if HYBERDEBUG_S GE 1
        jg       moredisk
else
        jg       short moredisk
endif
        mov      diskal,0              ; set length to zero
        mov      edx,diskan            ; dx = index of next disk list entry
        cmp      edx,diskln            ; compare it with the number of entries
if HYBERDEBUG_S GE 1
        jge      moredisk
else
        jge      short moredisk        ; jump if no more disk areas (diskal
endif                                  ;      is already zero, used below)
        mov      ecx,diskl             ; get pointer to disk list
        mov      eax,gs:[ecx][edx*8]   ; eax = next disk list sector address
        mov      diskaa,eax            ; save it in diskaa
        mov      eax,gs:[ecx][edx*8]+4 ; eax = next disk list sector count
        mov      diskal,eax            ; save it in diskal
        inc      edx                   ; set index to next entry
        mov      diskan,edx            ; save it
        cmp      edx,diskln            ; compare it with the number of entries
if HYBERDEBUG_S GE 1
        jl       moredisk
else
        jl       short moredisk        ; jump if this entry is not the last
endif
        mov      ax,drive2             ; use the last dlist entry drive id
        mov      drive,ax              ; update the drive parms next
;-----------------------------------------------------------------------------
; get drive parms, store in local vars nhead, nsec, ncyl; calculate secpcyl
;-----------------------------------------------------------------------------
        mov      ah,08h                ; ah = get drive parameters
        mov      dl,byte ptr drive     ; dl = drive number
        int      13h                   ; returns: dh = max head num
        jc       errgdp2               ;          cl[5:0] = max sec num
        inc      dh                    ;          cl[7:6] || ch = max cyl
        shr      dx,8
        mov      nhead,dx
        mov      al,cl
        and      ax,003Fh
        mov      nsec,ax
        shr      cl,6
        ror      cx,8
        inc      cx
        mov      ncyl,cx
        mov      ax,nsec
        mul      nhead
        mov      secpcyl,ax

        dbgmsg   0,"drive=",1,drive,0," nhead=",2,nhead,0," nsec=",2,nsec
        dbgmsg   0," ncyl=",2,ncyl,0," secpcyl=",2,secpcyl,0,"\n"

moredisk:
        mov      eax,ebx               ; eax = sector count
        shl      eax,9                 ; convert it to a byte count
        add      pageaa,eax            ; increment page area address
        sub      pageal,ebx            ; decrement page area len
        jg       short morepage        ; jump if area not exausted

        mov      pageal,0              ; set length to zero
        mov      edx,pagean            ; dx = index of next page list entry
        cmp      edx,pageln            ; compare it with the number of entries
        jge      short morepage        ; jump if no more page areas (pageal
                                       ;      is already zero, used below)
        mov      ecx,pagel             ; get pointer to page list
        xor      eax,eax
        mov      ax,gs:[ecx][edx*4]    ; eax = next page list page number
        shl      eax,12                ; convert to physical address
        mov      pageaa,eax            ; save it in pageaa
        xor      eax,eax
        mov      ax,gs:[ecx][edx*4]+2  ; eax = next page list page count
        shl      eax,3                 ; convert pages to sectors
        mov      pageal,eax            ; save it in pageal
        inc      pagean                ; set index to next entry
morepage:

;-----------------------------------------------------------------------------
; check for more data to write and disk list exausted conditions
;-----------------------------------------------------------------------------
        cmp      pageal,0
        je       short hddsrealexitok  ; no more pages; we're done
        cmp      diskal,0
        je       short errdle          ; disk list exausted, we're toast
        jmp      hddsrealloop          ; more pages, more disk; keep going

;-----------------------------------------------------------------------------
; Exit
;-----------------------------------------------------------------------------
hddsrealexitok:
        xor      ax,ax                 ; set no error rc
hddsrealexit:
        dbgmsg   0,"hddsreal done, ax=",2,ax,0,"\n"
        LeaveProc
        RETC                            ; return with ax = rc from hddsreal

;-----------------------------------------------------------------------------
; error return code setups
;-----------------------------------------------------------------------------
errgdp1:
        mov      al,01h
        jmp      short hddsrealexit
errgdp2:
        mov      al,02h
        jmp      short hddsrealexit
errdle:
        mov      ax,0103
        jmp      short hddsrealexit

EndProc hddsreal


Procedure hddswrt,near
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
    ArgVar      sect,      DWORD       ; absolute sector on disk
    ArgVar      src,       DWORD       ; physical data address
    ArgVar      secct,     DWORD       ; count of sectors to write
    ArgVar      iobuf,     DWORD       ; physical buffer addr (< 1M)
    ArgVar      iobufsz,   DWORD       ; size of buf (sectors)
    ArgVar      secpcyl,   WORD        ; sectors per cylinder
    ArgVar      secptrk,   WORD        ; sectors per track
    ArgVar      drive,     WORD        ; drive ID (80 = drive 0)
        EnterProc

if HYBERDEBUG_S GE 2
        dbgmsg   0,"hddswrt starting sect=",4,sect,0," src=",4,src,
        dbgmsg   0," secct=",4,secct,0," iobuf=",4,iobuf,0,"\n"
        dbgmsg   0,"        iobufsz=",4,iobufsz,0," secpcyl=",2,secpcyl
        dbgmsg   0," secptrk=",2,secptrk,0," drive=",2,drive,0,"\n"
endif ;HYBERDEBUG_S GE 2

;-----------------------------------------------------------------------------
; calculate write length
;-----------------------------------------------------------------------------
hddswrtloop:
        mov      ebx,secct             ; ebx = sector count
        mov      eax,100000h           ;
        sub      eax,src               ; eax = bytes of data < 1M
        jle      short bufcomp         ; jump if data addr >= 1M
        shr      eax,9                 ; eax = sectors of data < 1M
        cmp      eax,ebx               ;
        jge      short bufcomp         ; jump if sect < 1M GE sector ct
        mov      ebx,eax               ; ebx = sect ct < 1M (which is smaller)

bufcomp:                               ; here, ebx = min(secct, sectors < 1M)
        mov      eax,iobufsz
        cmp      eax,ebx
        jge      short gotcount        ; jump if buf size GE sect ct
        mov      ebx,eax               ; ebx = buf sz (which is smaller)

gotcount:                              ; here, ebx = count for copy / write
;-----------------------------------------------------------------------------
; copy data if above 1M
;-----------------------------------------------------------------------------
        mov      esi,src               ; si = source data address
        cmp      esi,100000h           ; compare with 1 Meg
if HYBERDEBUG_S GE 3
        jl       nocopy
else
        jl       short nocopy          ; jump if below 1M
endif
        mov      eax,iobuf             ; eax = iobuf physical address
        shr      eax,4                 ; ax = iobuf paragraph address
        mov      es,ax                 ; es = destination segment
        xor      edi,edi               ; clear desination index
        mov      ecx,ebx               ; ecx = copy count in sectors
        shl      ecx,7                 ; ecx = copy count in dwords
if HYBERDEBUG_S GE 3
        dbgmsg   0,"hddswrt copy from=",2,gs,0,":",4,esi,0," to="
        dbgmsg   2,es,0,":",4,edi,0," count=",2,cx,0," dwords\n"
endif ;HYBERDEBUG_S GE 3

    rep movs     es:dword ptr [edi],gs:dword ptr [esi]     ; copy data
        jmp      short ready2wrt

nocopy:
        mov      eax,src               ; eax = source data address
        shr      eax,4                 ; ax = source paragraph address
        mov      es,ax                 ; es = source segment

ready2wrt:                             ; here, ebx = sect ct and es =
                                       ;   source segment
;-----------------------------------------------------------------------------
; write data to disk
;-----------------------------------------------------------------------------
        xor      edx,edx
        mov      eax,sect              ; edx:eax = disk sector
        xor      ecx,ecx
        mov      cx,secpcyl            ; ecx = sectors per cyl
        div      ecx                   ; eax = cyl, edx = sect within cyl
        cmp      eax,3FFh
if HYBERDEBUG_S GE 2
        jg       errc2b
else
        jg       errc2b                ; jump if max cyl size exceeded
endif
        mov      di,ax                 ; di = cylinder address
        mov      eax,edx
        xor      edx,edx               ; edx:eax = sect within cyl
        mov      cx,secptrk            ; ecx = sectors per track
        div      ecx                   ; eax = head, edx = sect within track


        mov      cx,di                 ; cx[9:0] = cyl
        rol      cx,8                  ; cx[1:0] || cx[15:8] = cyl
        shl      cl,6                  ; cx[7:6] || cx[15:8] = cyl
        inc      dl                    ; since sector is 1 based
        or       cl,dl                 ; cx = cccccccc ccssssss
        mov      dh,al                 ; dh = head
        mov      dl,byte ptr drive     ; dl = drive (C: usually = 80h)
        mov      ah,03h                ; ah = write sectors command
        mov      al,bl                 ; al = sector count
        push     ebx
        xor      bx,bx                 ; es:bx = data address

;if HYBERDEBUG_S GE 3
        dbgmsg   0,"hddswrt int 13 ax=",2,ax,0," cx=",2,cx,0," dx=",2,dx
        dbgmsg   0," es:bx=",2,es,0,":",2,bx,0,"\n"
;endif ;HYBERDEBUG_S GE 3

        int      13h                   ; do it

        pop      ebx
        jc       short erri13w

;-----------------------------------------------------------------------------
; update addresses and count, loop if more to write
;-----------------------------------------------------------------------------
        sub      secct,ebx             ; decrement sector count
        jz       short hddswrtok       ; if zero, we're done
        add      sect,ebx              ; increment sector address
        shl      ebx,9                 ; ebx = byte length
        add      src,ebx               ; increment storage address
        jmp      hddswrtloop

;-----------------------------------------------------------------------------
; Exit
;-----------------------------------------------------------------------------
hddswrtok:
        xor      ax,ax                 ; set no error rc
hddswrtexit:
if HYBERDEBUG_S GE 2
        dbgmsg   0,"hddswrt done, ax=",2,ax,0,"\n"
endif ;HYBERDEBUG_S GE 2
        LeaveProc
        RETC                            ; return with ax = rc
;-----------------------------------------------------------------------------
; error return code setups
;-----------------------------------------------------------------------------
erri13w:
        mov      al,04h
        jmp      short hddswrtexit
errc2b:
        mov      ax,0105
        jmp      short hddswrtexit

EndProc hddswrt

CODE    ends

endif  ;HYBERI13

        end
